Merge pull request #80 from tootsuite/fix/serverRulesUI
fix: Update server rules scene UI design
This commit is contained in:
commit
fa14477f7d
|
@ -162,6 +162,8 @@
|
|||
"title": "Some ground rules.",
|
||||
"subtitle": "These rules are set by the admins of %s.",
|
||||
"prompt": "By continuing, you're subject to the terms of service and privacy policy for %s.",
|
||||
"terms_of_service": "terms of service",
|
||||
"privacy_policy": "privacy policy",
|
||||
"button": {
|
||||
"confirm": "I Agree"
|
||||
}
|
||||
|
|
|
@ -101,6 +101,8 @@
|
|||
2DF75BB925D1474100694EC8 /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BB825D1474100694EC8 /* ManagedObjectObserver.swift */; };
|
||||
2DF75BC725D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DF75BC625D1475D00694EC8 /* ManagedObjectContextObjectsDidChange.swift */; };
|
||||
2DFF41892614A4DC00F776A4 /* UIView+Constraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2DFF41882614A4DC00F776A4 /* UIView+Constraint.swift */; };
|
||||
5D0393902612D259007FE196 /* WebViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D03938F2612D259007FE196 /* WebViewController.swift */; };
|
||||
5D0393962612D266007FE196 /* WebViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5D0393952612D266007FE196 /* WebViewModel.swift */; };
|
||||
5D526FE225BE9AC400460CB9 /* MastodonSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 5D526FE125BE9AC400460CB9 /* MastodonSDK */; };
|
||||
5DF1054125F886D400D6C0D4 /* ViedeoPlaybackService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */; };
|
||||
5DF1054725F8870E00D6C0D4 /* VideoPlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DF1054625F8870E00D6C0D4 /* VideoPlayerViewModel.swift */; };
|
||||
|
@ -410,6 +412,8 @@
|
|||
3C030226D3C73DCC23D67452 /* Pods_Mastodon_MastodonUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Mastodon_MastodonUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
452147B2903DF38070FE56A2 /* Pods_MastodonTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MastodonTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
459EA4F43058CAB47719E963 /* Pods-Mastodon-MastodonUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Mastodon-MastodonUITests.debug.xcconfig"; path = "Target Support Files/Pods-Mastodon-MastodonUITests/Pods-Mastodon-MastodonUITests.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
5D03938F2612D259007FE196 /* WebViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewController.swift; sourceTree = "<group>"; };
|
||||
5D0393952612D266007FE196 /* WebViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewModel.swift; sourceTree = "<group>"; };
|
||||
5DF1054025F886D400D6C0D4 /* ViedeoPlaybackService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViedeoPlaybackService.swift; sourceTree = "<group>"; };
|
||||
5DF1054625F8870E00D6C0D4 /* VideoPlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoPlayerViewModel.swift; sourceTree = "<group>"; };
|
||||
5DF1056325F887CB00D6C0D4 /* AVPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVPlayer.swift; sourceTree = "<group>"; };
|
||||
|
@ -951,6 +955,15 @@
|
|||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5D03938E2612D200007FE196 /* Webview */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D03938F2612D259007FE196 /* WebViewController.swift */,
|
||||
5D0393952612D266007FE196 /* WebViewModel.swift */,
|
||||
);
|
||||
path = Webview;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
DB01409B25C40BB600F9F3CF /* Onboarding */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -1328,6 +1341,7 @@
|
|||
DB8AF55525C1379F002E6C99 /* Scene */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5D03938E2612D200007FE196 /* Webview */,
|
||||
2D7631A425C1532200929FB9 /* Share */,
|
||||
DB8AF54E25C13703002E6C99 /* MainTab */,
|
||||
DB01409B25C40BB600F9F3CF /* Onboarding */,
|
||||
|
@ -1860,6 +1874,7 @@
|
|||
DB447681260B3ED600B66B82 /* CustomEmojiPickerSection.swift in Sources */,
|
||||
0FB3D33225E5F50E00AAD544 /* PickServerSearchCell.swift in Sources */,
|
||||
DB71FD5225F8CCAA00512AE1 /* APIService+Status.swift in Sources */,
|
||||
5D0393962612D266007FE196 /* WebViewModel.swift in Sources */,
|
||||
2D5A3D3825CF8D9F002347D6 /* ScrollViewContainer.swift in Sources */,
|
||||
DB1E347825F519300079D7DF /* PickServerItem.swift in Sources */,
|
||||
DB1FD45A25F27898004CFCFC /* CategoryPickerItem.swift in Sources */,
|
||||
|
@ -1939,6 +1954,7 @@
|
|||
2D3F9E0425DFA133004262D9 /* UITapGestureRecognizer.swift in Sources */,
|
||||
2D694A7425F9EB4E0038ADDC /* ContentWarningOverlayView.swift in Sources */,
|
||||
DB9A486C26032AC1008B817C /* AttachmentContainerView+EmptyStateView.swift in Sources */,
|
||||
5D0393902612D259007FE196 /* WebViewController.swift in Sources */,
|
||||
DB4481CC25EE2AFE00BEFB67 /* PollItem.swift in Sources */,
|
||||
DB44767B260B3B8C00B66B82 /* CustomEmojiPickerInputView.swift in Sources */,
|
||||
DB4563BD25E11A24004DA0B9 /* KeyboardResponderService.swift in Sources */,
|
||||
|
|
|
@ -46,6 +46,7 @@ extension SceneCoordinator {
|
|||
case mastodonServerRules(viewModel: MastodonServerRulesViewModel)
|
||||
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
|
||||
case mastodonResendEmail(viewModel: MastodonResendEmailViewModel)
|
||||
case mastodonWebView(viewModel:WebViewModel)
|
||||
|
||||
// compose
|
||||
case compose(viewModel: ComposeViewModel)
|
||||
|
@ -193,6 +194,10 @@ private extension SceneCoordinator {
|
|||
let _viewController = MastodonResendEmailViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .mastodonWebView(let viewModel):
|
||||
let _viewController = WebViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
viewController = _viewController
|
||||
case .compose(let viewModel):
|
||||
let _viewController = ComposeViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
|
|
|
@ -410,6 +410,8 @@ internal enum L10n {
|
|||
}
|
||||
}
|
||||
internal enum ServerRules {
|
||||
/// privacy policy
|
||||
internal static let privacyPolicy = L10n.tr("Localizable", "Scene.ServerRules.PrivacyPolicy")
|
||||
/// 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))
|
||||
|
@ -418,6 +420,8 @@ internal enum L10n {
|
|||
internal static func subtitle(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.ServerRules.Subtitle", String(describing: p1))
|
||||
}
|
||||
/// terms of service
|
||||
internal static let termsOfService = L10n.tr("Localizable", "Scene.ServerRules.TermsOfService")
|
||||
/// Some ground rules.
|
||||
internal static let title = L10n.tr("Localizable", "Scene.ServerRules.Title")
|
||||
internal enum Button {
|
||||
|
|
|
@ -129,8 +129,10 @@ tap the link to confirm your account.";
|
|||
"Scene.ServerPicker.Title" = "Pick a Server,
|
||||
any server.";
|
||||
"Scene.ServerRules.Button.Confirm" = "I Agree";
|
||||
"Scene.ServerRules.PrivacyPolicy" = "privacy policy";
|
||||
"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.TermsOfService" = "terms of service";
|
||||
"Scene.ServerRules.Title" = "Some ground rules.";
|
||||
"Scene.Welcome.Slogan" = "Social networking
|
||||
back in your hands.";
|
|
@ -8,6 +8,8 @@
|
|||
import os.log
|
||||
import UIKit
|
||||
import Combine
|
||||
import MastodonSDK
|
||||
import SafariServices
|
||||
|
||||
final class MastodonServerRulesViewController: UIViewController, NeedsDependency {
|
||||
|
||||
|
@ -44,19 +46,20 @@ final class MastodonServerRulesViewController: UIViewController, NeedsDependency
|
|||
return label
|
||||
}()
|
||||
|
||||
let bottonContainerView: UIView = {
|
||||
let bottomContainerView: 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
|
||||
private(set) lazy var bottomPromptTextView: UITextView = {
|
||||
let textView = UITextView()
|
||||
textView.font = .preferredFont(forTextStyle: .body)
|
||||
textView.textColor = .label
|
||||
textView.isSelectable = true
|
||||
textView.isEditable = false
|
||||
textView.backgroundColor = Asset.Colors.Background.onboardingBackground.color
|
||||
return textView
|
||||
}()
|
||||
|
||||
let confirmButton: PrimaryActionButton = {
|
||||
|
@ -86,36 +89,39 @@ extension MastodonServerRulesViewController {
|
|||
super.viewDidLoad()
|
||||
|
||||
setupOnboardingAppearance()
|
||||
configTextView()
|
||||
|
||||
defer { setupNavigationBarBackgroundView() }
|
||||
|
||||
bottonContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(bottonContainerView)
|
||||
bottomContainerView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(bottomContainerView)
|
||||
NSLayoutConstraint.activate([
|
||||
view.bottomAnchor.constraint(equalTo: bottonContainerView.bottomAnchor),
|
||||
bottonContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
bottonContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
view.bottomAnchor.constraint(equalTo: bottomContainerView.bottomAnchor),
|
||||
bottomContainerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
bottomContainerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
])
|
||||
bottonContainerView.preservesSuperviewLayoutMargins = true
|
||||
bottomContainerView.preservesSuperviewLayoutMargins = true
|
||||
defer {
|
||||
view.bringSubviewToFront(bottonContainerView)
|
||||
view.bringSubviewToFront(bottomContainerView)
|
||||
}
|
||||
|
||||
confirmButton.translatesAutoresizingMaskIntoConstraints = false
|
||||
bottonContainerView.addSubview(confirmButton)
|
||||
bottomContainerView.addSubview(confirmButton)
|
||||
NSLayoutConstraint.activate([
|
||||
bottonContainerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: confirmButton.bottomAnchor, constant: MastodonServerRulesViewController.viewBottomPaddingHeight),
|
||||
confirmButton.leadingAnchor.constraint(equalTo: bottonContainerView.readableContentGuide.leadingAnchor, constant: MastodonServerRulesViewController.actionButtonMargin),
|
||||
bottonContainerView.readableContentGuide.trailingAnchor.constraint(equalTo: confirmButton.trailingAnchor, constant: MastodonServerRulesViewController.actionButtonMargin),
|
||||
bottomContainerView.layoutMarginsGuide.bottomAnchor.constraint(equalTo: confirmButton.bottomAnchor, constant: MastodonServerRulesViewController.viewBottomPaddingHeight),
|
||||
confirmButton.leadingAnchor.constraint(equalTo: bottomContainerView.readableContentGuide.leadingAnchor, constant: MastodonServerRulesViewController.actionButtonMargin),
|
||||
bottomContainerView.readableContentGuide.trailingAnchor.constraint(equalTo: confirmButton.trailingAnchor, constant: MastodonServerRulesViewController.actionButtonMargin),
|
||||
confirmButton.heightAnchor.constraint(equalToConstant: MastodonServerRulesViewController.actionButtonHeight).priority(.defaultHigh),
|
||||
])
|
||||
|
||||
bottomPromptLabel.translatesAutoresizingMaskIntoConstraints = false
|
||||
bottonContainerView.addSubview(bottomPromptLabel)
|
||||
bottomPromptTextView.translatesAutoresizingMaskIntoConstraints = false
|
||||
bottomContainerView.addSubview(bottomPromptTextView)
|
||||
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),
|
||||
bottomPromptTextView.frameLayoutGuide.topAnchor.constraint(equalTo: bottomContainerView.topAnchor, constant: 20),
|
||||
bottomPromptTextView.frameLayoutGuide.leadingAnchor.constraint(equalTo: bottomContainerView.readableContentGuide.leadingAnchor),
|
||||
bottomPromptTextView.frameLayoutGuide.trailingAnchor.constraint(equalTo: bottomContainerView.readableContentGuide.trailingAnchor),
|
||||
bottomPromptTextView.frameLayoutGuide.heightAnchor.constraint(equalToConstant: 50),
|
||||
confirmButton.topAnchor.constraint(equalTo: bottomPromptTextView.frameLayoutGuide.bottomAnchor, constant: 20),
|
||||
])
|
||||
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
@ -165,8 +171,32 @@ extension MastodonServerRulesViewController {
|
|||
extension MastodonServerRulesViewController {
|
||||
func updateScrollViewContentInset() {
|
||||
view.layoutIfNeeded()
|
||||
scrollView.contentInset.bottom = bottonContainerView.frame.height
|
||||
scrollView.verticalScrollIndicatorInsets.bottom = bottonContainerView.frame.height
|
||||
scrollView.contentInset.bottom = bottomContainerView.frame.height
|
||||
scrollView.verticalScrollIndicatorInsets.bottom = bottomContainerView.frame.height
|
||||
}
|
||||
|
||||
func configTextView() {
|
||||
let linkColor = Asset.Colors.Button.normal.color
|
||||
|
||||
let str = NSString(string: L10n.Scene.ServerRules.prompt(viewModel.domain))
|
||||
let termsOfServiceRange = str.range(of: L10n.Scene.ServerRules.termsOfService)
|
||||
let privacyRange = str.range(of: L10n.Scene.ServerRules.privacyPolicy)
|
||||
let attributeString = NSMutableAttributedString(string: L10n.Scene.ServerRules.prompt(viewModel.domain), attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .body), NSAttributedString.Key.foregroundColor: UIColor.label])
|
||||
attributeString.addAttribute(.link, value: Mastodon.API.serverRulesURL(domain: viewModel.domain), range: termsOfServiceRange)
|
||||
attributeString.addAttribute(.link, value: Mastodon.API.privacyURL(domain: viewModel.domain), range: privacyRange)
|
||||
let linkAttributes = [NSAttributedString.Key.foregroundColor:linkColor]
|
||||
bottomPromptTextView.attributedText = attributeString
|
||||
bottomPromptTextView.linkTextAttributes = linkAttributes
|
||||
bottomPromptTextView.delegate = self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension MastodonServerRulesViewController: UITextViewDelegate {
|
||||
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
|
||||
let safariVC = SFSafariViewController(url: URL)
|
||||
self.present(safariVC, animated: true, completion: nil)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,13 +35,16 @@ final class MastodonServerRulesViewModel {
|
|||
|
||||
var rulesAttributedString: NSAttributedString {
|
||||
let attributedString = NSMutableAttributedString(string: "\n")
|
||||
let configuration = UIImage.SymbolConfiguration(font: .preferredFont(forTextStyle: .title3))
|
||||
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)
|
||||
let imageName = String(i + 1) + ".circle.fill"
|
||||
let image = UIImage(systemName: imageName, withConfiguration: configuration)!
|
||||
let attachment = NSTextAttachment()
|
||||
attachment.image = image.withTintColor(.black)
|
||||
let imageAttribute = NSAttributedString(attachment: attachment)
|
||||
|
||||
let ruleString = NSAttributedString(string: " " + rule.text + "\n\n")
|
||||
attributedString.append(imageAttribute)
|
||||
attributedString.append(ruleString)
|
||||
}
|
||||
return attributedString
|
||||
|
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// WebViewController.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by xiaojian sun on 2021/3/30.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import os.log
|
||||
import UIKit
|
||||
import WebKit
|
||||
|
||||
final class WebViewController: UIViewController, NeedsDependency {
|
||||
|
||||
weak var context: AppContext! { willSet { precondition(!isViewLoaded) } }
|
||||
weak var coordinator: SceneCoordinator! { willSet { precondition(!isViewLoaded) } }
|
||||
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
var viewModel: WebViewModel!
|
||||
|
||||
let webView: WKWebView = {
|
||||
let configuration = WKWebViewConfiguration()
|
||||
configuration.processPool = WKProcessPool()
|
||||
let webView = WKWebView(frame: .zero, configuration: configuration)
|
||||
return webView
|
||||
}()
|
||||
|
||||
deinit {
|
||||
os_log(.info, log: .debug, "%{public}s[%{public}ld], %{public}s", (#file as NSString).lastPathComponent, #line, #function)
|
||||
|
||||
// cleanup cookie
|
||||
let httpCookieStore = webView.configuration.websiteDataStore.httpCookieStore
|
||||
httpCookieStore.getAllCookies { cookies in
|
||||
for cookie in cookies {
|
||||
httpCookieStore.delete(cookie, completionHandler: nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension WebViewController {
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(WebViewController.cancelBarButtonItemPressed(_:)))
|
||||
|
||||
webView.translatesAutoresizingMaskIntoConstraints = false
|
||||
view.addSubview(webView)
|
||||
NSLayoutConstraint.activate([
|
||||
webView.topAnchor.constraint(equalTo: view.topAnchor),
|
||||
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
|
||||
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
|
||||
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
|
||||
])
|
||||
|
||||
let request = URLRequest(url: viewModel.url)
|
||||
webView.load(request)
|
||||
}
|
||||
}
|
||||
|
||||
extension WebViewController {
|
||||
@objc private func cancelBarButtonItemPressed(_ sender: UIBarButtonItem) {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//
|
||||
// WebViewModel.swift
|
||||
// Mastodon
|
||||
//
|
||||
// Created by xiaojian sun on 2021/3/30.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class WebViewModel {
|
||||
public init(url: URL) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
// input
|
||||
let url: URL
|
||||
}
|
|
@ -89,6 +89,13 @@ extension Mastodon.API {
|
|||
return URL(string: "https://" + domain + "/auth/confirmation/new")!
|
||||
}
|
||||
|
||||
public static func serverRulesURL(domain: String) -> URL {
|
||||
return URL(string: "https://" + domain + "/about/more")!
|
||||
}
|
||||
|
||||
public static func privacyURL(domain: String) -> URL {
|
||||
return URL(string: "https://" + domain + "/terms")!
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.API {
|
||||
|
|
Loading…
Reference in New Issue