diff --git a/Localization/app.json b/Localization/app.json index 120458f74..facda97cf 100644 --- a/Localization/app.json +++ b/Localization/app.json @@ -22,6 +22,11 @@ "publish_post_failure": { "title": "Publish Failure", "message": "Failed to publish the post.\nPlease check your internet connection." + }, + "sign_out": { + "title": "Sign out", + "message": "Are you sure you want to sign out?", + "confirm": "Sign Out" } }, "controls": { @@ -321,6 +326,41 @@ }, "favorite": { "title": "Your Favorites" + }, + "settings": { + "title": "Settings", + "section": { + "appearance": { + "title": "Appearance", + "automatic": "Automatic", + "light": "Always Light", + "dark": "Always Dark" + }, + "notifications": { + "title": "Notifications", + "favorites": "Favorites my post", + "follows": "Follows me", + "boosts": "Reblogs my post", + "mentions": "Mentions me", + "trigger": { + "anyone": "anyone", + "follower": "a follower", + "follow": "anyone I follow", + "noone": "no one", + "title": "Notify me when" + } + }, + "boringzone": { + "title": "The Boring zone", + "terms": "Terms of Service", + "privacy": "Privacy Policy" + }, + "spicyzone": { + "title": "The spicy zone", + "clear": "Clear Media Cache", + "signout": "Sign Out" + } + } } } } diff --git a/Mastodon/Coordinator/SceneCoordinator.swift b/Mastodon/Coordinator/SceneCoordinator.swift index 36d42745e..28064ac38 100644 --- a/Mastodon/Coordinator/SceneCoordinator.swift +++ b/Mastodon/Coordinator/SceneCoordinator.swift @@ -65,6 +65,7 @@ extension SceneCoordinator { #if DEBUG case publicTimeline + case settings #endif var isOnboarding: Bool { @@ -262,6 +263,10 @@ private extension SceneCoordinator { let _viewController = PublicTimelineViewController() _viewController.viewModel = PublicTimelineViewModel(context: appContext) viewController = _viewController + case .settings: + let _viewController = SettingsViewController() + _viewController.viewModel = SettingsViewModel(context: appContext, coordinator: self) + viewController = _viewController #endif } diff --git a/Mastodon/Generated/Strings.swift b/Mastodon/Generated/Strings.swift index dac01a4b3..4b2175e86 100644 --- a/Mastodon/Generated/Strings.swift +++ b/Mastodon/Generated/Strings.swift @@ -35,6 +35,14 @@ internal enum L10n { /// Server Error internal static let title = L10n.tr("Localizable", "Common.Alerts.ServerError.Title") } + internal enum SignOut { + /// Sign Out + internal static let confirm = L10n.tr("Localizable", "Common.Alerts.SignOut.Confirm") + /// Are you sure you want to sign out? + internal static let message = L10n.tr("Localizable", "Common.Alerts.SignOut.Message") + /// Sign out + internal static let title = L10n.tr("Localizable", "Common.Alerts.SignOut.Title") + } internal enum SignUpFailure { /// Sign Up Failure internal static let title = L10n.tr("Localizable", "Common.Alerts.SignUpFailure.Title") @@ -595,16 +603,16 @@ internal enum L10n { /// Appearance internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Appearance.Title") } - internal enum BoringZone { + internal enum Boringzone { /// Privacy Policy - internal static let privacy = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.Privacy") + internal static let privacy = L10n.tr("Localizable", "Scene.Settings.Section.Boringzone.Privacy") /// Terms of Service - internal static let terms = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.Terms") + internal static let terms = L10n.tr("Localizable", "Scene.Settings.Section.Boringzone.Terms") /// The Boring zone - internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.BoringZone.Title") + internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Boringzone.Title") } internal enum Notifications { - /// Boosts my post + /// Reblogs my post internal static let boosts = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Boosts") /// Favorites my post internal static let favorites = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Favorites") @@ -622,18 +630,18 @@ internal enum L10n { /// a follower internal static let follower = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Trigger.Follower") /// no one - internal static let noOne = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Trigger.NoOne") + internal static let noone = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Trigger.Noone") /// Notify me when internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Notifications.Trigger.Title") } } - internal enum SpicyZone { + internal enum Spicyzone { /// Clear Media Cache - internal static let clear = L10n.tr("Localizable", "Scene.Settings.Section.SpicyZone.Clear") + internal static let clear = L10n.tr("Localizable", "Scene.Settings.Section.Spicyzone.Clear") /// Sign Out - internal static let signOut = L10n.tr("Localizable", "Scene.Settings.Section.SpicyZone.SignOut") + internal static let signout = L10n.tr("Localizable", "Scene.Settings.Section.Spicyzone.Signout") /// The spicy zone - internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.SpicyZone.Title") + internal static let title = L10n.tr("Localizable", "Scene.Settings.Section.Spicyzone.Title") } } } diff --git a/Mastodon/Resources/en.lproj/Localizable.strings b/Mastodon/Resources/en.lproj/Localizable.strings index 6abcb4cfe..d584be424 100644 --- a/Mastodon/Resources/en.lproj/Localizable.strings +++ b/Mastodon/Resources/en.lproj/Localizable.strings @@ -6,6 +6,9 @@ Please check your internet connection."; "Common.Alerts.PublishPostFailure.Title" = "Publish Failure"; "Common.Alerts.ServerError.Title" = "Server Error"; +"Common.Alerts.SignOut.Confirm" = "Sign Out"; +"Common.Alerts.SignOut.Message" = "Are you sure you want to sign out?"; +"Common.Alerts.SignOut.Title" = "Sign out"; "Common.Alerts.SignUpFailure.Title" = "Sign Up Failure"; "Common.Alerts.VoteFailure.PollExpired" = "The poll has expired"; "Common.Alerts.VoteFailure.Title" = "Vote Failure"; @@ -186,26 +189,26 @@ any server."; "Scene.ServerRules.Subtitle" = "These rules are set by the admins of %@."; "Scene.ServerRules.TermsOfService" = "terms of service"; "Scene.ServerRules.Title" = "Some ground rules."; -"Scene.Welcome.Slogan" = "Social networking -back in your hands."; -"Scene.Settings.Title" = "Settings"; -"Scene.Settings.Section.Appearance.Title" = "Appearance"; "Scene.Settings.Section.Appearance.Automatic" = "Automatic"; -"Scene.Settings.Section.Appearance.Light" = "Always Light"; "Scene.Settings.Section.Appearance.Dark" = "Always Dark"; -"Scene.Settings.Section.Notifications.Title" = "Notifications"; +"Scene.Settings.Section.Appearance.Light" = "Always Light"; +"Scene.Settings.Section.Appearance.Title" = "Appearance"; +"Scene.Settings.Section.Boringzone.Privacy" = "Privacy Policy"; +"Scene.Settings.Section.Boringzone.Terms" = "Terms of Service"; +"Scene.Settings.Section.Boringzone.Title" = "The Boring zone"; +"Scene.Settings.Section.Notifications.Boosts" = "Reblogs my post"; "Scene.Settings.Section.Notifications.Favorites" = "Favorites my post"; "Scene.Settings.Section.Notifications.Follows" = "Follows me"; -"Scene.Settings.Section.Notifications.Boosts" = "Boosts my post"; "Scene.Settings.Section.Notifications.Mentions" = "Mentions me"; +"Scene.Settings.Section.Notifications.Title" = "Notifications"; "Scene.Settings.Section.Notifications.Trigger.Anyone" = "anyone"; -"Scene.Settings.Section.Notifications.Trigger.Follower" = "a follower"; "Scene.Settings.Section.Notifications.Trigger.Follow" = "anyone I follow"; -"Scene.Settings.Section.Notifications.Trigger.NoOne" = "no one"; +"Scene.Settings.Section.Notifications.Trigger.Follower" = "a follower"; +"Scene.Settings.Section.Notifications.Trigger.Noone" = "no one"; "Scene.Settings.Section.Notifications.Trigger.Title" = "Notify me when"; -"Scene.Settings.Section.BoringZone.Title" = "The Boring zone"; -"Scene.Settings.Section.BoringZone.Terms" = "Terms of Service"; -"Scene.Settings.Section.BoringZone.Privacy" = "Privacy Policy"; -"Scene.Settings.Section.SpicyZone.Title" = "The spicy zone"; -"Scene.Settings.Section.SpicyZone.Clear" = "Clear Media Cache"; -"Scene.Settings.Section.SpicyZone.SignOut" = "Sign Out"; +"Scene.Settings.Section.Spicyzone.Clear" = "Clear Media Cache"; +"Scene.Settings.Section.Spicyzone.Signout" = "Sign Out"; +"Scene.Settings.Section.Spicyzone.Title" = "The spicy zone"; +"Scene.Settings.Title" = "Settings"; +"Scene.Welcome.Slogan" = "Social networking +back in your hands."; \ No newline at end of file diff --git a/Mastodon/Scene/Settings/SettingsViewController.swift b/Mastodon/Scene/Settings/SettingsViewController.swift index 73dc43df4..c134ade33 100644 --- a/Mastodon/Scene/Settings/SettingsViewController.swift +++ b/Mastodon/Scene/Settings/SettingsViewController.swift @@ -26,7 +26,7 @@ class SettingsViewController: UIViewController, NeedsDependency { let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone let follower = L10n.Scene.Settings.Section.Notifications.Trigger.follower let follow = L10n.Scene.Settings.Section.Notifications.Trigger.follow - let noOne = L10n.Scene.Settings.Section.Notifications.Trigger.noOne + let noOne = L10n.Scene.Settings.Section.Notifications.Trigger.noone let menu = UIMenu( image: nil, identifier: nil, @@ -206,11 +206,32 @@ class SettingsViewController: UIViewController, NeedsDependency { tableView.tableFooterView = footerView } + func alertToSignout() { + let alertController = UIAlertController( + title: L10n.Common.Alerts.SignOut.title, + message: L10n.Common.Alerts.SignOut.message, + preferredStyle: .alert + ) + + let cancelAction = UIAlertAction(title: L10n.Common.Controls.Actions.cancel, style: .cancel, handler: nil) + let signOutAction = UIAlertAction(title: L10n.Common.Alerts.SignOut.confirm, style: .destructive) { [weak self] _ in + guard let self = self else { return } + self.signout() + } + alertController.addAction(cancelAction) + alertController.addAction(signOutAction) + self.coordinator.present( + scene: .alertController(alertController: alertController), + from: self, + transition: .alertController(animated: true, completion: nil) + ) + } + func signout() { guard let activeMastodonAuthenticationBox = context.authenticationService.activeMastodonAuthenticationBox.value else { return } - + context.authenticationService.signOutMastodonUser( domain: activeMastodonAuthenticationBox.domain, userID: activeMastodonAuthenticationBox.userID @@ -282,9 +303,10 @@ extension SettingsViewController: UITableViewDelegate { if indexPath.section == 2 { coordinator.present( - scene: .webview(url: URL(string: "https://mastodon.online/terms")!), + scene: .safari(url: URL(string: "https://mastodon.online/terms")!), from: self, - transition: .modal(animated: true, completion: nil)) + transition: .safariPresent(animated: true, completion: nil) + ) } // iTODO: clear media cache @@ -292,7 +314,7 @@ extension SettingsViewController: UITableViewDelegate { // logout if indexPath.section == 3, indexPath.row == 1 { - signout() + alertToSignout() } } } @@ -377,9 +399,10 @@ extension SettingsViewController: SettingsToggleCellDelegate { extension SettingsViewController: ActiveLabelDelegate { func activeLabel(_ activeLabel: ActiveLabel, didSelectActiveEntity entity: ActiveEntity) { coordinator.present( - scene: .webview(url: URL(string: "https://github.com/tootsuite/mastodon")!), + scene: .safari(url: URL(string: "https://github.com/tootsuite/mastodon")!), from: self, - transition: .modal(animated: true, completion: nil)) + transition: .safariPresent(animated: true, completion: nil) + ) } } diff --git a/Mastodon/Scene/Settings/SettingsViewModel.swift b/Mastodon/Scene/Settings/SettingsViewModel.swift index 6a817ffff..0d625c0ed 100644 --- a/Mastodon/Scene/Settings/SettingsViewModel.swift +++ b/Mastodon/Scene/Settings/SettingsViewModel.swift @@ -63,7 +63,7 @@ class SettingsViewModel: NSObject, NeedsDependency { let anyone = L10n.Scene.Settings.Section.Notifications.Trigger.anyone let follower = L10n.Scene.Settings.Section.Notifications.Trigger.follower let follow = L10n.Scene.Settings.Section.Notifications.Trigger.follow - let noOne = L10n.Scene.Settings.Section.Notifications.Trigger.noOne + let noOne = L10n.Scene.Settings.Section.Notifications.Trigger.noone return [anyone: anyoneSwitchItems, follower: followerSwitchItems, follow: followSwitchItems, @@ -229,26 +229,26 @@ class SettingsViewModel: NSObject, NeedsDependency { snapshot.appendItems(notificationItems) // boring zone - let boringLinks = [L10n.Scene.Settings.Section.BoringZone.terms, - L10n.Scene.Settings.Section.BoringZone.privacy] + let boringLinks = [L10n.Scene.Settings.Section.Boringzone.terms, + L10n.Scene.Settings.Section.Boringzone.privacy] var boringLinkItems = [SettingsItem]() for l in boringLinks { let item = SettingsItem.boringZone(item: SettingsItem.Link(title: l, color: .systemBlue)) boringLinkItems.append(item) } - let boringSection = SettingsSection.boringZone(title: L10n.Scene.Settings.Section.BoringZone.title, items: boringLinkItems) + let boringSection = SettingsSection.boringZone(title: L10n.Scene.Settings.Section.Boringzone.title, items: boringLinkItems) snapshot.appendSections([boringSection]) snapshot.appendItems(boringLinkItems) // spicy zone - let spicyLinks = [L10n.Scene.Settings.Section.SpicyZone.clear, - L10n.Scene.Settings.Section.SpicyZone.signOut] + let spicyLinks = [L10n.Scene.Settings.Section.Spicyzone.clear, + L10n.Scene.Settings.Section.Spicyzone.signout] var spicyLinkItems = [SettingsItem]() for l in spicyLinks { let item = SettingsItem.boringZone(item: SettingsItem.Link(title: l, color: .systemRed)) spicyLinkItems.append(item) } - let spicySection = SettingsSection.boringZone(title: L10n.Scene.Settings.Section.SpicyZone.title, items: spicyLinkItems) + let spicySection = SettingsSection.boringZone(title: L10n.Scene.Settings.Section.Spicyzone.title, items: spicyLinkItems) snapshot.appendSections([spicySection]) snapshot.appendItems(spicyLinkItems) diff --git a/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift b/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift index 374e05d6d..b35b2b50f 100644 --- a/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift +++ b/Mastodon/Scene/Settings/View/Cell/SettingsToggleTableViewCell.swift @@ -14,7 +14,6 @@ protocol SettingsToggleCellDelegate: class { class SettingsToggleTableViewCell: UITableViewCell { lazy var switchButton: UISwitch = { let view = UISwitch(frame:.zero) - view.translatesAutoresizingMaskIntoConstraints = false return view }() @@ -48,13 +47,7 @@ class SettingsToggleTableViewCell: UITableViewCell { // MARK: Private methods private func setupUI() { selectionStyle = .none - textLabel?.font = .systemFont(ofSize: 17, weight: .regular) - contentView.addSubview(switchButton) - - NSLayoutConstraint.activate([ - switchButton.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor), - switchButton.centerYAnchor.constraint(equalTo: contentView.centerYAnchor), - ]) + accessoryView = switchButton switchButton.addTarget(self, action: #selector(valueDidChange(sender:)), for: .valueChanged) } diff --git a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Push.swift b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Push.swift index 3618b06c5..ef383e4db 100644 --- a/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Push.swift +++ b/MastodonSDK/Sources/MastodonSDK/API/Mastodon+API+Push.swift @@ -32,7 +32,7 @@ extension Mastodon.API.Notification { public static func subscription( session: URLSession, domain: String, - authorization: Mastodon.API.OAuth.Authorization? + authorization: Mastodon.API.OAuth.Authorization ) -> AnyPublisher, Error> { let request = Mastodon.API.get( url: pushEndpointURL(domain: domain), @@ -65,7 +65,7 @@ extension Mastodon.API.Notification { public static func createSubscription( session: URLSession, domain: String, - authorization: Mastodon.API.OAuth.Authorization?, + authorization: Mastodon.API.OAuth.Authorization, query: CreateSubscriptionQuery ) -> AnyPublisher, Error> { let request = Mastodon.API.post( @@ -99,7 +99,7 @@ extension Mastodon.API.Notification { public static func updateSubscription( session: URLSession, domain: String, - authorization: Mastodon.API.OAuth.Authorization?, + authorization: Mastodon.API.OAuth.Authorization, query: UpdateSubscriptionQuery ) -> AnyPublisher, Error> { let request = Mastodon.API.put( @@ -117,10 +117,44 @@ extension Mastodon.API.Notification { } extension Mastodon.API.Notification { - public struct CreateSubscriptionQuery: PostQuery { - var queryItems: [URLQueryItem]? - var contentType: String? - var body: Data? + public struct CreateSubscriptionQuery: Codable, PostQuery { + let endpoint: String + let p256dh: String + let auth: String + let favourite: Bool? + let follow: Bool? + let reblog: Bool? + let mention: Bool? + let poll: Bool? + + var queryItems: [URLQueryItem]? { + var items = [URLQueryItem]() + + items.append(URLQueryItem(name: "subscription[endpoint]", value: endpoint)) + items.append(URLQueryItem(name: "subscription[keys][p256dh]", value: p256dh)) + items.append(URLQueryItem(name: "subscription[keys][auth]", value: auth)) + + if let followValue = follow?.queryItemValue { + let followItem = URLQueryItem(name: "data[alerts][follow]", value: followValue) + items.append(followItem) + } + + if let favouriteValue = favourite?.queryItemValue { + let favouriteItem = URLQueryItem(name: "data[alerts][favourite]", value: favouriteValue) + items.append(favouriteItem) + } + + if let reblogValue = reblog?.queryItemValue { + let reblogItem = URLQueryItem(name: "data[alerts][reblog]", value: reblogValue) + items.append(reblogItem) + } + + if let mentionValue = mention?.queryItemValue { + let mentionItem = URLQueryItem(name: "data[alerts][mention]", value: mentionValue) + items.append(mentionItem) + } + return items + } public init( endpoint: String, @@ -132,38 +166,48 @@ extension Mastodon.API.Notification { mention: Bool?, poll: Bool? ) { - queryItems = [URLQueryItem]() - - queryItems?.append(URLQueryItem(name: "subscription[endpoint]", value: endpoint)) - queryItems?.append(URLQueryItem(name: "subscription[keys][p256dh]", value: p256dh)) - queryItems?.append(URLQueryItem(name: "subscription[keys][auth]", value: auth)) + self.endpoint = endpoint + self.p256dh = p256dh + self.auth = auth + self.favourite = favourite + self.follow = follow + self.reblog = reblog + self.mention = mention + self.poll = poll + } + } + + public struct UpdateSubscriptionQuery: Codable, PutQuery { + let favourite: Bool? + let follow: Bool? + let reblog: Bool? + let mention: Bool? + let poll: Bool? + + var queryItems: [URLQueryItem]? { + var items = [URLQueryItem]() if let followValue = follow?.queryItemValue { let followItem = URLQueryItem(name: "data[alerts][follow]", value: followValue) - queryItems?.append(followItem) + items.append(followItem) } if let favouriteValue = favourite?.queryItemValue { let favouriteItem = URLQueryItem(name: "data[alerts][favourite]", value: favouriteValue) - queryItems?.append(favouriteItem) + items.append(favouriteItem) } if let reblogValue = reblog?.queryItemValue { let reblogItem = URLQueryItem(name: "data[alerts][reblog]", value: reblogValue) - queryItems?.append(reblogItem) + items.append(reblogItem) } if let mentionValue = mention?.queryItemValue { let mentionItem = URLQueryItem(name: "data[alerts][mention]", value: mentionValue) - queryItems?.append(mentionItem) + items.append(mentionItem) } + return items } - } - - public struct UpdateSubscriptionQuery: PutQuery { - var queryItems: [URLQueryItem]? - var contentType: String? - var body: Data? public init( favourite: Bool?, @@ -172,27 +216,11 @@ extension Mastodon.API.Notification { mention: Bool?, poll: Bool? ) { - queryItems = [URLQueryItem]() - - if let followValue = follow?.queryItemValue { - let followItem = URLQueryItem(name: "data[alerts][follow]", value: followValue) - queryItems?.append(followItem) - } - - if let favouriteValue = favourite?.queryItemValue { - let favouriteItem = URLQueryItem(name: "data[alerts][favourite]", value: favouriteValue) - queryItems?.append(favouriteItem) - } - - if let reblogValue = reblog?.queryItemValue { - let reblogItem = URLQueryItem(name: "data[alerts][reblog]", value: reblogValue) - queryItems?.append(reblogItem) - } - - if let mentionValue = mention?.queryItemValue { - let mentionItem = URLQueryItem(name: "data[alerts][mention]", value: mentionValue) - queryItems?.append(mentionItem) - } + self.favourite = favourite + self.follow = follow + self.reblog = reblog + self.mention = mention + self.poll = poll } } }