chore: make onboarding ready

This commit is contained in:
CMK 2021-02-26 18:27:47 +08:00
parent 24fc57d982
commit 7aa45ff230
30 changed files with 350 additions and 619 deletions

View File

@ -22,8 +22,8 @@
"cancel": "Cancel", "cancel": "Cancel",
"take_photo": "Take photo", "take_photo": "Take photo",
"save_photo": "Save photo", "save_photo": "Save photo",
"sign_in": "Sign in", "sign_in": "Sign In",
"sign_up": "Sign up", "sign_up": "Sign Up",
"see_more": "See More", "see_more": "See More",
"preview": "Preview", "preview": "Preview",
"open_in_safari": "Open in Safari" "open_in_safari": "Open in Safari"
@ -110,7 +110,7 @@
"dont_receive_email": { "dont_receive_email": {
"title": "Check your email", "title": "Check your email",
"description": "Check if your email address is correct as well as your junk folder if you havent.", "description": "Check if your email address is correct as well as your junk folder if you havent.",
"resend_email": "Resend email" "resend_email": "Resend Email"
}, },
"open_email_app": { "open_email_app": {
"title": "Check your inbox.", "title": "Check your inbox.",

View File

@ -10,8 +10,8 @@
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA0FDE25E0B57E0017CCDE /* WelcomeViewController.swift */; }; 0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA0FDE25E0B57E0017CCDE /* WelcomeViewController.swift */; };
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */; }; 0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */; };
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101B25E10E760017CCDE /* UIFont.swift */; }; 0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101B25E10E760017CCDE /* UIFont.swift */; };
0FAA102725E1126A0017CCDE /* PickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* PickServerViewController.swift */; }; 0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */; };
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */; }; 0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */; };
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */; }; 0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */; };
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */; }; 0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */; };
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; }; 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */; };
@ -83,7 +83,6 @@
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; }; 5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; };
5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; }; 5E0DEC05797A7E6933788DDB /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */; };
5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; }; 5E44BF88AD33646E64727BCF /* Pods_MastodonTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */; };
DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */; };
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; }; DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; };
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; }; DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */; };
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */; }; DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */; };
@ -95,6 +94,7 @@
DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; }; DB118A8C25E4BFB500FAB162 /* HighlightDimmableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */; };
DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; }; DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; };
DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; }; DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.swift */; };
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */; };
DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; }; DB3D0FF325BAA61700EAA174 /* AlamofireImage in Frameworks */ = {isa = PBXBuildFile; productRef = DB3D0FF225BAA61700EAA174 /* AlamofireImage */; };
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; }; DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; }; DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; };
@ -118,6 +118,7 @@
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; }; DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; };
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; }; DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */; };
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; }; DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */; };
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */; };
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; }; DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */; };
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; }; DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; };
DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; }; DB89B9F725C10FD0008580ED /* CoreDataStack.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */; };
@ -142,7 +143,6 @@
DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; }; DB8AF55025C13703002E6C99 /* MainTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */; };
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; }; DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.swift */; };
DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; }; DB8AF56825C13E2A002E6C99 /* HomeTimelineIndex.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */; };
DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */; };
DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; }; DB98336B25C9420100AD9700 /* APIService+App.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98336A25C9420100AD9700 /* APIService+App.swift */; };
DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; }; DB98337125C9443200AD9700 /* APIService+Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337025C9443200AD9700 /* APIService+Authentication.swift */; };
DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; }; DB98337F25C9452D00AD9700 /* APIService+APIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB98337E25C9452D00AD9700 /* APIService+APIError.swift */; };
@ -217,8 +217,8 @@
0FAA0FDE25E0B57E0017CCDE /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; }; 0FAA0FDE25E0B57E0017CCDE /* WelcomeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
0FAA101125E105390017CCDE /* PrimaryActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryActionButton.swift; sourceTree = "<group>"; }; 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrimaryActionButton.swift; sourceTree = "<group>"; };
0FAA101B25E10E760017CCDE /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; }; 0FAA101B25E10E760017CCDE /* UIFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIFont.swift; sourceTree = "<group>"; };
0FAA102625E1126A0017CCDE /* PickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewController.swift; sourceTree = "<group>"; }; 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewController.swift; sourceTree = "<group>"; };
0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewModel.swift; sourceTree = "<group>"; }; 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewModel.swift; sourceTree = "<group>"; };
0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerTitleCell.swift; sourceTree = "<group>"; }; 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerTitleCell.swift; sourceTree = "<group>"; };
0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = "<group>"; }; 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoriesCell.swift; sourceTree = "<group>"; };
0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = "<group>"; }; 0FB3D30E25E525CD00AAD544 /* PickServerCategoryView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerCategoryView.swift; sourceTree = "<group>"; };
@ -292,7 +292,6 @@
A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; }; A4ABE34829701A4496C5BB64 /* Pods_Mastodon.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon.framework; sourceTree = BUILT_PRODUCTS_DIR; };
BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = "<group>"; }; BB482D32A7B9825BF5327C4F /* Pods-Mastodon-MastodonUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.release.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.release.xcconfig"; sourceTree = "<group>"; };
CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; CD92E0F10BDE4FE7C4B999F2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewController.swift; sourceTree = "<group>"; };
DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewController.swift; sourceTree = "<group>"; }; DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewController.swift; sourceTree = "<group>"; };
DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; }; DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift; sourceTree = "<group>"; };
DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModel.swift; sourceTree = "<group>"; }; DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPinBasedAuthenticationViewModel.swift; sourceTree = "<group>"; };
@ -303,6 +302,7 @@
DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; }; DB118A8B25E4BFB500FAB162 /* HighlightDimmableButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDimmableButton.swift; sourceTree = "<group>"; };
DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; }; DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; }; DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = "<group>"; };
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = "<group>"; };
DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = "<group>"; };
DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -331,6 +331,7 @@
DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = "<group>"; }; DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = "<group>"; };
DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = "<group>"; }; DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = "<group>"; };
DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSKeyValueObservation.swift; sourceTree = "<group>"; }; DB68586325E619B700F0A850 /* NSKeyValueObservation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NSKeyValueObservation.swift; sourceTree = "<group>"; };
DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DarkContentStatusBarStyleNavigationController.swift; sourceTree = "<group>"; };
DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; }; DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = "<group>"; };
DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; }; DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = "<group>"; };
DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -357,7 +358,6 @@
DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = "<group>"; }; DB8AF54F25C13703002E6C99 /* MainTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainTabBarController.swift; sourceTree = "<group>"; };
DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; }; DB8AF55C25C138B7002E6C99 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = "<group>"; };
DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = "<group>"; }; DB8AF56725C13E2A002E6C99 /* HomeTimelineIndex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTimelineIndex.swift; sourceTree = "<group>"; };
DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationViewModel.swift; sourceTree = "<group>"; };
DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = "<group>"; }; DB98336A25C9420100AD9700 /* APIService+App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+App.swift"; sourceTree = "<group>"; };
DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = "<group>"; }; DB98337025C9443200AD9700 /* APIService+Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Authentication.swift"; sourceTree = "<group>"; };
DB98337E25C9452D00AD9700 /* APIService+APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+APIError.swift"; sourceTree = "<group>"; }; DB98337E25C9452D00AD9700 /* APIService+APIError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "APIService+APIError.swift"; sourceTree = "<group>"; };
@ -445,8 +445,8 @@
0FB3D31825E525DE00AAD544 /* CollectionViewCell */, 0FB3D31825E525DE00AAD544 /* CollectionViewCell */,
0FB3D30D25E525C000AAD544 /* View */, 0FB3D30D25E525C000AAD544 /* View */,
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */, 0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */,
0FAA102625E1126A0017CCDE /* PickServerViewController.swift */, 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */,
0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */, 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */,
); );
path = PickServer; path = PickServer;
sourceTree = "<group>"; sourceTree = "<group>";
@ -598,7 +598,6 @@
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */, 2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */, 2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */, 2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */,
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
); );
path = Protocol; path = Protocol;
sourceTree = "<group>"; sourceTree = "<group>";
@ -636,6 +635,7 @@
2D7631A425C1532200929FB9 /* Share */ = { 2D7631A425C1532200929FB9 /* Share */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DB68A04F25E9028800CFDF14 /* NavigationController */,
DB9D6C2025E502C60051B173 /* ViewModel */, DB9D6C2025E502C60051B173 /* ViewModel */,
2D7631A525C1532D00929FB9 /* View */, 2D7631A525C1532D00929FB9 /* View */,
); );
@ -695,27 +695,26 @@
DB01409B25C40BB600F9F3CF /* Onboarding */ = { DB01409B25C40BB600F9F3CF /* Onboarding */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DB68A03825E900CC00CFDF14 /* Share */,
0FAA0FDD25E0B5700017CCDE /* Welcome */, 0FAA0FDD25E0B5700017CCDE /* Welcome */,
0FAA102525E1125D0017CCDE /* PickServer */, 0FAA102525E1125D0017CCDE /* PickServer */,
DB0140A625C40C0900F9F3CF /* PinBasedAuthentication */,
DBE0821A25CD382900FD6BBD /* Register */, DBE0821A25CD382900FD6BBD /* Register */,
DB72602125E36A2500235243 /* ServerRules */, DB72602125E36A2500235243 /* ServerRules */,
2D364F7025E66D5B00204FDC /* ResendEmail */, 2D364F7025E66D5B00204FDC /* ResendEmail */,
2D59819925E4A55C000FB903 /* ConfirmEmail */, 2D59819925E4A55C000FB903 /* ConfirmEmail */,
DB0140A625C40C0900F9F3CF /* PinBased */,
DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */,
DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */,
); );
path = Onboarding; path = Onboarding;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DB0140A625C40C0900F9F3CF /* PinBased */ = { DB0140A625C40C0900F9F3CF /* PinBasedAuthentication */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */, DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */,
DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */, DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */,
DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */, DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */,
); );
path = PinBased; path = PinBasedAuthentication;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DB084B5125CBC56300F898ED /* CoreDataStack */ = { DB084B5125CBC56300F898ED /* CoreDataStack */ = {
@ -856,6 +855,23 @@
path = Preference; path = Preference;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
DB68A03825E900CC00CFDF14 /* Share */ = {
isa = PBXGroup;
children = (
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
DB2F073325E8ECF000957B2D /* AuthenticationViewModel.swift */,
);
path = Share;
sourceTree = "<group>";
};
DB68A04F25E9028800CFDF14 /* NavigationController */ = {
isa = PBXGroup;
children = (
DB68A04925E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift */,
);
path = NavigationController;
sourceTree = "<group>";
};
DB72602125E36A2500235243 /* ServerRules */ = { DB72602125E36A2500235243 /* ServerRules */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -1412,7 +1428,7 @@
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */, 2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */, DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */,
2D7631B325C159F700929FB9 /* Item.swift in Sources */, 2D7631B325C159F700929FB9 /* Item.swift in Sources */,
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */, 0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */, 2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */, 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */, 0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
@ -1427,7 +1443,7 @@
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */, 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */, DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */, 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
0FAA102725E1126A0017CCDE /* PickServerViewController.swift in Sources */, 0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */,
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */, DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */,
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */, 2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */,
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */, DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
@ -1467,13 +1483,14 @@
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */, DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */, 2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */, 0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */,
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */,
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */, 0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */, 2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */,
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */, DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */, DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */, DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */, 2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */,
DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */,
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */, 2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */, DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */, 2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
@ -1504,7 +1521,6 @@
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */, 2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */, 2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */,
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */, 2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */,
DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */,
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */, DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */,
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */, DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */,
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */, DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,

View File

@ -7,12 +7,17 @@
<key>CoreDataStack.xcscheme_^#shared#^_</key> <key>CoreDataStack.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>9</integer> <integer>11</integer>
</dict> </dict>
<key>Mastodon - RTL.xcscheme_^#shared#^_</key> <key>Mastodon - RTL.xcscheme_^#shared#^_</key>
<dict> <dict>
<key>orderHint</key> <key>orderHint</key>
<integer>8</integer> <integer>9</integer>
</dict>
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
</dict> </dict>
<key>Mastodon.xcscheme_^#shared#^_</key> <key>Mastodon.xcscheme_^#shared#^_</key>
<dict> <dict>

View File

@ -38,44 +38,61 @@ extension SceneCoordinator {
} }
enum Scene { enum Scene {
// onboarding
case welcome case welcome
case pickServer(viewMode: PickServerViewModel) case mastodonPickServer(viewMode: MastodonPickServerViewModel)
case authentication(viewModel: AuthenticationViewModel)
case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel) case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel)
case mastodonRegister(viewModel: MastodonRegisterViewModel) case mastodonRegister(viewModel: MastodonRegisterViewModel)
case mastodonServerRules(viewModel: MastodonServerRulesViewModel) case mastodonServerRules(viewModel: MastodonServerRulesViewModel)
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel) case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
case mastodonResendEmail(viewModel: MastodonResendEmailViewModel) case mastodonResendEmail(viewModel: MastodonResendEmailViewModel)
// misc
case alertController(alertController: UIAlertController) case alertController(alertController: UIAlertController)
#if DEBUG #if DEBUG
case publicTimeline case publicTimeline
#endif #endif
var isOnboarding: Bool {
switch self {
case .welcome,
.mastodonPickServer,
.mastodonPinBasedAuthentication,
.mastodonRegister,
.mastodonServerRules,
.mastodonConfirmEmail,
.mastodonResendEmail:
return true
default:
return false
}
}
} }
} }
extension SceneCoordinator { extension SceneCoordinator {
func setup() { func setup() {
// Check user authentication status let viewController = MainTabBarController(context: appContext, coordinator: self)
sceneDelegate.window?.rootViewController = viewController
let request = MastodonAuthentication.sortedFetchRequest }
func setupOnboardingIfNeeds(animated: Bool) {
// Check user authentication status and show onboarding if needs
do { do {
let fetchResult = try appContext.managedObjectContext.fetch(request) let request = MastodonAuthentication.sortedFetchRequest
DispatchQueue.main.async { if try appContext.managedObjectContext.fetch(request).isEmpty {
var rootViewController: UIViewController DispatchQueue.main.async {
if fetchResult.isEmpty { self.present(
let welcomViewController = WelcomeViewController() scene: .welcome,
self.setupDependency(for: welcomViewController) from: nil,
rootViewController = UINavigationController(rootViewController: welcomViewController) transition: .modal(animated: animated, completion: nil)
} else { )
rootViewController = MainTabBarController(context: self.appContext, coordinator: self)
} }
self.sceneDelegate.window?.rootViewController = rootViewController
} }
} catch { } catch {
assertionFailure("CoreDataStack error at app launch!") assertionFailure(error.localizedDescription)
} }
} }
@ -103,7 +120,13 @@ extension SceneCoordinator {
presentingViewController.showDetailViewController(navigationController, sender: sender) presentingViewController.showDetailViewController(navigationController, sender: sender)
case .modal(let animated, let completion): case .modal(let animated, let completion):
let modalNavigationController = UINavigationController(rootViewController: viewController) let modalNavigationController: UINavigationController = {
if scene.isOnboarding {
return DarkContentStatusBarStyleNavigationController(rootViewController: viewController)
} else {
return UINavigationController(rootViewController: viewController)
}
}()
if let adaptivePresentationControllerDelegate = viewController as? UIAdaptivePresentationControllerDelegate { if let adaptivePresentationControllerDelegate = viewController as? UIAdaptivePresentationControllerDelegate {
modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate
} }
@ -143,12 +166,8 @@ private extension SceneCoordinator {
case .welcome: case .welcome:
let _viewController = WelcomeViewController() let _viewController = WelcomeViewController()
viewController = _viewController viewController = _viewController
case .pickServer(let viewModel): case .mastodonPickServer(let viewModel):
let _viewController = PickServerViewController() let _viewController = MastodonPickServerViewController()
_viewController.viewModel = viewModel
viewController = _viewController
case .authentication(let viewModel):
let _viewController = AuthenticationViewController()
_viewController.viewModel = viewModel _viewController.viewModel = viewModel
viewController = _viewController viewController = _viewController
case .mastodonPinBasedAuthentication(let viewModel): case .mastodonPinBasedAuthentication(let viewModel):

View File

@ -50,9 +50,9 @@ internal enum L10n {
internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto") internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto")
/// See More /// See More
internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore") internal static let seeMore = L10n.tr("Localizable", "Common.Controls.Actions.SeeMore")
/// Sign in /// Sign In
internal static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn") internal static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn")
/// Sign up /// Sign Up
internal static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp") internal static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp")
/// Take photo /// Take photo
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto") internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
@ -101,7 +101,7 @@ internal enum L10n {
internal enum DontReceiveEmail { internal enum DontReceiveEmail {
/// Check if your email address is correct as well as your junk folder if you havent. /// Check if your email address is correct as well as your junk folder if you havent.
internal static let description = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.Description") internal static let description = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.Description")
/// Resend email /// Resend Email
internal static let resendEmail = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail") internal static let resendEmail = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail")
/// Check your email /// Check your email
internal static let title = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.Title") internal static let title = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.Title")

View File

@ -13,8 +13,8 @@
"Common.Controls.Actions.Save" = "Save"; "Common.Controls.Actions.Save" = "Save";
"Common.Controls.Actions.SavePhoto" = "Save photo"; "Common.Controls.Actions.SavePhoto" = "Save photo";
"Common.Controls.Actions.SeeMore" = "See More"; "Common.Controls.Actions.SeeMore" = "See More";
"Common.Controls.Actions.SignIn" = "Sign in"; "Common.Controls.Actions.SignIn" = "Sign In";
"Common.Controls.Actions.SignUp" = "Sign up"; "Common.Controls.Actions.SignUp" = "Sign Up";
"Common.Controls.Actions.TakePhoto" = "Take photo"; "Common.Controls.Actions.TakePhoto" = "Take photo";
"Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive"; "Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive";
"Common.Controls.Status.ShowPost" = "Show Post"; "Common.Controls.Status.ShowPost" = "Show Post";
@ -26,7 +26,7 @@
"Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email"; "Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email";
"Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App"; "Scene.ConfirmEmail.Button.OpenEmailApp" = "Open Email App";
"Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you havent."; "Scene.ConfirmEmail.DontReceiveEmail.Description" = "Check if your email address is correct as well as your junk folder if you havent.";
"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Resend email"; "Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Resend Email";
"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Check your email"; "Scene.ConfirmEmail.DontReceiveEmail.Title" = "Check your email";
"Scene.ConfirmEmail.OpenEmailApp.Description" = "We just sent you an email. Check your junk folder if you havent."; "Scene.ConfirmEmail.OpenEmailApp.Description" = "We just sent you an email. Check your junk folder if you havent.";
"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Mail"; "Scene.ConfirmEmail.OpenEmailApp.Mail" = "Mail";
@ -62,4 +62,4 @@ any server.";
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@."; "Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
"Scene.ServerRules.Title" = "Some ground rules."; "Scene.ServerRules.Title" = "Some ground rules.";
"Scene.Welcome.Slogan" = "Social networking "Scene.Welcome.Slogan" = "Social networking
back in your hands."; back in your hands.";

View File

@ -37,35 +37,5 @@ extension HomeTimelineViewController {
coordinator.present(scene: .publicTimeline, from: self, transition: .show) coordinator.present(scene: .publicTimeline, from: self, transition: .show)
} }
@objc private func signOutAction(_ sender: UIAction) {
guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else {
return
}
let currentAccountCount = context.authenticationService.mastodonAuthentications.value.count
let isAuthenticationExistWhenSignOut = currentAccountCount - 1 > 0
// prepare advance
let authenticationViewModel = AuthenticationViewModel(context: context, coordinator: coordinator, isAuthenticationExist: isAuthenticationExistWhenSignOut)
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 }
if !isAuthenticationExistWhenSignOut {
self.coordinator.present(scene: .authentication(viewModel: authenticationViewModel), from: nil, transition: .modal(animated: true, completion: nil))
}
}
}
.store(in: &disposeBag)
}
} }
#endif #endif

