forked from zelo72/mastodon-ios
chore: make onboarding ready
This commit is contained in:
parent
24fc57d982
commit
7aa45ff230
|
@ -22,8 +22,8 @@
|
|||
"cancel": "Cancel",
|
||||
"take_photo": "Take photo",
|
||||
"save_photo": "Save photo",
|
||||
"sign_in": "Sign in",
|
||||
"sign_up": "Sign up",
|
||||
"sign_in": "Sign In",
|
||||
"sign_up": "Sign Up",
|
||||
"see_more": "See More",
|
||||
"preview": "Preview",
|
||||
"open_in_safari": "Open in Safari"
|
||||
|
@ -110,7 +110,7 @@
|
|||
"dont_receive_email": {
|
||||
"title": "Check your email",
|
||||
"description": "Check if your email address is correct as well as your junk folder if you haven’t.",
|
||||
"resend_email": "Resend email"
|
||||
"resend_email": "Resend Email"
|
||||
},
|
||||
"open_email_app": {
|
||||
"title": "Check your inbox.",
|
||||
|
|
|
@ -10,8 +10,8 @@
|
|||
0FAA0FDF25E0B57E0017CCDE /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA0FDE25E0B57E0017CCDE /* WelcomeViewController.swift */; };
|
||||
0FAA101225E105390017CCDE /* PrimaryActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101125E105390017CCDE /* PrimaryActionButton.swift */; };
|
||||
0FAA101C25E10E760017CCDE /* UIFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA101B25E10E760017CCDE /* UIFont.swift */; };
|
||||
0FAA102725E1126A0017CCDE /* PickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* PickServerViewController.swift */; };
|
||||
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */; };
|
||||
0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */; };
|
||||
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */; };
|
||||
0FB3D2FE25E4CB6400AAD544 /* PickServerTitleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D2FD25E4CB6400AAD544 /* PickServerTitleCell.swift */; };
|
||||
0FB3D30825E524C600AAD544 /* PickServerCategoriesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FB3D30725E524C600AAD544 /* PickServerCategoriesCell.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 */; };
|
||||
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 */; };
|
||||
DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */; };
|
||||
DB0140A125C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */; };
|
||||
DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.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 */; };
|
||||
DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; };
|
||||
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 */; };
|
||||
DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB3D100F25BAA75E00EAA174 /* Localizable.strings */; };
|
||||
DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB427DD525BAA00100D1B89D /* AppDelegate.swift */; };
|
||||
|
@ -118,6 +118,7 @@
|
|||
DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; };
|
||||
DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.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 */; };
|
||||
DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */; };
|
||||
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 */; };
|
||||
DB8AF55D25C138B7002E6C99 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB8AF55C25C138B7002E6C99 /* UIViewController.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 */; };
|
||||
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 */; };
|
||||
|
@ -217,8 +217,8 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PickServerViewModel.swift; sourceTree = "<group>"; };
|
||||
0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonPickServerViewController.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>"; };
|
||||
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>"; };
|
||||
|
@ -292,7 +292,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -303,6 +302,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -331,6 +331,7 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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; };
|
||||
|
@ -357,7 +358,6 @@
|
|||
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>"; };
|
||||
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>"; };
|
||||
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>"; };
|
||||
|
@ -445,8 +445,8 @@
|
|||
0FB3D31825E525DE00AAD544 /* CollectionViewCell */,
|
||||
0FB3D30D25E525C000AAD544 /* View */,
|
||||
0FB3D2FC25E4CB4B00AAD544 /* TableViewCell */,
|
||||
0FAA102625E1126A0017CCDE /* PickServerViewController.swift */,
|
||||
0FB3D2F625E4C24D00AAD544 /* PickServerViewModel.swift */,
|
||||
0FAA102625E1126A0017CCDE /* MastodonPickServerViewController.swift */,
|
||||
0FB3D2F625E4C24D00AAD544 /* MastodonPickServerViewModel.swift */,
|
||||
);
|
||||
path = PickServer;
|
||||
sourceTree = "<group>";
|
||||
|
@ -598,7 +598,6 @@
|
|||
2D38F1C525CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift */,
|
||||
2D38F20725CD491300561493 /* DisposeBagCollectable.swift */,
|
||||
2D5A3D3725CF8D9F002347D6 /* ScrollViewContainer.swift */,
|
||||
2D82B9FE25E7863200E36F0F /* OnboardingViewControllerAppearance.swift */,
|
||||
);
|
||||
path = Protocol;
|
||||
sourceTree = "<group>";
|
||||
|
@ -636,6 +635,7 @@
|
|||
2D7631A425C1532200929FB9 /* Share */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB68A04F25E9028800CFDF14 /* NavigationController */,
|
||||
DB9D6C2025E502C60051B173 /* ViewModel */,
|
||||
2D7631A525C1532D00929FB9 /* View */,
|
||||
);
|
||||
|
@ -695,27 +695,26 @@
|
|||
DB01409B25C40BB600F9F3CF /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB68A03825E900CC00CFDF14 /* Share */,
|
||||
0FAA0FDD25E0B5700017CCDE /* Welcome */,
|
||||
0FAA102525E1125D0017CCDE /* PickServer */,
|
||||
DB0140A625C40C0900F9F3CF /* PinBasedAuthentication */,
|
||||
DBE0821A25CD382900FD6BBD /* Register */,
|
||||
DB72602125E36A2500235243 /* ServerRules */,
|
||||
2D364F7025E66D5B00204FDC /* ResendEmail */,
|
||||
2D59819925E4A55C000FB903 /* ConfirmEmail */,
|
||||
DB0140A625C40C0900F9F3CF /* PinBased */,
|
||||
DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */,
|
||||
DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */,
|
||||
);
|
||||
path = Onboarding;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB0140A625C40C0900F9F3CF /* PinBased */ = {
|
||||
DB0140A625C40C0900F9F3CF /* PinBasedAuthentication */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
DB0140A025C40C0600F9F3CF /* MastodonPinBasedAuthenticationViewController.swift */,
|
||||
DB0140AD25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift */,
|
||||
DB0140A725C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift */,
|
||||
);
|
||||
path = PinBased;
|
||||
path = PinBasedAuthentication;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB084B5125CBC56300F898ED /* CoreDataStack */ = {
|
||||
|
@ -856,6 +855,23 @@
|
|||
path = Preference;
|
||||
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 */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1412,7 +1428,7 @@
|
|||
2D59819B25E4A581000FB903 /* MastodonConfirmEmailViewController.swift in Sources */,
|
||||
DB45FB1D25CA9D23005A8AC7 /* APIService+HomeTimeline.swift in Sources */,
|
||||
2D7631B325C159F700929FB9 /* Item.swift in Sources */,
|
||||
0FB3D2F725E4C24D00AAD544 /* PickServerViewModel.swift in Sources */,
|
||||
0FB3D2F725E4C24D00AAD544 /* MastodonPickServerViewModel.swift in Sources */,
|
||||
2D61335E25C1894B00CAE157 /* APIService.swift in Sources */,
|
||||
2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */,
|
||||
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
||||
|
@ -1427,7 +1443,7 @@
|
|||
2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */,
|
||||
DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */,
|
||||
2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */,
|
||||
0FAA102725E1126A0017CCDE /* PickServerViewController.swift in Sources */,
|
||||
0FAA102725E1126A0017CCDE /* MastodonPickServerViewController.swift in Sources */,
|
||||
DB68586425E619B700F0A850 /* NSKeyValueObservation.swift in Sources */,
|
||||
2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */,
|
||||
DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */,
|
||||
|
@ -1467,13 +1483,14 @@
|
|||
DB9D6BE925E4F5340051B173 /* SearchViewController.swift in Sources */,
|
||||
2D38F1C625CD37F400561493 /* ContentOffsetAdjustableTimelineViewControllerDelegate.swift in Sources */,
|
||||
0FB3D30F25E525CD00AAD544 /* PickServerCategoryView.swift in Sources */,
|
||||
DB2F073525E8ECF000957B2D /* AuthenticationViewModel.swift in Sources */,
|
||||
DB68A04A25E9027700CFDF14 /* DarkContentStatusBarStyleNavigationController.swift in Sources */,
|
||||
0FB3D33825E6401400AAD544 /* PickServerCell.swift in Sources */,
|
||||
2D364F7825E66D8300204FDC /* MastodonResendEmailViewModel.swift in Sources */,
|
||||
DB8AF54525C13647002E6C99 /* NeedsDependency.swift in Sources */,
|
||||
DB9D6BF825E4F5690051B173 /* NotificationViewController.swift in Sources */,
|
||||
DB45FADD25CA6F6B005A8AC7 /* APIService+CoreData+MastodonUser.swift in Sources */,
|
||||
2D32EABA25CB9B0500C9ED86 /* UIView.swift in Sources */,
|
||||
DB98334725C8056600AD9700 /* AuthenticationViewModel.swift in Sources */,
|
||||
2D38F20825CD491300561493 /* DisposeBagCollectable.swift in Sources */,
|
||||
DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */,
|
||||
2D76319F25C1521200929FB9 /* StatusSection.swift in Sources */,
|
||||
|
@ -1504,7 +1521,6 @@
|
|||
2D7631A825C1535600929FB9 /* StatusTableViewCell.swift in Sources */,
|
||||
2D76316525C14BD100929FB9 /* PublicTimelineViewController.swift in Sources */,
|
||||
2D69CFF425CA9E2200C3A1B2 /* LoadMoreConfigurableTableViewContainer.swift in Sources */,
|
||||
DB01409625C40B6700F9F3CF /* AuthenticationViewController.swift in Sources */,
|
||||
DB427DD825BAA00100D1B89D /* SceneDelegate.swift in Sources */,
|
||||
DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */,
|
||||
DB0140AE25C40C7300F9F3CF /* MastodonPinBasedAuthenticationViewModel.swift in Sources */,
|
||||
|
|
|
@ -7,12 +7,17 @@
|
|||
<key>CoreDataStack.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>9</integer>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
<key>Mastodon - RTL.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>8</integer>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>Mastodon - Release.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
<key>Mastodon.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
|
|
|
@ -38,44 +38,61 @@ extension SceneCoordinator {
|
|||
}
|
||||
|
||||
enum Scene {
|
||||
// onboarding
|
||||
case welcome
|
||||
case pickServer(viewMode: PickServerViewModel)
|
||||
case authentication(viewModel: AuthenticationViewModel)
|
||||
case mastodonPickServer(viewMode: MastodonPickServerViewModel)
|
||||
case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel)
|
||||
case mastodonRegister(viewModel: MastodonRegisterViewModel)
|
||||
case mastodonServerRules(viewModel: MastodonServerRulesViewModel)
|
||||
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
|
||||
case mastodonResendEmail(viewModel: MastodonResendEmailViewModel)
|
||||
|
||||
// misc
|
||||
case alertController(alertController: UIAlertController)
|
||||
|
||||
#if DEBUG
|
||||
case publicTimeline
|
||||
#endif
|
||||
|
||||
var isOnboarding: Bool {
|
||||
switch self {
|
||||
case .welcome,
|
||||
.mastodonPickServer,
|
||||
.mastodonPinBasedAuthentication,
|
||||
.mastodonRegister,
|
||||
.mastodonServerRules,
|
||||
.mastodonConfirmEmail,
|
||||
.mastodonResendEmail:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SceneCoordinator {
|
||||
|
||||
func setup() {
|
||||
// Check user authentication status
|
||||
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
let viewController = MainTabBarController(context: appContext, coordinator: self)
|
||||
sceneDelegate.window?.rootViewController = viewController
|
||||
}
|
||||
|
||||
func setupOnboardingIfNeeds(animated: Bool) {
|
||||
// Check user authentication status and show onboarding if needs
|
||||
do {
|
||||
let fetchResult = try appContext.managedObjectContext.fetch(request)
|
||||
DispatchQueue.main.async {
|
||||
var rootViewController: UIViewController
|
||||
if fetchResult.isEmpty {
|
||||
let welcomViewController = WelcomeViewController()
|
||||
self.setupDependency(for: welcomViewController)
|
||||
rootViewController = UINavigationController(rootViewController: welcomViewController)
|
||||
} else {
|
||||
rootViewController = MainTabBarController(context: self.appContext, coordinator: self)
|
||||
let request = MastodonAuthentication.sortedFetchRequest
|
||||
if try appContext.managedObjectContext.fetch(request).isEmpty {
|
||||
DispatchQueue.main.async {
|
||||
self.present(
|
||||
scene: .welcome,
|
||||
from: nil,
|
||||
transition: .modal(animated: animated, completion: nil)
|
||||
)
|
||||
}
|
||||
self.sceneDelegate.window?.rootViewController = rootViewController
|
||||
}
|
||||
} catch {
|
||||
assertionFailure("CoreDataStack error at app launch!")
|
||||
assertionFailure(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +120,13 @@ extension SceneCoordinator {
|
|||
presentingViewController.showDetailViewController(navigationController, sender: sender)
|
||||
|
||||
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 {
|
||||
modalNavigationController.presentationController?.delegate = adaptivePresentationControllerDelegate
|
||||
}
|
||||
|
@ -143,12 +166,8 @@ private extension SceneCoordinator {
|
|||
case .welcome:
|
||||
let _viewController = WelcomeViewController()
|
||||
viewController = _viewController
|
||||
case .pickServer(let viewModel):
|
||||
let _viewController = PickServerViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .authentication(let viewModel):
|
||||
let _viewController = AuthenticationViewController()
|
||||
case .mastodonPickServer(let viewModel):
|
||||
let _viewController = MastodonPickServerViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .mastodonPinBasedAuthentication(let viewModel):
|
||||
|
|
|
@ -50,9 +50,9 @@ internal enum L10n {
|
|||
internal static let savePhoto = L10n.tr("Localizable", "Common.Controls.Actions.SavePhoto")
|
||||
/// See More
|
||||
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")
|
||||
/// Sign up
|
||||
/// Sign Up
|
||||
internal static let signUp = L10n.tr("Localizable", "Common.Controls.Actions.SignUp")
|
||||
/// Take photo
|
||||
internal static let takePhoto = L10n.tr("Localizable", "Common.Controls.Actions.TakePhoto")
|
||||
|
@ -101,7 +101,7 @@ internal enum L10n {
|
|||
internal enum DontReceiveEmail {
|
||||
/// Check if your email address is correct as well as your junk folder if you haven’t.
|
||||
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")
|
||||
/// Check your email
|
||||
internal static let title = L10n.tr("Localizable", "Scene.ConfirmEmail.DontReceiveEmail.Title")
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
"Common.Controls.Actions.Save" = "Save";
|
||||
"Common.Controls.Actions.SavePhoto" = "Save photo";
|
||||
"Common.Controls.Actions.SeeMore" = "See More";
|
||||
"Common.Controls.Actions.SignIn" = "Sign in";
|
||||
"Common.Controls.Actions.SignUp" = "Sign up";
|
||||
"Common.Controls.Actions.SignIn" = "Sign In";
|
||||
"Common.Controls.Actions.SignUp" = "Sign Up";
|
||||
"Common.Controls.Actions.TakePhoto" = "Take photo";
|
||||
"Common.Controls.Status.MediaContentWarning" = "Tap to reveal that may be sensitive";
|
||||
"Common.Controls.Status.ShowPost" = "Show Post";
|
||||
|
@ -26,7 +26,7 @@
|
|||
"Scene.ConfirmEmail.Button.DontReceiveEmail" = "I never got an email";
|
||||
"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 haven’t.";
|
||||
"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Resend email";
|
||||
"Scene.ConfirmEmail.DontReceiveEmail.ResendEmail" = "Resend Email";
|
||||
"Scene.ConfirmEmail.DontReceiveEmail.Title" = "Check your email";
|
||||
"Scene.ConfirmEmail.OpenEmailApp.Description" = "We just sent you an email. Check your junk folder if you haven’t.";
|
||||
"Scene.ConfirmEmail.OpenEmailApp.Mail" = "Mail";
|
||||
|
@ -62,4 +62,4 @@ any server.";
|
|||
"Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@.";
|
||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||
"Scene.Welcome.Slogan" = "Social networking
|
||||
back in your hands.";
|
||||
back in your hands.";
|
||||
|
|
|
@ -37,35 +37,5 @@ extension HomeTimelineViewController {
|
|||
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
|
||||
|
|
|
@ -70,8 +70,20 @@ extension HomeTimelineViewController {
|
|||
return imageView
|
||||
}()
|
||||
navigationItem.leftBarButtonItem = settingBarButtonItem
|
||||
settingBarButtonItem.target = self
|
||||
settingBarButtonItem.action = #selector(HomeTimelineViewController.settingBarButtonItemPressed(_:))
|
||||
#if DEBUG
|
||||
// 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
|
||||
composeBarButtonItem.target = self
|
||||
composeBarButtonItem.action = #selector(HomeTimelineViewController.composeBarButtonItemPressed(_:))
|
||||
|
@ -111,11 +123,6 @@ extension HomeTimelineViewController {
|
|||
}
|
||||
}
|
||||
.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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -11,7 +11,8 @@ import os.log
|
|||
import ThirdPartyMailer
|
||||
import UIKit
|
||||
|
||||
final class MastodonConfirmEmailViewController: UIViewController, NeedsDependency, OnboardingViewControllerAppearance {
|
||||
final class MastodonConfirmEmailViewController: UIViewController, NeedsDependency {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
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)
|
||||
return button
|
||||
}()
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonConfirmEmailViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
self.setupOnboardingAppearance()
|
||||
setupOnboardingAppearance()
|
||||
|
||||
// resizedView
|
||||
let resizedView = UIView()
|
||||
|
@ -107,19 +113,15 @@ extension MastodonConfirmEmailViewController {
|
|||
case .finished:
|
||||
break
|
||||
}
|
||||
} receiveValue: { _ in
|
||||
self.coordinator.setup()
|
||||
} receiveValue: { response in
|
||||
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)
|
||||
}
|
||||
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
super.viewWillAppear(animated)
|
||||
navigationController?.setNavigationBarHidden(false, animated: false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonConfirmEmailViewController {
|
||||
|
@ -174,3 +176,6 @@ extension MastodonConfirmEmailViewController {
|
|||
self.coordinator.present(scene: .alertController(alertController: alertController), from: self, transition: .alertController(animated: true, completion: nil))
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - OnboardingViewControllerAppearance
|
||||
extension MastodonConfirmEmailViewController: OnboardingViewControllerAppearance { }
|
||||
|
|
|
@ -9,7 +9,7 @@ import UIKit
|
|||
|
||||
class PickServerCategoryCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
var category: PickServerViewModel.Category? {
|
||||
var category: MastodonPickServerViewModel.Category? {
|
||||
didSet {
|
||||
categoryView.category = category
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// PickServerViewController.swift
|
||||
// MastodonPickServerViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/20.
|
||||
|
@ -10,14 +10,14 @@ import Combine
|
|||
import OSLog
|
||||
import MastodonSDK
|
||||
|
||||
final class PickServerViewController: UIViewController, NeedsDependency {
|
||||
final class MastodonPickServerViewController: UIViewController, NeedsDependency {
|
||||
|
||||
private var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var viewModel: PickServerViewModel!
|
||||
var viewModel: MastodonPickServerViewModel!
|
||||
|
||||
private var isAuthenticating = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
|
@ -58,7 +58,7 @@ final class PickServerViewController: UIViewController, NeedsDependency {
|
|||
|
||||
}
|
||||
|
||||
extension PickServerViewController {
|
||||
extension MastodonPickServerViewController {
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle {
|
||||
return .darkContent
|
||||
|
@ -72,9 +72,9 @@ extension PickServerViewController {
|
|||
|
||||
view.addSubview(nextStepButton)
|
||||
NSLayoutConstraint.activate([
|
||||
nextStepButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor, constant: 12),
|
||||
nextStepButton.heightAnchor.constraint(equalToConstant: PickServerViewController.actionButtonHeight).priority(.defaultHigh),
|
||||
nextStepButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: MastodonPickServerViewController.actionButtonMargin),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: nextStepButton.trailingAnchor, constant: MastodonPickServerViewController.actionButtonMargin),
|
||||
nextStepButton.heightAnchor.constraint(equalToConstant: MastodonPickServerViewController.actionButtonHeight).priority(.defaultHigh),
|
||||
view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: nextStepButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
|
||||
])
|
||||
|
||||
|
@ -139,11 +139,11 @@ extension PickServerViewController {
|
|||
|
||||
viewModel
|
||||
.authenticated
|
||||
.receive(on: DispatchQueue.main)
|
||||
.flatMap { [weak self] (domain, user) -> AnyPublisher<Result<Bool, Error>, Never> in
|
||||
guard let self = self else { return Just(.success(false)).eraseToAnyPublisher() }
|
||||
return 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 {
|
||||
|
@ -151,7 +151,7 @@ extension PickServerViewController {
|
|||
assertionFailure(error.localizedDescription)
|
||||
case .success(let isActived):
|
||||
assert(isActived)
|
||||
self.coordinator.setup()
|
||||
self.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
@ -181,9 +181,9 @@ extension PickServerViewController {
|
|||
guard let server = viewModel.selectedServer.value else { return }
|
||||
isAuthenticating.send(true)
|
||||
context.apiService.createApplication(domain: server.domain)
|
||||
.tryMap { response -> PickServerViewModel.AuthenticateInfo in
|
||||
.tryMap { response -> MastodonPickServerViewModel.AuthenticateInfo in
|
||||
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)
|
||||
}
|
||||
return info
|
||||
|
@ -222,24 +222,24 @@ extension PickServerViewController {
|
|||
isAuthenticating.send(true)
|
||||
|
||||
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 response.value.registrations != false else {
|
||||
return Fail(error: AuthenticationViewModel.AuthenticationError.registrationClosed).eraseToAnyPublisher()
|
||||
}
|
||||
return self.context.apiService.createApplication(domain: server.domain)
|
||||
.map { PickServerViewModel.SignUpResponseFirst(instance: response, application: $0) }
|
||||
.map { MastodonPickServerViewModel.SignUpResponseFirst(instance: response, application: $0) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.switchToLatest()
|
||||
.tryMap { response -> PickServerViewModel.SignUpResponseSecond in
|
||||
.tryMap { response -> MastodonPickServerViewModel.SignUpResponseSecond in
|
||||
let application = response.application.value
|
||||
guard let authenticateInfo = AuthenticationViewModel.AuthenticateInfo(domain: server.domain, application: application) else {
|
||||
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 }
|
||||
let instance = response.instance
|
||||
let authenticateInfo = response.authenticateInfo
|
||||
|
@ -248,7 +248,7 @@ extension PickServerViewController {
|
|||
clientID: authenticateInfo.clientID,
|
||||
clientSecret: authenticateInfo.clientSecret
|
||||
)
|
||||
.map { PickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
|
||||
.map { MastodonPickServerViewModel.SignUpResponseThird(instance: instance, authenticateInfo: authenticateInfo, applicationToken: $0) }
|
||||
.eraseToAnyPublisher()
|
||||
}
|
||||
.switchToLatest()
|
||||
|
@ -277,7 +277,7 @@ extension PickServerViewController {
|
|||
}
|
||||
}
|
||||
|
||||
extension PickServerViewController: UITableViewDelegate {
|
||||
extension MastodonPickServerViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||||
let category = Section.allCases[section]
|
||||
switch category {
|
||||
|
@ -316,7 +316,7 @@ extension PickServerViewController: UITableViewDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension PickServerViewController: UITableViewDataSource {
|
||||
extension MastodonPickServerViewController: UITableViewDataSource {
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> 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)) {
|
||||
if newMode == .collapse {
|
||||
expandServerDomainSet.remove(server.domain)
|
||||
|
@ -392,18 +392,18 @@ extension PickServerViewController: PickServerCellDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
extension PickServerViewController: PickServerSearchCellDelegate {
|
||||
extension MastodonPickServerViewController: PickServerSearchCellDelegate {
|
||||
func pickServerSearchCell(didChange searchText: String?) {
|
||||
viewModel.searchText.send(searchText)
|
||||
}
|
||||
}
|
||||
|
||||
extension PickServerViewController: PickServerCategoriesDataSource, PickServerCategoriesDelegate {
|
||||
extension MastodonPickServerViewController: PickServerCategoriesDataSource, PickServerCategoriesDelegate {
|
||||
func numberOfCategories() -> Int {
|
||||
return viewModel.categories.count
|
||||
}
|
||||
|
||||
func category(at index: Int) -> PickServerViewModel.Category {
|
||||
func category(at index: Int) -> MastodonPickServerViewModel.Category {
|
||||
return viewModel.categories[index]
|
||||
}
|
||||
|
||||
|
@ -417,4 +417,4 @@ extension PickServerViewController: PickServerCategoriesDataSource, PickServerCa
|
|||
}
|
||||
|
||||
// MARK: - OnboardingViewControllerAppearance
|
||||
extension PickServerViewController: OnboardingViewControllerAppearance { }
|
||||
extension MastodonPickServerViewController: OnboardingViewControllerAppearance { }
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// PickServerViewModel.swift
|
||||
// MastodonPickServerViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by BradGao on 2021/2/23.
|
||||
|
@ -11,7 +11,7 @@ import Combine
|
|||
import MastodonSDK
|
||||
import CoreDataStack
|
||||
|
||||
class PickServerViewModel: NSObject {
|
||||
class MastodonPickServerViewModel: NSObject {
|
||||
enum PickServerMode {
|
||||
case signUp
|
||||
case signIn
|
||||
|
@ -173,7 +173,7 @@ class PickServerViewModel: NSObject {
|
|||
}
|
||||
|
||||
// MARK: - SignIn methods & structs
|
||||
extension PickServerViewModel {
|
||||
extension MastodonPickServerViewModel {
|
||||
enum AuthenticationError: Error, LocalizedError {
|
||||
case badCredentials
|
||||
case registrationClosed
|
||||
|
@ -322,7 +322,7 @@ extension PickServerViewModel {
|
|||
}
|
||||
|
||||
// MARK: - SignUp methods & structs
|
||||
extension PickServerViewModel {
|
||||
extension MastodonPickServerViewModel {
|
||||
struct SignUpResponseFirst {
|
||||
let instance: Mastodon.Response.Content<Mastodon.Entity.Instance>
|
||||
let application: Mastodon.Response.Content<Mastodon.Entity.Application>
|
|
@ -10,7 +10,7 @@ import MastodonSDK
|
|||
|
||||
protocol PickServerCategoriesDataSource: class {
|
||||
func numberOfCategories() -> Int
|
||||
func category(at index: Int) -> PickServerViewModel.Category
|
||||
func category(at index: Int) -> MastodonPickServerViewModel.Category
|
||||
func selectedIndex() -> Int
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ import UIKit
|
|||
import MastodonSDK
|
||||
|
||||
class PickServerCategoryView: UIView {
|
||||
var category: PickServerViewModel.Category? {
|
||||
var category: MastodonPickServerViewModel.Category? {
|
||||
didSet {
|
||||
updateCategory()
|
||||
}
|
||||
|
|
|
@ -92,9 +92,9 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency, O
|
|||
textField.autocapitalizationType = .none
|
||||
textField.autocorrectionType = .no
|
||||
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,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
textField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
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.autocorrectionType = .no
|
||||
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,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
textField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
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.keyboardType = .emailAddress
|
||||
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,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
textField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
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.isSecureTextEntry = true
|
||||
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,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
textField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
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.autocorrectionType = .no
|
||||
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,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color,
|
||||
attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.Label.secondary.color,
|
||||
NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)])
|
||||
textField.borderStyle = UITextField.BorderStyle.roundedRect
|
||||
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
|
||||
}()
|
||||
|
||||
let signUpButton: UIButton = {
|
||||
let buttonContainer = UIView()
|
||||
let signUpButton: PrimaryActionButton = {
|
||||
let button = PrimaryActionButton()
|
||||
button.isEnabled = false
|
||||
button.setTitle(L10n.Common.Controls.Actions.continue, for: .normal)
|
||||
return button
|
||||
}()
|
||||
|
||||
let signUpActivityIndicatorView: UIActivityIndicatorView = {
|
||||
let activityIndicatorView = UIActivityIndicatorView(style: .medium)
|
||||
activityIndicatorView.hidesWhenStopped = true
|
||||
return activityIndicatorView
|
||||
}()
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonRegisterViewController {
|
||||
|
@ -294,19 +294,17 @@ extension MastodonRegisterViewController {
|
|||
stackView.setCustomSpacing(32, after: passwordCheckLabel)
|
||||
|
||||
// button
|
||||
stackView.addArrangedSubview(buttonContainer)
|
||||
signUpButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
stackView.addArrangedSubview(signUpButton)
|
||||
buttonContainer.addSubview(signUpButton)
|
||||
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),
|
||||
])
|
||||
|
||||
signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.addSubview(signUpActivityIndicatorView)
|
||||
NSLayoutConstraint.activate([
|
||||
signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor),
|
||||
signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor),
|
||||
])
|
||||
|
||||
|
||||
Publishers.CombineLatest(
|
||||
KeyboardResponderService.shared.state.eraseToAnyPublisher(),
|
||||
KeyboardResponderService.shared.willEndFrame.eraseToAnyPublisher()
|
||||
|
@ -332,7 +330,7 @@ extension MastodonRegisterViewController {
|
|||
self.scrollView.verticalScrollIndicatorInsets.bottom = padding + 16
|
||||
|
||||
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 contentOffsetY = self.scrollView.contentOffset.y
|
||||
DispatchQueue.main.async {
|
||||
|
@ -346,9 +344,7 @@ extension MastodonRegisterViewController {
|
|||
.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 ? "" : L10n.Common.Controls.Actions.continue, for: .normal)
|
||||
self.signUpButton.isEnabled = !isRegistering
|
||||
isRegistering ? self.signUpButton.showLoading() : self.signUpButton.stopLoading()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
|
||||
|
@ -463,7 +459,7 @@ extension MastodonRegisterViewController {
|
|||
.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] _ in
|
||||
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)
|
||||
}
|
||||
|
@ -471,11 +467,6 @@ extension MastodonRegisterViewController {
|
|||
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 {
|
||||
|
@ -493,7 +484,7 @@ extension MastodonRegisterViewController: UITextFieldDelegate {
|
|||
case passwordTextField:
|
||||
viewModel.password.value = text
|
||||
case inviteTextField:
|
||||
viewModel.invite.value = text
|
||||
viewModel.reason.value = text
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
@ -536,13 +527,24 @@ extension MastodonRegisterViewController {
|
|||
let username = viewModel.username.value
|
||||
let email = viewModel.email.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 {
|
||||
// show server rules before register
|
||||
let mastodonServerRulesViewModel = MastodonServerRulesViewModel(
|
||||
context: context,
|
||||
domain: viewModel.domain,
|
||||
rules: rules
|
||||
authenticateInfo: viewModel.authenticateInfo,
|
||||
rules: rules,
|
||||
registerQuery: query,
|
||||
applicationAuthorization: viewModel.applicationAuthorization
|
||||
)
|
||||
|
||||
viewModel.isRegistering.value = false
|
||||
|
@ -551,51 +553,28 @@ extension MastodonRegisterViewController {
|
|||
return
|
||||
} else {
|
||||
// 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)
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -23,33 +23,19 @@ final class MastodonRegisterViewModel {
|
|||
let displayName = CurrentValueSubject<String, Never>("")
|
||||
let email = CurrentValueSubject<String, Never>("")
|
||||
let password = CurrentValueSubject<String, Never>("")
|
||||
let invite = 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)
|
||||
let reason = CurrentValueSubject<String, Never>("")
|
||||
|
||||
// output
|
||||
lazy var approvalRequired: Bool = {
|
||||
if let approvalRequired = instance.approvalRequired {
|
||||
return approvalRequired
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
let approvalRequired: Bool
|
||||
let applicationAuthorization: Mastodon.API.OAuth.Authorization
|
||||
|
||||
let usernameValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let displayNameValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let emailValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let passwordValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
let inviteValidateState = CurrentValueSubject<ValidateState, Never>(.empty)
|
||||
|
||||
let isRegistering = CurrentValueSubject<Bool, Never>(false)
|
||||
let isAllValid = CurrentValueSubject<Bool, Never>(false)
|
||||
|
||||
let error = CurrentValueSubject<Error?, Never>(nil)
|
||||
|
||||
init(
|
||||
|
@ -62,6 +48,7 @@ final class MastodonRegisterViewModel {
|
|||
self.authenticateInfo = authenticateInfo
|
||||
self.instance = instance
|
||||
self.applicationToken = applicationToken
|
||||
self.approvalRequired = instance.approvalRequired ?? false
|
||||
self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken)
|
||||
|
||||
username
|
||||
|
@ -107,7 +94,7 @@ final class MastodonRegisterViewModel {
|
|||
.assign(to: \.value, on: passwordValidateState)
|
||||
.store(in: &disposeBag)
|
||||
if approvalRequired {
|
||||
invite
|
||||
reason
|
||||
.map { invite in
|
||||
guard !invite.isEmpty else { return .empty }
|
||||
return .valid
|
||||
|
|
|
@ -10,7 +10,8 @@ import os.log
|
|||
import UIKit
|
||||
import WebKit
|
||||
|
||||
final class MastodonResendEmailViewController: UIViewController, NeedsDependency, WKNavigationDelegate {
|
||||
final class MastodonResendEmailViewController: UIViewController, NeedsDependency {
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
|
@ -35,6 +36,7 @@ final class MastodonResendEmailViewController: UIViewController, NeedsDependency
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonResendEmailViewController {
|
||||
|
|
|
@ -11,6 +11,7 @@ import os.log
|
|||
import WebKit
|
||||
|
||||
final class MastodonResendEmailViewModel {
|
||||
|
||||
// input
|
||||
let resendEmailURL: URL
|
||||
let email: String
|
||||
|
@ -25,6 +26,7 @@ final class MastodonResendEmailViewModel {
|
|||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
extension MastodonResendEmailViewModel {
|
||||
|
||||
|
|
|
@ -7,9 +7,12 @@
|
|||
|
||||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
|
||||
final class MastodonServerRulesViewController: UIViewController, NeedsDependency {
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
|
@ -56,7 +59,7 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
|
|||
return label
|
||||
}()
|
||||
|
||||
let confirmButton: UIButton = {
|
||||
let confirmButton: PrimaryActionButton = {
|
||||
let button = PrimaryActionButton()
|
||||
button.titleLabel?.font = .preferredFont(forTextStyle: .headline)
|
||||
button.setTitleColor(.white, for: .normal)
|
||||
|
@ -69,6 +72,10 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
|
|||
scrollView.alwaysBounceVertical = true
|
||||
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
|
||||
view.addSubview(scrollView)
|
||||
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),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor),
|
||||
scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor),
|
||||
|
@ -136,6 +143,14 @@ extension MastodonServerRulesViewController {
|
|||
|
||||
rulesLabel.attributedText = viewModel.rulesAttributedString
|
||||
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 {
|
||||
@objc private func confirmButtonPressed(_ sender: UIButton) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
|
||||
final class MastodonServerRulesViewModel {
|
||||
|
@ -13,12 +14,30 @@ final class MastodonServerRulesViewModel {
|
|||
// input
|
||||
let context: AppContext
|
||||
let domain: String
|
||||
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
||||
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.domain = domain
|
||||
self.authenticateInfo = authenticateInfo
|
||||
self.rules = rules
|
||||
self.registerQuery = registerQuery
|
||||
self.applicationAuthorization = applicationAuthorization
|
||||
}
|
||||
|
||||
var rulesAttributedString: NSAttributedString {
|
||||
|
@ -32,9 +51,6 @@ final class MastodonServerRulesViewModel {
|
|||
attributedString.append(indexString)
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ protocol OnboardingViewControllerAppearance: UIViewController {
|
|||
extension OnboardingViewControllerAppearance {
|
||||
|
||||
static var actionButtonHeight: CGFloat { return 46 }
|
||||
static var actionButtonMargin: CGFloat { return 12 }
|
||||
static var viewBottomPaddingHeight: CGFloat { return 11 }
|
||||
|
||||
func setupOnboardingAppearance() {
|
|
@ -47,6 +47,11 @@ final class WelcomeViewController: UIViewController, NeedsDependency {
|
|||
button.translatesAutoresizingMaskIntoConstraints = false
|
||||
return button
|
||||
}()
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", ((#file as NSString).lastPathComponent), #line, #function)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension WelcomeViewController {
|
||||
|
@ -74,14 +79,14 @@ extension WelcomeViewController {
|
|||
view.addSubview(signInButton)
|
||||
view.addSubview(signUpButton)
|
||||
NSLayoutConstraint.activate([
|
||||
signInButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor, constant: 12),
|
||||
signInButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: WelcomeViewController.actionButtonMargin),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: signInButton.trailingAnchor, constant: WelcomeViewController.actionButtonMargin),
|
||||
view.layoutMarginsGuide.bottomAnchor.constraint(equalTo: signInButton.bottomAnchor, constant: WelcomeViewController.viewBottomPaddingHeight),
|
||||
signInButton.heightAnchor.constraint(equalToConstant: 46).priority(.defaultHigh),
|
||||
signInButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh),
|
||||
|
||||
signInButton.topAnchor.constraint(equalTo: signUpButton.bottomAnchor, constant: 9),
|
||||
signUpButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: 12),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor, constant: 12),
|
||||
signUpButton.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor, constant: WelcomeViewController.actionButtonMargin),
|
||||
view.readableContentGuide.trailingAnchor.constraint(equalTo: signUpButton.trailingAnchor, constant: WelcomeViewController.actionButtonMargin),
|
||||
signUpButton.heightAnchor.constraint(equalToConstant: WelcomeViewController.actionButtonHeight).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
|
@ -89,19 +94,28 @@ extension WelcomeViewController {
|
|||
signInButton.addTarget(self, action: #selector(signInButtonDidClicked(_:)), for: .touchUpInside)
|
||||
}
|
||||
|
||||
override var preferredStatusBarStyle: UIStatusBarStyle { return .darkContent }
|
||||
|
||||
}
|
||||
|
||||
extension WelcomeViewController {
|
||||
@objc
|
||||
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
|
||||
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
|
||||
extension WelcomeViewController: OnboardingViewControllerAppearance { }
|
||||
|
||||
// MARK: - UIAdaptivePresentationControllerDelegate
|
||||
extension WelcomeViewController: UIAdaptivePresentationControllerDelegate {
|
||||
func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle {
|
||||
return .fullScreen
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -25,22 +25,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
self.coordinator = sceneCoordinator
|
||||
|
||||
sceneCoordinator.setup()
|
||||
|
||||
// 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)
|
||||
// }
|
||||
|
||||
sceneCoordinator.setupOnboardingIfNeeds(animated: false)
|
||||
window.makeKeyAndVisible()
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ extension Mastodon.API.Error: LocalizedError {
|
|||
|
||||
public var errorDescription: String? {
|
||||
guard let mastodonError = mastodonError else {
|
||||
return nil
|
||||
return "HTTP \(httpResponseStatus.code)"
|
||||
}
|
||||
switch mastodonError {
|
||||
case .generic(let error):
|
||||
|
@ -49,7 +49,7 @@ extension Mastodon.API.Error: LocalizedError {
|
|||
|
||||
public var failureReason: String? {
|
||||
guard let mastodonError = mastodonError else {
|
||||
return nil
|
||||
return httpResponseStatus.reasonPhrase
|
||||
}
|
||||
switch mastodonError {
|
||||
case .generic(let error):
|
||||
|
|
Loading…
Reference in New Issue