From 9a79b9a5458d9fb9e65c4e48cc0c45a6c189ad70 Mon Sep 17 00:00:00 2001 From: CMK Date: Mon, 22 Feb 2021 16:20:44 +0800 Subject: [PATCH] feat: add MastodonServerRules scene --- Mastodon.xcodeproj/project.pbxproj | 44 ++++- Mastodon/Coordinator/SceneCoordinator.swift | 5 + Mastodon/Generated/Assets.swift | 2 +- Mastodon/Generated/Strings.swift | 107 +++++++++++ .../Contents.json | 0 Mastodon/Resources/en.lproj/InfoPlist.strings | 2 + .../Resources/en.lproj/Localizable.strings | 43 ++++- .../AuthenticationViewController.swift | 48 +++-- .../MastodonRegisterViewController.swift | 110 +++++++---- .../Register/MastodonRegisterViewModel.swift | 3 + .../MastodonServerRulesViewController.swift | 174 ++++++++++++++++++ .../MastodonServerRulesViewModel.swift | 41 +++++ .../HomeTimelineViewController.swift | 2 +- Mastodon/Vender/UIViewPreview.swift | 49 +++++ 14 files changed, 561 insertions(+), 69 deletions(-) rename Mastodon/Resources/Assets.xcassets/Colors/Background/{signUp.system.background.colorset => onboarding.background.colorset}/Contents.json (100%) create mode 100644 Mastodon/Resources/en.lproj/InfoPlist.strings create mode 100644 Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift create mode 100644 Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewModel.swift create mode 100644 Mastodon/Vender/UIViewPreview.swift diff --git a/Mastodon.xcodeproj/project.pbxproj b/Mastodon.xcodeproj/project.pbxproj index 3eac67d43..c7180d3bf 100644 --- a/Mastodon.xcodeproj/project.pbxproj +++ b/Mastodon.xcodeproj/project.pbxproj @@ -74,6 +74,8 @@ DB0140CF25C42AEE00F9F3CF /* OSLog.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0140CE25C42AEE00F9F3CF /* OSLog.swift */; }; DB084B5725CBC56C00F898ED /* Toot.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB084B5625CBC56C00F898ED /* Toot.swift */; }; DB0AC6FC25CD02E600D75117 /* APIService+Instance.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */; }; + DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */; }; + DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB2B3AE825E38850007045F9 /* UIViewPreview.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 */; }; @@ -96,6 +98,8 @@ DB5086AB25CC0BBB00C2C187 /* AvatarConfigurableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */; }; DB5086B825CC0D6400C2C187 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = DB5086B725CC0D6400C2C187 /* Kingfisher */; }; DB5086BE25CC0D9900C2C187 /* SplashPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB5086BD25CC0D9900C2C187 /* SplashPreference.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 */; }; DB89B9FE25C10FD0008580ED /* CoreDataStackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DB89B9FD25C10FD0008580ED /* CoreDataStackTests.swift */; }; DB89BA0025C10FD0008580ED /* CoreDataStack.h in Headers */ = {isa = PBXBuildFile; fileRef = DB89B9F025C10FD0008580ED /* CoreDataStack.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -252,6 +256,8 @@ DB0140CE25C42AEE00F9F3CF /* OSLog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OSLog.swift; sourceTree = ""; }; DB084B5625CBC56C00F898ED /* Toot.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toot.swift; sourceTree = ""; }; DB0AC6FB25CD02E600D75117 /* APIService+Instance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "APIService+Instance.swift"; sourceTree = ""; }; + DB2B3ABD25E37E15007045F9 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + DB2B3AE825E38850007045F9 /* UIViewPreview.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewPreview.swift; sourceTree = ""; }; DB3D0FED25BAA42200EAA174 /* MastodonSDK */ = {isa = PBXFileReference; lastKnownFileType = folder; path = MastodonSDK; sourceTree = ""; }; DB3D100E25BAA75E00EAA174 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; DB427DD225BAA00100D1B89D /* Mastodon.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Mastodon.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -279,6 +285,8 @@ DB5086A425CC0B7000C2C187 /* AvatarBarButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarBarButtonItem.swift; sourceTree = ""; }; DB5086AA25CC0BBB00C2C187 /* AvatarConfigurableView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AvatarConfigurableView.swift; sourceTree = ""; }; DB5086BD25CC0D9900C2C187 /* SplashPreference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashPreference.swift; sourceTree = ""; }; + DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewController.swift; sourceTree = ""; }; + DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MastodonServerRulesViewModel.swift; sourceTree = ""; }; DB89B9EE25C10FD0008580ED /* CoreDataStack.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreDataStack.framework; sourceTree = BUILT_PRODUCTS_DIR; }; DB89B9F025C10FD0008580ED /* CoreDataStack.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreDataStack.h; sourceTree = ""; }; DB89B9F125C10FD0008580ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -437,6 +445,7 @@ isa = PBXGroup; children = ( 2D5A3D0225CF8742002347D6 /* ControlContainableScrollViews.swift */, + DB2B3AE825E38850007045F9 /* UIViewPreview.swift */, ); path = Vender; sourceTree = ""; @@ -564,6 +573,7 @@ children = ( DB0140A625C40C0900F9F3CF /* PinBased */, DBE0821A25CD382900FD6BBD /* Register */, + DB72602125E36A2500235243 /* ServerRules */, DB01409525C40B6700F9F3CF /* AuthenticationViewController.swift */, DB98334625C8056600AD9700 /* AuthenticationViewModel.swift */, ); @@ -605,6 +615,7 @@ children = ( DB427DDE25BAA00100D1B89D /* Assets.xcassets */, DB3D100F25BAA75E00EAA174 /* Localizable.strings */, + DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */, ); path = Resources; sourceTree = ""; @@ -644,13 +655,13 @@ children = ( DB427DE325BAA00100D1B89D /* Info.plist */, DB89BA1025C10FF5008580ED /* Mastodon.entitlements */, - 2D5A3D0125CF8640002347D6 /* Vender */, 2D76319C25C151DE00929FB9 /* Diffiable */, DB8AF52A25C13561002E6C99 /* State */, 2D61335525C1886800CAE157 /* Service */, DB8AF55525C1379F002E6C99 /* Scene */, DB8AF54125C13647002E6C99 /* Coordinator */, DB8AF56225C138BC002E6C99 /* Extension */, + 2D5A3D0125CF8640002347D6 /* Vender */, DB5086CB25CC0DB400C2C187 /* Preference */, 2D69CFF225CA9E2200C3A1B2 /* Protocol */, DB98338425C945ED00AD9700 /* Generated */, @@ -715,6 +726,15 @@ path = Preference; sourceTree = ""; }; + DB72602125E36A2500235243 /* ServerRules */ = { + isa = PBXGroup; + children = ( + DB72601B25E36A2100235243 /* MastodonServerRulesViewController.swift */, + DB72602625E36A6F00235243 /* MastodonServerRulesViewModel.swift */, + ); + path = ServerRules; + sourceTree = ""; + }; DB89B9EF25C10FD0008580ED /* CoreDataStack */ = { isa = PBXGroup; children = ( @@ -805,12 +825,11 @@ DB8AF55525C1379F002E6C99 /* Scene */ = { isa = PBXGroup; children = ( - 2D38F1D325CD463600561493 /* HomeTimeline */, 2D7631A425C1532200929FB9 /* Share */, DB8AF54E25C13703002E6C99 /* MainTab */, DB01409B25C40BB600F9F3CF /* Authentication */, + 2D38F1D325CD463600561493 /* HomeTimeline */, 2D76316325C14BAC00929FB9 /* PublicTimeline */, - DBD4ED0B25CC0FD40041B741 /* HomeTimeline */, ); path = Scene; sourceTree = ""; @@ -852,13 +871,6 @@ path = Generated; sourceTree = ""; }; - DBD4ED0B25CC0FD40041B741 /* HomeTimeline */ = { - isa = PBXGroup; - children = ( - ); - path = HomeTimeline; - sourceTree = ""; - }; DBE0821A25CD382900FD6BBD /* Register */ = { isa = PBXGroup; children = ( @@ -1057,6 +1069,7 @@ DB3D100D25BAA75E00EAA174 /* Localizable.strings in Resources */, DB427DDF25BAA00100D1B89D /* Assets.xcassets in Resources */, DB427DDD25BAA00100D1B89D /* Main.storyboard in Resources */, + DB2B3ABC25E37E15007045F9 /* InfoPlist.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1222,10 +1235,12 @@ 2D38F1F725CD47AC00561493 /* HomeTimelineViewModel+LoadOldestState.swift in Sources */, 2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */, DB8AF53025C13561002E6C99 /* AppContext.swift in Sources */, + DB72602725E36A6F00235243 /* MastodonServerRulesViewModel.swift in Sources */, 2D38F1F125CD477D00561493 /* HomeTimelineViewModel+LoadMiddleState.swift in Sources */, DB45FAD725CA6C76005A8AC7 /* UIBarButtonItem.swift in Sources */, 2D152A8C25C295CC009AA50C /* TimelinePostView.swift in Sources */, 2D42FF8525C8224F004A627A /* HitTestExpandedButton.swift in Sources */, + DB72601C25E36A2100235243 /* MastodonServerRulesViewController.swift in Sources */, 2D42FF8F25C8228A004A627A /* UIButton.swift in Sources */, 2D61335825C188A000CAE157 /* APIService+Persist+Timeline.swift in Sources */, DB45FAE325CA7181005A8AC7 /* MastodonUser.swift in Sources */, @@ -1266,6 +1281,7 @@ 2D76319F25C1521200929FB9 /* TimelineSection.swift in Sources */, DB084B5725CBC56C00F898ED /* Toot.swift in Sources */, DB0140A825C40C1500F9F3CF /* MastodonPinBasedAuthenticationViewModelNavigationDelegateShim.swift in Sources */, + DB2B3AE925E38850007045F9 /* UIViewPreview.swift in Sources */, DB427DD625BAA00100D1B89D /* AppDelegate.swift in Sources */, DB98338725C945ED00AD9700 /* Strings.swift in Sources */, DB45FAB625CA5485005A8AC7 /* UIAlertController.swift in Sources */, @@ -1380,6 +1396,14 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + DB2B3ABE25E37E15007045F9 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + DB2B3ABD25E37E15007045F9 /* en */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; DB3D100F25BAA75E00EAA174 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 6bdf135af..0b4123965 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -40,6 +40,7 @@ extension SceneCoordinator { case authentication(viewModel: AuthenticationViewModel) case mastodonPinBasedAuthentication(viewModel: MastodonPinBasedAuthenticationViewModel) case mastodonRegister(viewModel: MastodonRegisterViewModel) + case mastodonServerRules(viewModel: MastodonServerRulesViewModel) case alertController(alertController: UIAlertController) } @@ -125,6 +126,10 @@ private extension SceneCoordinator { let _viewController = MastodonRegisterViewController() _viewController.viewModel = viewModel viewController = _viewController + case .mastodonServerRules(let viewModel): + let _viewController = MastodonServerRulesViewController() + _viewController.viewModel = viewModel + viewController = _viewController case .alertController(let alertController): if let popoverPresentationController = alertController.popoverPresentationController { assert( diff --git a/Mastodon/Generated/Assets.swift b/Mastodon/Generated/Assets.swift index 84de0ae9d..4510a6860 100644 --- a/Mastodon/Generated/Assets.swift +++ b/Mastodon/Generated/Assets.swift @@ -27,8 +27,8 @@ internal enum Asset { } internal enum Colors { internal enum Background { + internal static let onboardingBackground = ColorAsset(name: "Colors/Background/onboarding.background") internal static let secondarySystemBackground = ColorAsset(name: "Colors/Background/secondary.system.background") - internal static let signUpSystemBackground = ColorAsset(name: "Colors/Background/signUp.system.background") internal static let systemBackground = ColorAsset(name: "Colors/Background/system.background") internal static let tertiarySystemBackground = ColorAsset(name: "Colors/Background/tertiary.system.background") } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index 3399d7352..f8383a825 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -13,11 +13,118 @@ internal enum L10n { internal enum Common { internal enum Controls { + internal enum Actions { + /// Add + internal static let add = L10n.tr("Localizable", "Common.Controls.Actions.Add") + /// Cancel + internal static let cancel = L10n.tr("Localizable", "Common.Controls.Actions.Cancel") + /// Confirm + internal static let confirm = L10n.tr("Localizable", "Common.Controls.Actions.Confirm") + /// Continue + internal static let `continue` = L10n.tr("Localizable", "Common.Controls.Actions.Continue") + /// Edit + internal static let edit = L10n.tr("Localizable", "Common.Controls.Actions.Edit") + /// OK + internal static let ok = L10n.tr("Localizable", "Common.Controls.Actions.Ok") + /// Open in Safari + internal static let openInSafari = L10n.tr("Localizable", "Common.Controls.Actions.OpenInSafari") + /// Preview + internal static let preview = L10n.tr("Localizable", "Common.Controls.Actions.Preview") + /// Remove + internal static let remove = L10n.tr("Localizable", "Common.Controls.Actions.Remove") + /// Save + internal static let save = L10n.tr("Localizable", "Common.Controls.Actions.Save") + /// Save photo + 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 + internal static let signIn = L10n.tr("Localizable", "Common.Controls.Actions.SignIn") + /// 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") + } internal enum Timeline { /// Load More internal static let loadMore = L10n.tr("Localizable", "Common.Controls.Timeline.LoadMore") } } + internal enum Countable { + internal enum Photo { + /// photos + internal static let multiple = L10n.tr("Localizable", "Common.Countable.Photo.Multiple") + /// photo + internal static let single = L10n.tr("Localizable", "Common.Countable.Photo.Single") + } + } + } + + internal enum Scene { + internal enum HomeTimeline { + /// Home + internal static let title = L10n.tr("Localizable", "Scene.HomeTimeline.Title") + } + internal enum PublicTimeline { + /// Public + internal static let title = L10n.tr("Localizable", "Scene.PublicTimeline.Title") + } + internal enum Register { + /// Tell us about you. + internal static let title = L10n.tr("Localizable", "Scene.Register.Title") + internal enum Input { + internal enum DisplayName { + /// display name + internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.DisplayName.Placeholder") + } + internal enum Email { + /// email + internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.Email.Placeholder") + } + internal enum Password { + /// password + internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.Password.Placeholder") + /// Your password needs at least: + internal static let prompt = L10n.tr("Localizable", "Scene.Register.Input.Password.Prompt") + /// Eight characters + internal static let promptEightCharacters = L10n.tr("Localizable", "Scene.Register.Input.Password.PromptEightCharacters") + } + internal enum Username { + /// This username is taken. + internal static let duplicatePrompt = L10n.tr("Localizable", "Scene.Register.Input.Username.DuplicatePrompt") + /// username + internal static let placeholder = L10n.tr("Localizable", "Scene.Register.Input.Username.Placeholder") + } + } + } + internal enum ServerPicker { + /// Pick a Server,\nany server. + internal static let title = L10n.tr("Localizable", "Scene.ServerPicker.Title") + internal enum Input { + /// Find a server or join your own... + internal static let placeholder = L10n.tr("Localizable", "Scene.ServerPicker.Input.Placeholder") + } + } + internal enum ServerRules { + /// By continuing, you're subject to the terms of service and privacy policy for %@. + internal static func prompt(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.ServerRules.Prompt", String(describing: p1)) + } + /// These rules are set by the admins of %@. + internal static func subtitle(_ p1: Any) -> String { + return L10n.tr("Localizable", "Scene.ServerRules.Subtitle", String(describing: p1)) + } + /// Some ground rules. + internal static let title = L10n.tr("Localizable", "Scene.ServerRules.Title") + internal enum Button { + /// I Agree + internal static let confirm = L10n.tr("Localizable", "Scene.ServerRules.Button.Confirm") + } + } + internal enum Welcome { + /// Social networking\nback in your hands. + internal static let slogan = L10n.tr("Localizable", "Scene.Welcome.Slogan") + } } } // swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length diff --git a/Mastodon/Resources/Assets.xcassets/Colors/Background/signUp.system.background.colorset/Contents.json b/Mastodon/Resources/Assets.xcassets/Colors/Background/onboarding.background.colorset/Contents.json similarity index 100% rename from Mastodon/Resources/Assets.xcassets/Colors/Background/signUp.system.background.colorset/Contents.json rename to Mastodon/Resources/Assets.xcassets/Colors/Background/onboarding.background.colorset/Contents.json diff --git a/Mastodon/Resources/en.lproj/InfoPlist.strings b/Mastodon/Resources/en.lproj/InfoPlist.strings new file mode 100644 index 000000000..972e1a7a2 --- /dev/null +++ b/Mastodon/Resources/en.lproj/InfoPlist.strings @@ -0,0 +1,2 @@ +"NSCameraUsageDescription" = "Used to take photo for toot"; +"NSPhotoLibraryAddUsageDescription" = "Used to save photo into the Photo Library"; \ No newline at end of file diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index caa87e952..707ef3cce 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -1,8 +1,37 @@ -/* - Localizable.strings - Mastodon - - Created by MainasuK Cirno on 2021/1/22. - -*/ +"Common.Controls.Actions.Add" = "Add"; +"Common.Controls.Actions.Cancel" = "Cancel"; +"Common.Controls.Actions.Confirm" = "Confirm"; +"Common.Controls.Actions.Continue" = "Continue"; +"Common.Controls.Actions.Edit" = "Edit"; +"Common.Controls.Actions.Ok" = "OK"; +"Common.Controls.Actions.OpenInSafari" = "Open in Safari"; +"Common.Controls.Actions.Preview" = "Preview"; +"Common.Controls.Actions.Remove" = "Remove"; +"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.TakePhoto" = "Take photo"; "Common.Controls.Timeline.LoadMore" = "Load More"; +"Common.Countable.Photo.Multiple" = "photos"; +"Common.Countable.Photo.Single" = "photo"; +"Scene.HomeTimeline.Title" = "Home"; +"Scene.PublicTimeline.Title" = "Public"; +"Scene.Register.Input.DisplayName.Placeholder" = "display name"; +"Scene.Register.Input.Email.Placeholder" = "email"; +"Scene.Register.Input.Password.Placeholder" = "password"; +"Scene.Register.Input.Password.Prompt" = "Your password needs at least:"; +"Scene.Register.Input.Password.PromptEightCharacters" = "Eight characters"; +"Scene.Register.Input.Username.DuplicatePrompt" = "This username is taken."; +"Scene.Register.Input.Username.Placeholder" = "username"; +"Scene.Register.Title" = "Tell us about you."; +"Scene.ServerPicker.Input.Placeholder" = "Find a server or join your own..."; +"Scene.ServerPicker.Title" = "Pick a Server, +any server."; +"Scene.ServerRules.Button.Confirm" = "I Agree"; +"Scene.ServerRules.Prompt" = "By continuing, you're subject to the terms of service and privacy policy for %@."; +"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."; \ No newline at end of file diff --git a/Mastodon/Scene/Authentication/AuthenticationViewController.swift b/Mastodon/Scene/Authentication/AuthenticationViewController.swift index b02cf3c4c..5ab500bc2 100644 --- a/Mastodon/Scene/Authentication/AuthenticationViewController.swift +++ b/Mastodon/Scene/Authentication/AuthenticationViewController.swift @@ -77,6 +77,7 @@ extension AuthenticationViewController { override func viewDidLoad() { super.viewDidLoad() + overrideUserInterfaceStyle = .dark // FIXME: title = "Authentication" view.backgroundColor = Asset.Colors.Background.systemBackground.color @@ -265,6 +266,22 @@ extension AuthenticationViewController { .store(in: &disposeBag) } + private struct SignUpResponseFirst { + let instance: Mastodon.Response.Content + let application: Mastodon.Response.Content + } + + private struct SignUpResponseSecond { + let instance: Mastodon.Response.Content + let authenticateInfo: AuthenticationViewModel.AuthenticateInfo + } + + private struct SignUpResponseThird { + let instance: Mastodon.Response.Content + let authenticateInfo: AuthenticationViewModel.AuthenticateInfo + let applicationToken: Mastodon.Response.Content + } + @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 { @@ -275,26 +292,34 @@ extension AuthenticationViewController { viewModel.isRegistering.value = true context.apiService.instance(domain: domain) - .compactMap { [weak self] response -> AnyPublisher, Error>? in + .compactMap { [weak self] response -> AnyPublisher? 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 -> AuthenticationViewModel.AuthenticateInfo in - let application = response.value + .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 authenticateInfo + return SignUpResponseSecond(instance: response.instance, authenticateInfo: authenticateInfo) } - .compactMap { [weak self] authenticateInfo -> AnyPublisher<(Mastodon.Response.Content, AuthenticationViewModel.AuthenticateInfo), Error>? in + .compactMap { [weak self] response -> AnyPublisher? in guard let self = self else { return nil } - return self.context.apiService.applicationAccessToken(domain: domain, clientID: authenticateInfo.clientID, clientSecret: authenticateInfo.clientSecret) - .map { ($0, authenticateInfo) } - .eraseToAnyPublisher() + 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) @@ -308,12 +333,13 @@ extension AuthenticationViewController { case .finished: break } - } receiveValue: { [weak self] response, authenticateInfo in + } receiveValue: { [weak self] response in guard let self = self else { return } let mastodonRegisterViewModel = MastodonRegisterViewModel( domain: domain, - authenticateInfo: authenticateInfo, - applicationToken: response.value + authenticateInfo: response.authenticateInfo, + instance: response.instance.value, + applicationToken: response.applicationToken.value ) self.coordinator.present(scene: .mastodonRegister(viewModel: mastodonRegisterViewModel), from: self, transition: .show) } diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift index 5361b3117..de5b5c680 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewController.swift @@ -20,13 +20,19 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { var viewModel: MastodonRegisterViewModel! let tapGestureRecognizer = UITapGestureRecognizer.singleTapGestureRecognizer - let stackViewTopDistance: CGFloat = 16 - var scrollview: UIScrollView = { + let statusBarBackground: UIView = { + let view = UIView() + view.backgroundColor = Asset.Colors.Background.onboardingBackground.color + return view + }() + + let scrollView: UIScrollView = { let scrollview = UIScrollView() scrollview.showsVerticalScrollIndicator = false scrollview.translatesAutoresizingMaskIntoConstraints = false scrollview.keyboardDismissMode = .interactive + scrollview.clipsToBounds = false // make content could display over bleeding return scrollview }() @@ -34,7 +40,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { let label = UILabel() label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34)) label.textColor = Asset.Colors.Label.black.color - label.text = "Tell us about you." + label.text = L10n.Scene.Register.title return label }() @@ -92,7 +98,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { textField.autocorrectionType = .no textField.backgroundColor = .white textField.textColor = .black - textField.attributedPlaceholder = NSAttributedString(string: "username", + textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Username.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) textField.borderStyle = UITextField.BorderStyle.roundedRect @@ -113,7 +119,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { textField.autocorrectionType = .no textField.backgroundColor = .white textField.textColor = .black - textField.attributedPlaceholder = NSAttributedString(string: "display name", + textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.DisplayName.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) textField.borderStyle = UITextField.BorderStyle.roundedRect @@ -130,7 +136,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { textField.keyboardType = .emailAddress textField.backgroundColor = .white textField.textColor = .black - textField.attributedPlaceholder = NSAttributedString(string: "email", + textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Email.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) textField.borderStyle = UITextField.BorderStyle.roundedRect @@ -154,7 +160,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { textField.isSecureTextEntry = true textField.backgroundColor = .white textField.textColor = .black - textField.attributedPlaceholder = NSAttributedString(string: "password", + textField.attributedPlaceholder = NSAttributedString(string: L10n.Scene.Register.Input.Password.placeholder, attributes: [NSAttributedString.Key.foregroundColor: Asset.Colors.lightSecondaryText.color, NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .headline)]) textField.borderStyle = UITextField.BorderStyle.roundedRect @@ -171,7 +177,7 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.lightDisabled.color), for: .disabled) button.isEnabled = false button.setTitleColor(Asset.Colors.Label.primary.color, for: .normal) - button.setTitle("Continue", for: .normal) + button.setTitle(L10n.Common.Controls.Actions.continue, for: .normal) button.layer.masksToBounds = true button.layer.cornerRadius = 8 button.layer.cornerCurve = .continuous @@ -186,11 +192,12 @@ final class MastodonRegisterViewController: UIViewController, NeedsDependency { } extension MastodonRegisterViewController { + override func viewDidLoad() { super.viewDidLoad() - navigationController?.navigationBar.isHidden = true - view.backgroundColor = Asset.Colors.Background.signUpSystemBackground.color + overrideUserInterfaceStyle = .light + view.backgroundColor = Asset.Colors.Background.onboardingBackground.color domainLabel.text = "@" + viewModel.domain + " " domainLabel.sizeToFit() passwordCheckLabel.attributedText = viewModel.attributeStringForPassword() @@ -210,7 +217,7 @@ extension MastodonRegisterViewController { stackView.axis = .vertical stackView.distribution = .fill stackView.spacing = 40 - stackView.layoutMargins = UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 4) + stackView.layoutMargins = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) stackView.isLayoutMarginsRelativeArrangement = true stackView.addArrangedSubview(largeTitleLabel) stackView.addArrangedSubview(photoView) @@ -220,25 +227,34 @@ extension MastodonRegisterViewController { stackView.addArrangedSubview(passwordTextField) stackView.addArrangedSubview(passwordCheckLabel) - // scrollview - view.addSubview(scrollview) + // scrollView + view.addSubview(scrollView) NSLayoutConstraint.activate([ - scrollview.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), - scrollview.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), - view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollview.frameLayoutGuide.trailingAnchor), - scrollview.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), - scrollview.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollview.contentLayoutGuide.widthAnchor), + scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), + view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor), + scrollView.frameLayoutGuide.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.bottomAnchor), + scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor), ]) // stackview - scrollview.addSubview(stackView) + scrollView.addSubview(stackView) stackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: scrollview.contentLayoutGuide.topAnchor, constant: stackViewTopDistance), - stackView.leadingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.leadingAnchor), - stackView.trailingAnchor.constraint(equalTo: scrollview.contentLayoutGuide.trailingAnchor), - stackView.widthAnchor.constraint(equalTo: scrollview.frameLayoutGuide.widthAnchor), - scrollview.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor), + stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), + stackView.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor), + scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor), + ]) + + statusBarBackground.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(statusBarBackground) + NSLayoutConstraint.activate([ + statusBarBackground.topAnchor.constraint(equalTo: view.topAnchor), + statusBarBackground.leadingAnchor.constraint(equalTo: view.leadingAnchor), + statusBarBackground.trailingAnchor.constraint(equalTo: view.trailingAnchor), + statusBarBackground.bottomAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), ]) // photoview @@ -283,11 +299,11 @@ extension MastodonRegisterViewController { signUpButton.translatesAutoresizingMaskIntoConstraints = false stackView.addArrangedSubview(signUpButton) NSLayoutConstraint.activate([ - signUpButton.heightAnchor.constraint(equalToConstant: 44).priority(.defaultHigh), + signUpButton.heightAnchor.constraint(equalToConstant: 46).priority(.defaultHigh), ]) signUpActivityIndicatorView.translatesAutoresizingMaskIntoConstraints = false - scrollview.addSubview(signUpActivityIndicatorView) + scrollView.addSubview(signUpActivityIndicatorView) NSLayoutConstraint.activate([ signUpActivityIndicatorView.centerXAnchor.constraint(equalTo: signUpButton.centerXAnchor), signUpActivityIndicatorView.centerYAnchor.constraint(equalTo: signUpButton.centerYAnchor), @@ -302,22 +318,22 @@ extension MastodonRegisterViewController { guard let self = self else { return } guard isShow, state == .dock else { - self.scrollview.contentInset.bottom = 0.0 - self.scrollview.verticalScrollIndicatorInsets.bottom = 0.0 + self.scrollView.contentInset.bottom = 0.0 + self.scrollView.verticalScrollIndicatorInsets.bottom = 0.0 return } // isShow AND dock state - let contentFrame = self.view.convert(self.scrollview.frame, to: nil) + let contentFrame = self.view.convert(self.scrollView.frame, to: nil) let padding = contentFrame.maxY - endFrame.minY guard padding > 0 else { - self.scrollview.contentInset.bottom = 0.0 - self.scrollview.verticalScrollIndicatorInsets.bottom = 0.0 + self.scrollView.contentInset.bottom = 0.0 + self.scrollView.verticalScrollIndicatorInsets.bottom = 0.0 return } - self.scrollview.contentInset.bottom = padding + 16 - self.scrollview.verticalScrollIndicatorInsets.bottom = padding + 16 + self.scrollView.contentInset.bottom = padding + 16 + self.scrollView.verticalScrollIndicatorInsets.bottom = padding + 16 }) .store(in: &disposeBag) @@ -326,7 +342,7 @@ extension MastodonRegisterViewController { .sink { [weak self] isRegistering in guard let self = self else { return } isRegistering ? self.signUpActivityIndicatorView.startAnimating() : self.signUpActivityIndicatorView.stopAnimating() - self.signUpButton.setTitle(isRegistering ? "" : "Continue", for: .normal) + self.signUpButton.setTitle(isRegistering ? "" : L10n.Common.Controls.Actions.continue, for: .normal) self.signUpButton.isEnabled = !isRegistering } .store(in: &disposeBag) @@ -379,7 +395,7 @@ extension MastodonRegisterViewController { .sink { [weak self] error in guard let self = self else { return } let alertController = UIAlertController(error, preferredStyle: .alert) - let okAction = UIAlertAction(title: "OK", style: .default, handler: nil) + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default, handler: nil) alertController.addAction(okAction) self.coordinator.present( scene: .alertController(alertController: alertController), @@ -402,6 +418,12 @@ extension MastodonRegisterViewController { signUpButton.addTarget(self, action: #selector(MastodonRegisterViewController.signUpButtonPressed(_:)), for: .touchUpInside) } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + navigationController?.setNavigationBarHidden(true, animated: false) + } } extension MastodonRegisterViewController: UITextFieldDelegate { @@ -411,12 +433,12 @@ extension MastodonRegisterViewController: UITextFieldDelegate { KeyboardResponderService.shared.isShow.value, KeyboardResponderService.shared.state.value == .dock { let endFrame = KeyboardResponderService.shared.endFrame.value - let contentFrame = self.scrollview.convert(self.passwordCheckLabel.frame, to: nil) + let contentFrame = self.scrollView.convert(self.passwordCheckLabel.frame, to: nil) let padding = contentFrame.maxY - endFrame.minY if padding > 0 { - let contentOffsetY = scrollview.contentOffset.y + let contentOffsetY = scrollView.contentOffset.y DispatchQueue.main.async { - self.scrollview.setContentOffset(CGPoint(x: 0, y: contentOffsetY + padding + 16.0), animated: true) + self.scrollView.setContentOffset(CGPoint(x: 0, y: contentOffsetY + padding + 16.0), animated: true) } } } @@ -484,6 +506,16 @@ extension MastodonRegisterViewController { guard !viewModel.isRegistering.value else { return } viewModel.isRegistering.value = true + if let rules = viewModel.instance.rules, !rules.isEmpty { + let mastodonServerRulesViewModel = MastodonServerRulesViewModel( + context: context, + domain: viewModel.domain, + rules: rules + ) + coordinator.present(scene: .mastodonServerRules(viewModel: mastodonServerRulesViewModel), from: self, transition: .show) + return + } + let query = Mastodon.API.Account.RegisterQuery( reason: nil, username: username, @@ -513,7 +545,7 @@ extension MastodonRegisterViewController { _ = response.value // TODO: let alertController = UIAlertController(title: "Success", message: "Regsiter request sent. Please check your email.\n(Auto sign in not implement yet.)", preferredStyle: .alert) - let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in + let okAction = UIAlertAction(title: L10n.Common.Controls.Actions.ok, style: .default) { [weak self] _ in guard let self = self else { return } self.navigationController?.popViewController(animated: true) } diff --git a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift index 944cb9b0c..daea2f53e 100644 --- a/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift +++ b/Mastodon/Scene/Authentication/Register/MastodonRegisterViewModel.swift @@ -16,6 +16,7 @@ final class MastodonRegisterViewModel { // input let domain: String let authenticateInfo: AuthenticationViewModel.AuthenticateInfo + let instance: Mastodon.Entity.Instance let applicationToken: Mastodon.Entity.Token let isRegistering = CurrentValueSubject(false) @@ -37,10 +38,12 @@ final class MastodonRegisterViewModel { init( domain: String, authenticateInfo: AuthenticationViewModel.AuthenticateInfo, + instance: Mastodon.Entity.Instance, applicationToken: Mastodon.Entity.Token ) { self.domain = domain self.authenticateInfo = authenticateInfo + self.instance = instance self.applicationToken = applicationToken self.applicationAuthorization = Mastodon.API.OAuth.Authorization(accessToken: applicationToken.accessToken) diff --git a/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift b/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift new file mode 100644 index 000000000..affd69d4d --- /dev/null +++ b/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewController.swift @@ -0,0 +1,174 @@ +// +// MastodonServerRulesViewController.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-2-22. +// + +import os.log +import UIKit + +final class MastodonServerRulesViewController: UIViewController, NeedsDependency { + + weak var context: AppContext! { willSet { precondition(!isViewLoaded) } } + weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } } + + var viewModel: MastodonServerRulesViewModel! + + let largeTitleLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .largeTitle).scaledFont(for: UIFont.boldSystemFont(ofSize: 34)) + label.textColor = .label + label.text = L10n.Scene.ServerRules.title + return label + }() + + private(set) lazy var subtitleLabel: UILabel = { + let label = UILabel() + label.font = UIFontMetrics(forTextStyle: .title1).scaledFont(for: UIFont.systemFont(ofSize: 20)) + label.textColor = .secondaryLabel + label.text = L10n.Scene.ServerRules.subtitle(viewModel.domain) + label.numberOfLines = 0 + return label + }() + + let rulesLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body) + label.textColor = Asset.Colors.Label.black.color + label.text = "Rules" + label.numberOfLines = 0 + return label + }() + + let bottonContainerView: UIView = { + let view = UIView() + view.backgroundColor = Asset.Colors.Background.onboardingBackground.color + return view + }() + + private(set) lazy var bottomPromptLabel: UILabel = { + let label = UILabel() + label.font = .preferredFont(forTextStyle: .body) + label.textColor = .label + label.text = L10n.Scene.ServerRules.prompt(viewModel.domain) + label.numberOfLines = 0 + return label + }() + + let confirmButton: UIButton = { + let button = UIButton(type: .system) + button.titleLabel?.font = .preferredFont(forTextStyle: .headline) + button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.lightBrandBlue.color), for: .normal) + button.setBackgroundImage(UIImage.placeholder(color: Asset.Colors.lightDisabled.color), for: .disabled) + button.setTitleColor(Asset.Colors.Label.primary.color, for: .normal) + button.setTitle(L10n.Scene.ServerRules.Button.confirm, for: .normal) + button.layer.masksToBounds = true + button.layer.cornerRadius = 8 + button.layer.cornerCurve = .continuous + return button + }() + + let scrollView = UIScrollView() + +} + +extension MastodonServerRulesViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + overrideUserInterfaceStyle = .light + view.backgroundColor = Asset.Colors.Background.onboardingBackground.color + + bottonContainerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(bottonContainerView) + NSLayoutConstraint.activate([ + view.bottomAnchor.constraint(equalTo: bottonContainerView.bottomAnchor), + bottonContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + bottonContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ]) + bottonContainerView.preservesSuperviewLayoutMargins = true + + confirmButton.translatesAutoresizingMaskIntoConstraints = false + bottonContainerView.addSubview(confirmButton) + NSLayoutConstraint.activate([ + bottonContainerView.bottomAnchor.constraint(greaterThanOrEqualTo: confirmButton.bottomAnchor, constant: 16), + bottonContainerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: confirmButton.bottomAnchor).priority(.defaultHigh), + confirmButton.leadingAnchor.constraint(equalTo: bottonContainerView.readableContentGuide.leadingAnchor), + confirmButton.trailingAnchor.constraint(equalTo: bottonContainerView.readableContentGuide.trailingAnchor), + confirmButton.heightAnchor.constraint(equalToConstant: 46).priority(.defaultHigh), + ]) + + bottomPromptLabel.translatesAutoresizingMaskIntoConstraints = false + bottonContainerView.addSubview(bottomPromptLabel) + NSLayoutConstraint.activate([ + bottomPromptLabel.topAnchor.constraint(equalTo: bottonContainerView.topAnchor, constant: 20), + bottomPromptLabel.leadingAnchor.constraint(equalTo: bottonContainerView.readableContentGuide.leadingAnchor), + bottomPromptLabel.trailingAnchor.constraint(equalTo: bottonContainerView.readableContentGuide.trailingAnchor), + confirmButton.topAnchor.constraint(equalTo: bottomPromptLabel.bottomAnchor, constant: 20), + ]) + + scrollView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(scrollView) + NSLayoutConstraint.activate([ + scrollView.frameLayoutGuide.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + scrollView.frameLayoutGuide.leadingAnchor.constraint(equalTo: view.readableContentGuide.leadingAnchor), + view.readableContentGuide.trailingAnchor.constraint(equalTo: scrollView.frameLayoutGuide.trailingAnchor), + scrollView.frameLayoutGuide.widthAnchor.constraint(equalTo: scrollView.contentLayoutGuide.widthAnchor), + bottonContainerView.topAnchor.constraint(equalTo: scrollView.frameLayoutGuide.bottomAnchor), + ]) + + let stackView = UIStackView() + stackView.axis = .vertical + stackView.distribution = .fill + stackView.spacing = 10 + stackView.layoutMargins = UIEdgeInsets(top: 20, left: 0, bottom: 0, right: 0) + stackView.addArrangedSubview(largeTitleLabel) + stackView.addArrangedSubview(subtitleLabel) + stackView.addArrangedSubview(rulesLabel) + + stackView.translatesAutoresizingMaskIntoConstraints = false + scrollView.addSubview(stackView) + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), + stackView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor), + stackView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor), + scrollView.contentLayoutGuide.bottomAnchor.constraint(equalTo: stackView.bottomAnchor), + ]) + + rulesLabel.attributedText = viewModel.rulesAttributedString + confirmButton.addTarget(self, action: #selector(MastodonServerRulesViewController.confirmButtonPressed(_:)), for: .touchUpInside) + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + navigationController?.setNavigationBarHidden(true, animated: false) + } + +} + +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) + + } +} + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct ServerRulesViewController_Previews: PreviewProvider { + + static var previews: some View { + UIViewControllerPreview { + let viewController = MastodonServerRulesViewController() + return viewController + } + .previewLayout(.fixed(width: 375, height: 800)) + } + +} + +#endif diff --git a/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewModel.swift b/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewModel.swift new file mode 100644 index 000000000..0c43392f5 --- /dev/null +++ b/Mastodon/Scene/Authentication/ServerRules/MastodonServerRulesViewModel.swift @@ -0,0 +1,41 @@ +// +// MastodonServerRulesViewModel.swift +// Mastodon +// +// Created by MainasuK Cirno on 2021-2-22. +// + +import UIKit +import MastodonSDK + +final class MastodonServerRulesViewModel { + + // input + let context: AppContext + let domain: String + let rules: [Mastodon.Entity.Instance.Rule] + + init(context: AppContext, domain: String, rules: [Mastodon.Entity.Instance.Rule]) { + self.context = context + self.domain = domain + self.rules = rules + } + + var rulesAttributedString: NSAttributedString { + let attributedString = NSMutableAttributedString(string: "\n") + for (i, rule) in rules.enumerated() { + let index = String(i + 1) + let indexString = NSAttributedString(string: index + ". ", attributes: [ + NSAttributedString.Key.foregroundColor: UIColor.secondaryLabel + ]) + let ruleString = NSAttributedString(string: rule.text + "\n\n") + 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 + } + +} diff --git a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift index 21e3d6fea..cbd3fa9b9 100644 --- a/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift +++ b/Mastodon/Scene/HomeTimeline/HomeTimelineViewController.swift @@ -50,7 +50,7 @@ extension HomeTimelineViewController { override func viewDidLoad() { super.viewDidLoad() - title = "Home" + title = L10n.Scene.HomeTimeline.title view.backgroundColor = Asset.Colors.Background.systemBackground.color navigationItem.leftBarButtonItem = avatarBarButtonItem avatarBarButtonItem.avatarButton.addTarget(self, action: #selector(HomeTimelineViewController.avatarButtonPressed(_:)), for: .touchUpInside) diff --git a/Mastodon/Vender/UIViewPreview.swift b/Mastodon/Vender/UIViewPreview.swift new file mode 100644 index 000000000..6494354e6 --- /dev/null +++ b/Mastodon/Vender/UIViewPreview.swift @@ -0,0 +1,49 @@ +// https://github.com/bielikb/UIViewPreview/blob/master/Sources/UIViewPreview/UIViewPreview.swift + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +public struct UIViewPreview: UIViewRepresentable { + public let view: View + public let width: CGFloat? + public init(width: CGFloat? = nil, _ builder: @escaping () -> View) { + self.view = builder() + self.width = width + } + // MARK: - UIViewRepresentable + public func makeUIView(context: Context) -> UIView { + return view + } + public func updateUIView(_ view: UIView, context: Context) { + view.translatesAutoresizingMaskIntoConstraints = false + view.setContentHuggingPriority(.defaultHigh, for: .horizontal) + view.setContentHuggingPriority(.defaultHigh, for: .vertical) + + if let width = width { + NSLayoutConstraint.activate([ + view.widthAnchor.constraint(equalToConstant: width), + ]) + } + } +} + +public struct UIViewControllerPreview: UIViewControllerRepresentable { + public let viewController: ViewController + + public init(_ builder: @escaping () -> ViewController) { + viewController = builder() + } + + // MARK: - UIViewControllerRepresentable + public func makeUIViewController(context: Context) -> ViewController { + viewController + } + + @available(iOS 13.0, tvOS 13.0, *) + @available(OSX, unavailable) + @available(watchOS, unavailable) + public func updateUIViewController(_ uiViewController: ViewController, context: UIViewControllerRepresentableContext>) { + return + } +} +#endif