View File

@ -70,8 +70,20 @@ extension HomeTimelineViewController {
return imageView return imageView
}() }()
navigationItem.leftBarButtonItem = settingBarButtonItem navigationItem.leftBarButtonItem = settingBarButtonItem
settingBarButtonItem.target = self #if DEBUG
settingBarButtonItem.action = #selector(HomeTimelineViewController.settingBarButtonItemPressed(_:)) // long press to trigger debug menu
settingBarButtonItem.menu = debugMenu
#else
// settingBarButtonItem.target = self
// settingBarButtonItem.action = #selector(HomeTimelineViewController.settingBarButtonItemPressed(_:))
settingBarButtonItem.menu = UIMenu(title: "Settings", image: nil, identifier: nil, options: .displayInline, children: [
UIAction(title: "Sign Out", image: UIImage(systemName: "escape"), attributes: .destructive) { [weak self] action in
guard let self = self else { return }
self.signOutAction(action)
}
])
#endif
navigationItem.rightBarButtonItem = composeBarButtonItem navigationItem.rightBarButtonItem = composeBarButtonItem
composeBarButtonItem.target = self composeBarButtonItem.target = self
composeBarButtonItem.action = #selector(HomeTimelineViewController.composeBarButtonItemPressed(_:)) composeBarButtonItem.action = #selector(HomeTimelineViewController.composeBarButtonItemPressed(_:))
@ -111,11 +123,6 @@ extension HomeTimelineViewController {
} }
} }
.store(in: &disposeBag) .store(in: &disposeBag)
#if DEBUG
// long press to trigger debug menu
settingBarButtonItem.menu = debugMenu
#endif
} }
@ -169,6 +176,30 @@ extension HomeTimelineViewController {
} }
} }
@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)
}
} }

View File

@ -1,354 +0,0 @@
//
// AuthenticationViewController.swift
// Mastodon
//
// Created by MainasuK Cirno on 2021/1/29.
//
import os.log
import UIKit
import Combine
import MastodonSDK
import UITextField_Shake
final class AuthenticationViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance{
var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: AuthenticationViewModel!
let domainLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .headline)
label.textColor = Asset.Colors.Label.primary.color
label.text = "Domain:"
return label
}()
let domainTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "example.com"
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = .URL
return textField
}()
let signInButton: UIButton = {
let button = UIButton(type: .system)
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Background.secondarySystemBackground.color), for: .normal)
button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.Background.secondarySystemBackground.color.withAlphaComponent(0.8)), for: .disabled)
button.setTitleColor(Asset.Colors.Label.primary.color, for: .normal)
button.setTitle("Sign in", for: .normal)
button.layer.masksToBounds = true
button.layer.cornerRadius = 8
button.layer.cornerCurve = .continuous
return button
}()
let signUpButton: UIButton = {
let button = UIButton(type: .system)
button.titleLabel?.font = .preferredFont(forTextStyle: .subheadline)
button.setTitleColor(Asset.Colors.Button.highlight.color, for: .normal)
button.setTitleColor(.systemGray, for: .disabled)
button.setTitle("Sign up", for: .normal)
return button
}()
let signInActivityIndicatorView: UIActivityIndicatorView = {
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
activityIndicatorView.hidesWhenStopped = true
return activityIndicatorView
}()
let signUpActivityIndicatorView: UIActivityIndicatorView = {
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
activityIndicatorView.hidesWhenStopped = true
return activityIndicatorView
}()
}
extension AuthenticationViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.setupOnboardingAppearance()
domainLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(domainLabel)
NSLayoutConstraint.activate([
domainLabel.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 16),
domainLabel.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
domainLabel.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
])
domainTextField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(domainTextField)
NSLayoutConstraint.activate([
domainTextField.topAnchor.constraint(equalTo: domainLabel.bottomAnchor, constant: 8),
domainTextField.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
domainTextField.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
])
signInButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(signInButton)
NSLayoutConstraint.activate([
signInButton.topAnchor.constraint(equalTo: domainTextField.bottomAnchor, constant: 20),
signInButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
signInButton.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
signInButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh),
])
signInActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(signInActivityIndicatorView)
NSLayoutConstraint.activate([
signInActivityIndicatorView.centerXAnchor.constraint(equalTo: signInButton.centerXAnchor),
signInActivityIndicatorView.centerYAnchor.constraint(equalTo: signInButton.centerYAnchor),
])
signUpButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(signUpButton)
NSLayoutConstraint.activate([
signUpButton.topAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: 8),
signUpButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
signUpButton.trailingAnchor.constraint(equalTo: view.readableContentGuide.trailingAnchor),
signUpButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh),
])
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(signUpActivityIndicatorView)
NSLayoutConstraint.activate([
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
])
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: domainTextField)
.compactMap { notification in
guard let textField = notification.object as? UITextField? else { return nil }
return textField?.text ?? ""
}
.assign(to: \.value, on: viewModel.input)
.store(in: &disposeBag)
viewModel.isAuthenticating
.receive(on: DispatchQueue.main)
.sink { [weak self] isAuthenticating in
guard let self = self else { return }
isAuthenticating ? self.signInActivityIndicatorView.startAnimating() : self.signInActivityIndicatorView.stopAnimating()
self.signInButton.setTitle(isAuthenticating ? "" : "Sign in", for: .normal)
}
.store(in: &disposeBag)
viewModel.isRegistering
.receive(on: DispatchQueue.main)
.sink { [weak self] isRegistering in
guard let self = self else { return }
isRegistering ? self.signUpActivityIndicatorView.startAnimating() : self.signUpActivityIndicatorView.stopAnimating()
self.signUpButton.setTitle(isRegistering ? "" : "Sign up", for: .normal)
}
.store(in: &disposeBag)
viewModel.isIdle
.receive(on: DispatchQueue.main)
.sink { [weak self] isIdle in
guard let self = self else { return }
self.signInButton.isEnabled = isIdle
self.signUpButton.isEnabled = isIdle
}
.store(in: &disposeBag)
viewModel.authenticated
.receive(on: DispatchQueue.main)
.sink { [weak self] domain, user in
guard let self = self else { return }
// reset view hierarchy only if needs
if self.viewModel.viewHierarchyShouldReset {
self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
.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 isActived):
assert(isActived)
self.coordinator.setup()
}
}
.store(in: &self.disposeBag)
} else {
self.dismiss(animated: true, completion: nil)
}
}
.store(in: &disposeBag)
viewModel.error
.compactMap { $0 }
.receive(on: DispatchQueue.main)
.sink { [weak self] error in
guard let self = self else { return }
let alertController = UIAlertController(for: error, title: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: "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)
signInButton.addTarget(self, action: #selector(AuthenticationViewController.signInButtonPressed(_:)), for: .touchUpInside)
signUpButton.addTarget(self, action: #selector(AuthenticationViewController.signUpButtonPressed(_:)), for: .touchUpInside)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
domainTextField.becomeFirstResponder()
}
}
extension AuthenticationViewController {
@objc private func signInButtonPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
guard viewModel.isDomainValid.value, let domain = viewModel.domain.value else {
domainTextField.shake()
return
}
guard viewModel.isIdle.value else { return }
viewModel.isAuthenticating.value = true
context.apiService.createApplication(domain: domain)
.tryMap { response -> AuthenticationViewModel.AuthenticateInfo in
let application = response.value
guard let info = AuthenticationViewModel.AuthenticateInfo(domain: domain, application: application) else {
throw APIService.APIError.explicit(.badResponse)
}
return info
}
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
// trigger state update
self.viewModel.isAuthenticating.value = false
switch completion {
case .failure(let error):
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: sign in fail: %s", ((#file as NSString).lastPathComponent), #line, #function, error.localizedDescription)
self.viewModel.error.value = error
case .finished:
break
}
} receiveValue: { [weak self] info in
guard let self = self else { return }
let mastodonPinBasedAuthenticationViewModel = MastodonPinBasedAuthenticationViewModel(authenticateURL: info.authorizeURL)
self.viewModel.authenticate(
info: info,
pinCodePublisher: mastodonPinBasedAuthenticationViewModel.pinCodePublisher
)
self.viewModel.mastodonPinBasedAuthenticationViewController = self.coordinator.present(
scene: .mastodonPinBasedAuthentication(viewModel: mastodonPinBasedAuthenticationViewModel),
from: nil,
transition: .modal(animated: true, completion: nil)
)
}
.store(in: &disposeBag)
}
private struct SignUpResponseFirst {
let instance: Mastodon.Response.Content<Mastodon.Entity.Instance>
let application: Mastodon.Response.Content<Mastodon.Entity.Application>
}
private struct SignUpResponseSecond {
let instance: Mastodon.Response.Content<Mastodon.Entity.Instance>
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
}
private struct SignUpResponseThird {
let instance: Mastodon.Response.Content<Mastodon.Entity.Instance>
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
let applicationToken: Mastodon.Response.Content<Mastodon.Entity.Token>
}
@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.isDomainValid.value, let domain = viewModel.domain.value else {
domainTextField.shake()
return
}
guard viewModel.isIdle.value else { return }
viewModel.isRegistering.value = true
context.apiService.instance(domain: domain)
.compactMap { [weak self] response -> AnyPublisher<SignUpResponseFirst, Error>? in
guard let self = self else { return nil }
guard response.value.registrations != false else {
return Fail(error: AuthenticationViewModel.AuthenticationError.registrationClosed).eraseToAnyPublisher()
}
return self.context.apiService.createApplication(domain: domain)
.map { SignUpResponseFirst(instance: response, application: $0) }
.eraseToAnyPublisher()
}
.switchToLatest()
.tryMap { response -> SignUpResponseSecond in
let application = response.application.value
guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(domain: domain, application: application) else {
throw APIService.APIError.explicit(.badResponse)
}
return SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo)
}
.compactMap { [weak self] response -> AnyPublisher<SignUpResponseThird, Error>? in
guard let self = self else { return nil }
let instance = response.instance
let authenticateInfo = response.authenticateInfo
return self.context.apiService.applicationAccessToken(
domain: domain,
clientID: authenticateInfo.clientID,
clientSecret: authenticateInfo.clientSecret
)
.map { SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
.eraseToAnyPublisher()
}
.switchToLatest()
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
self.viewModel.isRegistering.value = false
switch completion {
case .failure(let error):
self.viewModel.error.send(error)
case .finished:
break
}
} receiveValue: { [weak self] response in
guard let self = self else { return }
let mastodonRegisterViewModel = MastodonRegisterViewModel(
domain: domain,
authenticateInfo: response.authenticateInfo,
instance: response.instance.value,
applicationToken: response.applicationToken.value
)
self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: self, transition: .show)
}
.store(in: &disposeBag)
}
}
// MARK: - UIAdaptivePresentationControllerDelegate
extension AuthenticationViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .fullScreen
}
}

View File

@ -11,7 +11,8 @@ import os.log
import ThirdPartyMailer import ThirdPartyMailer
import UIKit import UIKit
final class MastodonConfirmEmailViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance { final class MastodonConfirmEmailViewController: UIViewController, NeedsDependency {
var disposeBag = Set<AnyCancellable>() var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
@ -57,13 +58,18 @@ final class MastodonConfirmEmailViewController: UIViewController, NeedsDependenc
button.addTarget(self, action: #selector(dontReceiveButtonPressed(_:)), for: UIControl.Event.touchUpInside) button.addTarget(self, action: #selector(dontReceiveButtonPressed(_:)), for: UIControl.Event.touchUpInside)
return button return button
}() }()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
} }
extension MastodonConfirmEmailViewController { extension MastodonConfirmEmailViewController {
override func viewDidLoad() { override func viewDidLoad() {
self.setupOnboardingAppearance() setupOnboardingAppearance()
// resizedView // resizedView
let resizedView = UIView() let resizedView = UIView()
@ -107,19 +113,15 @@ extension MastodonConfirmEmailViewController {
case .finished: case .finished:
break break
} }
} receiveValue: { _ in } receiveValue: { response in
self.coordinator.setup() os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s: user %s's email confirmed", ((#file as NSString).lastPathComponent), #line, #function, response.value.username)
self.dismiss(animated: true, completion: nil)
} }
.store(in: &self.disposeBag) .store(in: &self.disposeBag)
} }
.store(in: &self.disposeBag) .store(in: &self.disposeBag)
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: false)
}
} }
extension MastodonConfirmEmailViewController { extension MastodonConfirmEmailViewController {
@ -174,3 +176,6 @@ extension MastodonConfirmEmailViewController {
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil)) self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
} }
} }
// MARK: - OnboardingViewControllerAppearance
extension MastodonConfirmEmailViewController: OnboardingViewControllerAppearance { }

View File

@ -9,7 +9,7 @@ import UIKit
class PickServerCategoryCollectionViewCell: UICollectionViewCell { class PickServerCategoryCollectionViewCell: UICollectionViewCell {
var category: PickServerViewModel.Category? { var category: MastodonPickServerViewModel.Category? {
didSet { didSet {
categoryView.category = category categoryView.category = category
} }

View File

@ -1,5 +1,5 @@
// //
// PickServerViewController.swift // MastodonPickServerViewController.swift
// Mastodon // Mastodon
// //
// Created by BradGao on 2021/2/20. // Created by BradGao on 2021/2/20.
@ -10,14 +10,14 @@ import Combine
import OSLog import OSLog
import MastodonSDK import MastodonSDK
final class PickServerViewController: UIViewController, NeedsDependency { final class MastodonPickServerViewController: UIViewController, NeedsDependency {
private var disposeBag = Set<AnyCancellable>() private var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
var viewModel: PickServerViewModel! var viewModel: MastodonPickServerViewModel!
private var isAuthenticating = CurrentValueSubject<Bool, Never>(false) private var isAuthenticating = CurrentValueSubject<Bool, Never>(false)
@ -58,7 +58,7 @@ final class PickServerViewController: UIViewController, NeedsDependency {
} }
extension PickServerViewController { extension MastodonPickServerViewController {
override var preferredStatusBarStyle: UIStatusBarStyle { override var preferredStatusBarStyle: UIStatusBarStyle {
return .darkContent return .darkContent
@ -72,9 +72,9 @@ extension PickServerViewController {
view.addSubview(nextStepButton) view.addSubview(nextStepButton)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
nextStepButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12), nextStepButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: MastodonPickServerViewController.actionButtonMargin),
view.readableContentGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor, constant: 12), view.readableContentGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor, constant: MastodonPickServerViewController.actionButtonMargin),
nextStepButton.heightAnchor.constraint(equalToConstant: PickServerViewController.actionButtonHeight).priority(.defaultHigh), nextStepButton.heightAnchor.constraint(equalToConstant: MastodonPickServerViewController.actionButtonHeight).priority(.defaultHigh),
view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: nextStepButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight), view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: nextStepButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
]) ])
@ -139,11 +139,11 @@ extension PickServerViewController {
viewModel viewModel
.authenticated .authenticated
.receive(on: DispatchQueue.main)
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in .flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() } guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() }
return self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id) return self.context.authenticationService.activeMastodonUser(domain: domain, userID: user.id)
} }
.receive(on: DispatchQueue.main)
.sink { [weak self] result in .sink { [weak self] result in
guard let self = self else { return } guard let self = self else { return }
switch result { switch result {
@ -151,7 +151,7 @@ extension PickServerViewController {
assertionFailure(error.localizedDescription) assertionFailure(error.localizedDescription)
case .success(let isActived): case .success(let isActived):
assert(isActived) assert(isActived)
self.coordinator.setup() self.dismiss(animated: true, completion: nil)
} }
} }
.store(in: &disposeBag) .store(in: &disposeBag)
@ -181,9 +181,9 @@ extension PickServerViewController {
guard let server = viewModel.selectedServer.value else { return } guard let server = viewModel.selectedServer.value else { return }
isAuthenticating.send(true) isAuthenticating.send(true)
context.apiService.createApplication(domain: server.domain) context.apiService.createApplication(domain: server.domain)
.tryMap { response -> PickServerViewModel.AuthenticateInfo in .tryMap { response -> MastodonPickServerViewModel.AuthenticateInfo in
let application = response.value let application = response.value
guard let info = PickServerViewModel.AuthenticateInfo(domain: server.domain, application: application) else { guard let info = MastodonPickServerViewModel.AuthenticateInfo(domain: server.domain, application: application) else {
throw APIService.APIError.explicit(.badResponse) throw APIService.APIError.explicit(.badResponse)
} }
return info return info
@ -222,24 +222,24 @@ extension PickServerViewController {
isAuthenticating.send(true) isAuthenticating.send(true)
context.apiService.instance(domain: server.domain) context.apiService.instance(domain: server.domain)
.compactMap { [weak self] response -> AnyPublisher<PickServerViewModel.SignUpResponseFirst, Error>? in .compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseFirst, Error>? in
guard let self = self else { return nil } guard let self = self else { return nil }
guard response.value.registrations != false else { guard response.value.registrations != false else {
return Fail(error: AuthenticationViewModel.AuthenticationError.registrationClosed).eraseToAnyPublisher() return Fail(error: AuthenticationViewModel.AuthenticationError.registrationClosed).eraseToAnyPublisher()
} }
return self.context.apiService.createApplication(domain: server.domain) return self.context.apiService.createApplication(domain: server.domain)
.map { PickServerViewModel.SignUpResponseFirst(instance: response, application: $0) } .map { MastodonPickServerViewModel.SignUpResponseFirst(instance: response, application: $0) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
.switchToLatest() .switchToLatest()
.tryMap { response -> PickServerViewModel.SignUpResponseSecond in .tryMap { response -> MastodonPickServerViewModel.SignUpResponseSecond in
let application = response.application.value let application = response.application.value
guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(domain: server.domain, application: application) else { guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(domain: server.domain, application: application) else {
throw APIService.APIError.explicit(.badResponse) throw APIService.APIError.explicit(.badResponse)
} }
return PickServerViewModel.SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo) return MastodonPickServerViewModel.SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo)
} }
.compactMap { [weak self] response -> AnyPublisher<PickServerViewModel.SignUpResponseThird, Error>? in .compactMap { [weak self] response -> AnyPublisher<MastodonPickServerViewModel.SignUpResponseThird, Error>? in
guard let self = self else { return nil } guard let self = self else { return nil }
let instance = response.instance let instance = response.instance
let authenticateInfo = response.authenticateInfo let authenticateInfo = response.authenticateInfo
@ -248,7 +248,7 @@ extension PickServerViewController {
clientID: authenticateInfo.clientID, clientID: authenticateInfo.clientID,
clientSecret: authenticateInfo.clientSecret clientSecret: authenticateInfo.clientSecret
) )
.map { PickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) } .map { MastodonPickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
.eraseToAnyPublisher() .eraseToAnyPublisher()
} }
.switchToLatest() .switchToLatest()
@ -277,7 +277,7 @@ extension PickServerViewController {
} }
} }
extension PickServerViewController: UITableViewDelegate { extension MastodonPickServerViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let category = Section.allCases[section] let category = Section.allCases[section]
switch category { switch category {
@ -316,7 +316,7 @@ extension PickServerViewController: UITableViewDelegate {
} }
} }
extension PickServerViewController: UITableViewDataSource { extension MastodonPickServerViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return UIView() return UIView()
} }
@ -374,7 +374,7 @@ extension PickServerViewController: UITableViewDataSource {
} }
} }
extension PickServerViewController: PickServerCellDelegate { extension MastodonPickServerViewController: PickServerCellDelegate {
func pickServerCell(modeChange server: Mastodon.Entity.Server, newMode: PickServerCell.Mode, updates: (() -> Void)) { func pickServerCell(modeChange server: Mastodon.Entity.Server, newMode: PickServerCell.Mode, updates: (() -> Void)) {
if newMode == .collapse { if newMode == .collapse {
expandServerDomainSet.remove(server.domain) expandServerDomainSet.remove(server.domain)
@ -392,18 +392,18 @@ extension PickServerViewController: PickServerCellDelegate {
} }
} }
extension PickServerViewController: PickServerSearchCellDelegate { extension MastodonPickServerViewController: PickServerSearchCellDelegate {
func pickServerSearchCell(didChange searchText: String?) { func pickServerSearchCell(didChange searchText: String?) {
viewModel.searchText.send(searchText) viewModel.searchText.send(searchText)
} }
} }
extension PickServerViewController: PickServerCategoriesDataSource, PickServerCategoriesDelegate { extension MastodonPickServerViewController: PickServerCategoriesDataSource, PickServerCategoriesDelegate {
func numberOfCategories() -> Int { func numberOfCategories() -> Int {
return viewModel.categories.count return viewModel.categories.count
} }
func category(at index: Int) -> PickServerViewModel.Category { func category(at index: Int) -> MastodonPickServerViewModel.Category {
return viewModel.categories[index] return viewModel.categories[index]
} }
@ -417,4 +417,4 @@ extension PickServerViewController: PickServerCategoriesDataSource, PickServerCa
} }
// MARK: - OnboardingViewControllerAppearance // MARK: - OnboardingViewControllerAppearance
extension PickServerViewController: OnboardingViewControllerAppearance { } extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }

View File

@ -1,5 +1,5 @@
// //
// PickServerViewModel.swift // MastodonPickServerViewModel.swift
// Mastodon // Mastodon
// //
// Created by BradGao on 2021/2/23. // Created by BradGao on 2021/2/23.
@ -11,7 +11,7 @@ import Combine
import MastodonSDK import MastodonSDK
import CoreDataStack import CoreDataStack
class PickServerViewModel: NSObject { class MastodonPickServerViewModel: NSObject {
enum PickServerMode { enum PickServerMode {
case signUp case signUp
case signIn case signIn
@ -173,7 +173,7 @@ class PickServerViewModel: NSObject {
} }
// MARK: - SignIn methods & structs // MARK: - SignIn methods & structs
extension PickServerViewModel { extension MastodonPickServerViewModel {
enum AuthenticationError: Error, LocalizedError { enum AuthenticationError: Error, LocalizedError {
case badCredentials case badCredentials
case registrationClosed case registrationClosed
@ -322,7 +322,7 @@ extension PickServerViewModel {
} }
// MARK: - SignUp methods & structs // MARK: - SignUp methods & structs
extension PickServerViewModel { extension MastodonPickServerViewModel {
struct SignUpResponseFirst { struct SignUpResponseFirst {
let instance: Mastodon.Response.Content<Mastodon.Entity.Instance> let instance: Mastodon.Response.Content<Mastodon.Entity.Instance>
let application: Mastodon.Response.Content<Mastodon.Entity.Application> let application: Mastodon.Response.Content<Mastodon.Entity.Application>

View File

@ -10,7 +10,7 @@ import MastodonSDK
protocol PickServerCategoriesDataSource: class { protocol PickServerCategoriesDataSource: class {
func numberOfCategories() -> Int func numberOfCategories() -> Int
func category(at index: Int) -> PickServerViewModel.Category func category(at index: Int) -> MastodonPickServerViewModel.Category
func selectedIndex() -> Int func selectedIndex() -> Int
} }

View File

@ -9,7 +9,7 @@ import UIKit
import MastodonSDK import MastodonSDK
class PickServerCategoryView: UIView { class PickServerCategoryView: UIView {
var category: PickServerViewModel.Category? { var category: MastodonPickServerViewModel.Category? {
didSet { didSet {
updateCategory() updateCategory()
} }

View File

@ -92,9 +92,9 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.backgroundColor = .white textField.backgroundColor = .white
textField.textColor = Asset.Colors.Label.secondary.color textField.textColor = Asset.Colors.Label.primary.color
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Username.placeholder, textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Username.placeholder,
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
@ -113,9 +113,9 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.backgroundColor = .white textField.backgroundColor = .white
textField.textColor = Asset.Colors.Label.secondary.color textField.textColor = Asset.Colors.Label.primary.color
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.DisplayName.placeholder, textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.DisplayName.placeholder,
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
@ -130,9 +130,9 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.keyboardType = .emailAddress textField.keyboardType = .emailAddress
textField.backgroundColor = .white textField.backgroundColor = .white
textField.textColor = Asset.Colors.Label.secondary.color textField.textColor = Asset.Colors.Label.primary.color
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Email.placeholder, textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Email.placeholder,
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
@ -154,9 +154,9 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
textField.keyboardType = .asciiCapable textField.keyboardType = .asciiCapable
textField.isSecureTextEntry = true textField.isSecureTextEntry = true
textField.backgroundColor = .white textField.backgroundColor = .white
textField.textColor = Asset.Colors.Label.secondary.color textField.textColor = Asset.Colors.Label.primary.color
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Password.placeholder, textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Password.placeholder,
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
@ -170,9 +170,9 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
textField.autocapitalizationType = .none textField.autocapitalizationType = .none
textField.autocorrectionType = .no textField.autocorrectionType = .no
textField.backgroundColor = .white textField.backgroundColor = .white
textField.textColor = Asset.Colors.Label.secondary.color textField.textColor = Asset.Colors.Label.primary.color
textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest, textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Invite.registrationUserInviteRequest,
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
textField.borderStyle = UITextField.BorderStyle.roundedRect textField.borderStyle = UITextField.BorderStyle.roundedRect
let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height)) let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: 5, height: textField.frame.height))
@ -181,18 +181,18 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
return textField return textField
}() }()
let signUpButton: UIButton = { let buttonContainer = UIView()
let signUpButton: PrimaryActionButton = {
let button = PrimaryActionButton() let button = PrimaryActionButton()
button.isEnabled = false button.isEnabled = false
button.setTitle(L10n.Common.Controls.Actions.continue, for: .normal) button.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
return button return button
}() }()
let signUpActivityIndicatorView: UIActivityIndicatorView = { deinit {
let activityIndicatorView = UIActivityIndicatorView(style: .medium) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
activityIndicatorView.hidesWhenStopped = true }
return activityIndicatorView
}()
} }
extension MastodonRegisterViewController { extension MastodonRegisterViewController {
@ -294,19 +294,17 @@ extension MastodonRegisterViewController {
stackView.setCustomSpacing(32, after: passwordCheckLabel) stackView.setCustomSpacing(32, after: passwordCheckLabel)
// button // button
stackView.addArrangedSubview(buttonContainer)
signUpButton.translatesAutoresizingMaskIntoConstraints = false signUpButton.translatesAutoresizingMaskIntoConstraints = false
stackView.addArrangedSubview(signUpButton) buttonContainer.addSubview(signUpButton)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
signUpButton.topAnchor.constraint(equalTo: buttonContainer.topAnchor),
signUpButton.leadingAnchor.constraint(equalTo: buttonContainer.leadingAnchor, constant: MastodonRegisterViewController.actionButtonMargin),
buttonContainer.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor, constant: MastodonRegisterViewController.actionButtonMargin),
buttonContainer.bottomAnchor.constraint(equalTo: signUpButton.bottomAnchor),
signUpButton.heightAnchor.constraint(equalToConstant: MastodonRegisterViewController.actionButtonHeight).priority(.defaultHigh), signUpButton.heightAnchor.constraint(equalToConstant: MastodonRegisterViewController.actionButtonHeight).priority(.defaultHigh),
]) ])
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(signUpActivityIndicatorView)
NSLayoutConstraint.activate([
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
])
Publishers.CombineLatest( Publishers.CombineLatest(
KeyboardResponderService.shared.state.eraseToAnyPublisher(), KeyboardResponderService.shared.state.eraseToAnyPublisher(),
KeyboardResponderService.shared.willEndFrame.eraseToAnyPublisher() KeyboardResponderService.shared.willEndFrame.eraseToAnyPublisher()
@ -332,7 +330,7 @@ extension MastodonRegisterViewController {
self.scrollView.verticalScrollIndicatorInsets.bottom = padding + 16 self.scrollView.verticalScrollIndicatorInsets.bottom = padding + 16
if self.passwordTextField.isFirstResponder { if self.passwordTextField.isFirstResponder {
let contentFrame = self.scrollView.convert(self.signUpButton.frame, to: nil) let contentFrame = self.buttonContainer.convert(self.signUpButton.frame, to: nil)
let labelPadding = contentFrame.maxY - endFrame.minY let labelPadding = contentFrame.maxY - endFrame.minY
let contentOffsetY = self.scrollView.contentOffset.y let contentOffsetY = self.scrollView.contentOffset.y
DispatchQueue.main.async { DispatchQueue.main.async {
@ -346,9 +344,7 @@ extension MastodonRegisterViewController {
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] isRegistering in .sink { [weak self] isRegistering in
guard let self = self else { return } guard let self = self else { return }
isRegistering ? self.signUpActivityIndicatorView.startAnimating() : self.signUpActivityIndicatorView.stopAnimating() isRegistering ? self.signUpButton.showLoading() : self.signUpButton.stopLoading()
self.signUpButton.setTitle(isRegistering ? "" : L10n.Common.Controls.Actions.continue, for: .normal)
self.signUpButton.isEnabled = !isRegistering
} }
.store(in: &disposeBag) .store(in: &disposeBag)
@ -463,7 +459,7 @@ extension MastodonRegisterViewController {
.receive(on: DispatchQueue.main) .receive(on: DispatchQueue.main)
.sink { [weak self] _ in .sink { [weak self] _ in
guard let self = self else { return } guard let self = self else { return }
self.viewModel.invite.value = self.inviteTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? "" self.viewModel.reason.value = self.inviteTextField.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
} }
.store(in: &disposeBag) .store(in: &disposeBag)
} }
@ -471,11 +467,6 @@ extension MastodonRegisterViewController {
signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside) signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside)
} }
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(false, animated: false)
}
} }
extension MastodonRegisterViewController: UITextFieldDelegate { extension MastodonRegisterViewController: UITextFieldDelegate {
@ -493,7 +484,7 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
case passwordTextField: case passwordTextField:
viewModel.password.value = text viewModel.password.value = text
case inviteTextField: case inviteTextField:
viewModel.invite.value = text viewModel.reason.value = text
default: default:
break break
} }
@ -536,13 +527,24 @@ extension MastodonRegisterViewController {
let username = viewModel.username.value let username = viewModel.username.value
let email = viewModel.email.value let email = viewModel.email.value
let password = viewModel.password.value let password = viewModel.password.value
let query = Mastodon.API.Account.RegisterQuery(
reason: viewModel.reason.value,
username: username,
email: email,
password: password,
agreement: true, // TODO:
locale: "en" // TODO:
)
if let rules = viewModel.instance.rules, !rules.isEmpty { if let rules = viewModel.instance.rules, !rules.isEmpty {
// show server rules before register // show server rules before register
let mastodonServerRulesViewModel = MastodonServerRulesViewModel( let mastodonServerRulesViewModel = MastodonServerRulesViewModel(
context: context, context: context,
domain: viewModel.domain, domain: viewModel.domain,
rules: rules authenticateInfo: viewModel.authenticateInfo,
rules: rules,
registerQuery: query,
applicationAuthorization: viewModel.applicationAuthorization
) )
viewModel.isRegistering.value = false viewModel.isRegistering.value = false
@ -551,51 +553,28 @@ extension MastodonRegisterViewController {
return return
} else { } else {
// register without show server rules // register without show server rules
context.apiService.accountRegister(
domain: viewModel.domain,
query: query,
authorization: viewModel.applicationAuthorization
)
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
self.viewModel.isRegistering.value = false
switch completion {
case .failure(let error):
self.viewModel.error.send(error)
case .finished:
break
}
} receiveValue: { [weak self] response in
guard let self = self else { return }
let userToken = response.value
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
}
.store(in: &disposeBag)
} }
let query = Mastodon.API.Account.RegisterQuery(
reason: viewModel.invite.value,
username: username,
email: email,
password: password,
agreement: true, // TODO:
locale: "en" // TODO:
)
} }
} }
extension MastodonRegisterViewController {
// func register(query: Mastodon.API.Account.RegisterQuery) -> AnyPublisher<Mastodon.Response.Content<Mastodon.Entity.Token>, Error> {
// context.apiService.accountRegister(
// domain: viewModel.domain,
// query: query,
// authorization: viewModel.applicationAuthorization
// )
// .receive(on: DispatchQueue.main)
// .sink { [weak self] completion in
// guard let self = self else { return }
// self.viewModel.isRegistering.value = false
// switch completion {
// case .failure(let error):
// self.viewModel.error.send(error)
// case .finished:
// break
// }
// } receiveValue: { [weak self] response in
// guard let self = self else { return }
// let userToken = response.value
//
// let alertController = UIAlertController(title: L10n.Scene.Register.success, message: L10n.Scene.Register.checkEmail, preferredStyle: .alert)
// let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) { [weak self] _ in
// guard let self = self else { return }
// let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
// self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
// }
// alertController.addAction(okAction)
// self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
// }
// .store(in: &disposeBag)
// }
}

View File

@ -23,33 +23,19 @@ final class MastodonRegisterViewModel {
let displayName = CurrentValueSubject<String, Never>("") let displayName = CurrentValueSubject<String, Never>("")
let email = CurrentValueSubject<String, Never>("") let email = CurrentValueSubject<String, Never>("")
let password = CurrentValueSubject<String, Never>("") let password = CurrentValueSubject<String, Never>("")
let invite = CurrentValueSubject<String, Never>("") let reason = CurrentValueSubject<String, Never>("")
let isUsernameValidateDalay = CurrentValueSubject<Bool, Never>(true)
let isDisplayNameValidateDalay = CurrentValueSubject<Bool, Never>(true)
let isEmailValidateDalay = CurrentValueSubject<Bool, Never>(true)
let isPasswordValidateDalay = CurrentValueSubject<Bool, Never>(true)
let isInviteValidateDelay = CurrentValueSubject<Bool, Never>(true)
let isRegistering = CurrentValueSubject<Bool, Never>(false)
// output // output
lazy var approvalRequired: Bool = { let approvalRequired: Bool
if let approvalRequired = instance.approvalRequired {
return approvalRequired
}
return false
}()
let applicationAuthorization: Mastodon.API.OAuth.Authorization let applicationAuthorization: Mastodon.API.OAuth.Authorization
let usernameValidateState = CurrentValueSubject<ValidateState, Never>(.empty) let usernameValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
let displayNameValidateState = CurrentValueSubject<ValidateState, Never>(.empty) let displayNameValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
let emailValidateState = CurrentValueSubject<ValidateState, Never>(.empty) let emailValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
let passwordValidateState = CurrentValueSubject<ValidateState, Never>(.empty) let passwordValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
let inviteValidateState = CurrentValueSubject<ValidateState, Never>(.empty) let inviteValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
let isRegistering = CurrentValueSubject<Bool, Never>(false)
let isAllValid = CurrentValueSubject<Bool, Never>(false) let isAllValid = CurrentValueSubject<Bool, Never>(false)
let error = CurrentValueSubject<Error?, Never>(nil) let error = CurrentValueSubject<Error?, Never>(nil)
init( init(
@ -62,6 +48,7 @@ final class MastodonRegisterViewModel {
self.authenticateInfo = authenticateInfo self.authenticateInfo = authenticateInfo
self.instance = instance self.instance = instance
self.applicationToken = applicationToken self.applicationToken = applicationToken
self.approvalRequired = instance.approvalRequired ?? false
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken) self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
username username
@ -107,7 +94,7 @@ final class MastodonRegisterViewModel {
.assign(to: \.value, on: passwordValidateState) .assign(to: \.value, on: passwordValidateState)
.store(in: &disposeBag) .store(in: &disposeBag)
if approvalRequired { if approvalRequired {
invite reason
.map { invite in .map { invite in
guard !invite.isEmpty else { return .empty } guard !invite.isEmpty else { return .empty }
return .valid return .valid

View File

@ -10,7 +10,8 @@ import os.log
import UIKit import UIKit
import WebKit import WebKit
final class MastodonResendEmailViewController: UIViewController, NeedsDependency, WKNavigationDelegate { final class MastodonResendEmailViewController: UIViewController, NeedsDependency {
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@ -35,6 +36,7 @@ final class MastodonResendEmailViewController: UIViewController, NeedsDependency
} }
} }
} }
} }
extension MastodonResendEmailViewController { extension MastodonResendEmailViewController {

View File

@ -11,6 +11,7 @@ import os.log
import WebKit import WebKit
final class MastodonResendEmailViewModel { final class MastodonResendEmailViewModel {
// input // input
let resendEmailURL: URL let resendEmailURL: URL
let email: String let email: String
@ -25,6 +26,7 @@ final class MastodonResendEmailViewModel {
deinit { deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
} }
} }
extension MastodonResendEmailViewModel { extension MastodonResendEmailViewModel {

View File

@ -7,9 +7,12 @@
import os.log import os.log
import UIKit import UIKit
import Combine
final class MastodonServerRulesViewController: UIViewController, NeedsDependency { final class MastodonServerRulesViewController: UIViewController, NeedsDependency {
var disposeBag = Set<AnyCancellable>()
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
@ -56,7 +59,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
return label return label
}() }()
let confirmButton: UIButton = { let confirmButton: PrimaryActionButton = {
let button = PrimaryActionButton() let button = PrimaryActionButton()
button.titleLabel?.font = .preferredFont(forTextStyle: .headline) button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
button.setTitleColor(.white, for: .normal) button.setTitleColor(.white, for: .normal)
@ -69,6 +72,10 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
scrollView.alwaysBounceVertical = true scrollView.alwaysBounceVertical = true
return scrollView return scrollView
}() }()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
} }
@ -109,7 +116,7 @@ extension MastodonServerRulesViewController {
scrollView.translatesAutoresizingMaskIntoConstraints = false scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView) view.addSubview(scrollView)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.topAnchor), scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor),
scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor),
view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor), view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor),
scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor), scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor),
@ -136,6 +143,14 @@ extension MastodonServerRulesViewController {
rulesLabel.attributedText = viewModel.rulesAttributedString rulesLabel.attributedText = viewModel.rulesAttributedString
confirmButton.addTarget(self, action: #selector(MastodonServerRulesViewController.confirmButtonPressed(_:)), for: .touchUpInside) confirmButton.addTarget(self, action: #selector(MastodonServerRulesViewController.confirmButtonPressed(_:)), for: .touchUpInside)
viewModel.isRegistering
.receive(on: DispatchQueue.main)
.sink { [weak self] isRegistering in
guard let self = self else { return }
isRegistering ? self.confirmButton.showLoading() : self.confirmButton.stopLoading()
}
.store(in: &disposeBag)
} }
} }
@ -143,7 +158,31 @@ extension MastodonServerRulesViewController {
extension MastodonServerRulesViewController { extension MastodonServerRulesViewController {
@objc private func confirmButtonPressed(_ sender: UIButton) { @objc private func confirmButtonPressed(_ sender: UIButton) {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function) os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
let email = viewModel.registerQuery.email
context.apiService.accountRegister(
domain: viewModel.domain,
query: viewModel.registerQuery,
authorization: viewModel.applicationAuthorization
)
.receive(on: DispatchQueue.main)
.sink { [weak self] completion in
guard let self = self else { return }
self.viewModel.isRegistering.value = false
switch completion {
case .failure(let error):
self.viewModel.error.send(error)
case .finished:
break
}
} receiveValue: { [weak self] response in
guard let self = self else { return }
let userToken = response.value
let viewModel = MastodonConfirmEmailViewModel(context: self.context, email: email, authenticateInfo: self.viewModel.authenticateInfo, userToken: userToken)
self.coordinator.present(scene: .mastodonConfirmEmail(viewModel: viewModel), from: self, transition: .show)
}
.store(in: &disposeBag)
} }
} }

View File

@ -6,6 +6,7 @@
// //
import UIKit import UIKit
import Combine
import MastodonSDK import MastodonSDK
final class MastodonServerRulesViewModel { final class MastodonServerRulesViewModel {
@ -13,12 +14,30 @@ final class MastodonServerRulesViewModel {
// input // input
let context: AppContext let context: AppContext
let domain: String let domain: String
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
let rules: [Mastodon.Entity.Instance.Rule] let rules: [Mastodon.Entity.Instance.Rule]
let registerQuery: Mastodon.API.Account.RegisterQuery
let applicationAuthorization: Mastodon.API.OAuth.Authorization
// output
let isRegistering = CurrentValueSubject<Bool, Never>(false)
let error = CurrentValueSubject<Error?, Never>(nil)
init(context: AppContext, domain: String, rules: [Mastodon.Entity.Instance.Rule]) { init(
context: AppContext,
domain: String,
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
rules: [Mastodon.Entity.Instance.Rule],
registerQuery: Mastodon.API.Account.RegisterQuery,
applicationAuthorization: Mastodon.API.OAuth.Authorization
) {
self.context = context self.context = context
self.domain = domain self.domain = domain
self.authenticateInfo = authenticateInfo
self.rules = rules self.rules = rules
self.registerQuery = registerQuery
self.applicationAuthorization = applicationAuthorization
} }
var rulesAttributedString: NSAttributedString { var rulesAttributedString: NSAttributedString {
@ -32,9 +51,6 @@ final class MastodonServerRulesViewModel {
attributedString.append(indexString) attributedString.append(indexString)
attributedString.append(ruleString) attributedString.append(ruleString)
} }
// let paragraphStyle = NSMutableParagraphStyle()
// paragraphStyle.lineSpacing = 20
// attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSRange(location: 0, length: attributedString.length))
return attributedString return attributedString
} }

View File

@ -16,6 +16,7 @@ protocol OnboardingViewControllerAppearance: UIViewController {
extension OnboardingViewControllerAppearance { extension OnboardingViewControllerAppearance {
static var actionButtonHeight: CGFloat { return 46 } static var actionButtonHeight: CGFloat { return 46 }
static var actionButtonMargin: CGFloat { return 12 }
static var viewBottomPaddingHeight: CGFloat { return 11 } static var viewBottomPaddingHeight: CGFloat { return 11 }
func setupOnboardingAppearance() { func setupOnboardingAppearance() {

View File

@ -47,6 +47,11 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
button.translatesAutoresizingMaskIntoConstraints = false button.translatesAutoresizingMaskIntoConstraints = false
return button return button
}() }()
deinit {
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
}
} }
extension WelcomeViewController { extension WelcomeViewController {
@ -74,14 +79,14 @@ extension WelcomeViewController {
view.addSubview(signInButton) view.addSubview(signInButton)
view.addSubview(signUpButton) view.addSubview(signUpButton)
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
signInButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12), signInButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: WelcomeViewController.actionButtonMargin),
view.readableContentGuide.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor, constant: 12), view.readableContentGuide.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor, constant: WelcomeViewController.actionButtonMargin),
view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight), view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
signInButton.heightAnchor.constraint(equalToConstant: 46).priority(.defaultHigh), signInButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh),
signInButton.topAnchor.constraint(equalTo: signUpButton.bottomAnchor, constant: 9), signInButton.topAnchor.constraint(equalTo: signUpButton.bottomAnchor, constant: 9),
signUpButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12), signUpButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: WelcomeViewController.actionButtonMargin),
view.readableContentGuide.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor, constant: 12), view.readableContentGuide.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor, constant: WelcomeViewController.actionButtonMargin),
signUpButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh), signUpButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh),
]) ])
@ -89,19 +94,28 @@ extension WelcomeViewController {
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside) signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
} }
override var preferredStatusBarStyle: UIStatusBarStyle { return .darkContent }
} }
extension WelcomeViewController { extension WelcomeViewController {
@objc @objc
private func signUpButtonDidClicked(_ sender: UIButton) { private func signUpButtonDidClicked(_ sender: UIButton) {
coordinator.present(scene: .pickServer(viewMode: PickServerViewModel(context: context, mode: .signUp)), from: self, transition: .show) coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context, mode: .signUp)), from: self, transition: .show)
} }
@objc @objc
private func signInButtonDidClicked(_ sender: UIButton) { private func signInButtonDidClicked(_ sender: UIButton) {
coordinator.present(scene: .pickServer(viewMode: PickServerViewModel(context: context, mode: .signIn)), from: self, transition: .show) coordinator.present(scene: .mastodonPickServer(viewMode: MastodonPickServerViewModel(context: context, mode: .signIn)), from: self, transition: .show)
} }
} }
// MARK: - OnboardingViewControllerAppearance // MARK: - OnboardingViewControllerAppearance
extension WelcomeViewController: OnboardingViewControllerAppearance { } extension WelcomeViewController: OnboardingViewControllerAppearance { }
// MARK: - UIAdaptivePresentationControllerDelegate
extension WelcomeViewController: UIAdaptivePresentationControllerDelegate {
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
return .fullScreen
}
}

View File

@ -0,0 +1,14 @@
//
// DarkContentStatusBarStyleNavigationController.swift
//
//
// Created by MainasuK Cirno on 2021-2-26.
//
import UIKit
final class DarkContentStatusBarStyleNavigationController: UINavigationController {
override var preferredStatusBarStyle: UIStatusBarStyle {
return .darkContent
}
}

View File

@ -25,22 +25,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
self.coordinator = sceneCoordinator self.coordinator = sceneCoordinator
sceneCoordinator.setup() sceneCoordinator.setup()
sceneCoordinator.setupOnboardingIfNeeds(animated: false)
// do {
// let request = MastodonAuthentication.sortedFetchRequest
// if try appContext.managedObjectContext.fetch(request).isEmpty {
// DispatchQueue.main.async {
// sceneCoordinator.present(
// scene: .welcome,
// from: nil,
// transition: .modal(animated: false, completion: nil)
// )
// }
// }
// } catch {
// assertionFailure(error.localizedDescription)
// }
window.makeKeyAndVisible() window.makeKeyAndVisible()
} }

View File

@ -39,7 +39,7 @@ extension Mastodon.API.Error: LocalizedError {
public var errorDescription: String? { public var errorDescription: String? {
guard let mastodonError = mastodonError else { guard let mastodonError = mastodonError else {
return nil return "HTTP \(httpResponseStatus.code)"
} }
switch mastodonError { switch mastodonError {
case .generic(let error): case .generic(let error):
@ -49,7 +49,7 @@ extension Mastodon.API.Error: LocalizedError {
public var failureReason: String? { public var failureReason: String? {
guard let mastodonError = mastodonError else { guard let mastodonError = mastodonError else {
return nil return httpResponseStatus.reasonPhrase
} }
switch mastodonError { switch mastodonError {
case .generic(let error): case .generic(let error):