mirror of
https://github.com/mastodon/mastodon-ios
synced 2025-04-11 22:58:02 +02:00
Add Terms of Service to the onboarding flow
Fixes IOS-385
This commit is contained in:
parent
1421795eab
commit
fa207e3a44
@ -376,9 +376,13 @@
|
||||
"privacy": {
|
||||
"title": "Your Privacy",
|
||||
"description": "Although the Mastodon app does not collect any data, the server you sign up through may have a different policy.\n\nIf you disagree with the policy for **%s**, you can go back and pick a different server.",
|
||||
"terms_of_service_title": "Terms of Service",
|
||||
"terms_of_service": "Terms of Service - %s",
|
||||
"terms_of_service_description": "Please review the terms of service for **%s**. If you disagree, you can go back and pick a different server."
|
||||
"policy": {
|
||||
"ios": "Privacy Policy - Mastodon for iOS",
|
||||
"server": "Privacy Policy - %s"
|
||||
"server": "Privacy Policy - %s",
|
||||
"terms_of_service": "Terms of Service - %s"
|
||||
},
|
||||
"button": {
|
||||
"confirm": "I Agree"
|
||||
|
@ -376,9 +376,13 @@
|
||||
"privacy": {
|
||||
"title": "Your Privacy",
|
||||
"description": "Although the Mastodon app does not collect any data, the server you sign up through may have a different policy.\n\nIf you disagree with the policy for **%s**, you can go back and pick a different server.",
|
||||
"terms_of_service_title": "Terms of Service",
|
||||
"terms_of_service": "Terms of Service - %s",
|
||||
"terms_of_service_description": "Please review the terms of service for **%s**. If you disagree, you can go back and pick a different server."
|
||||
"policy": {
|
||||
"ios": "Privacy Policy - Mastodon for iOS",
|
||||
"server": "Privacy Policy - %s"
|
||||
"server": "Privacy Policy - %s",
|
||||
"terms_of_service": "Terms of Service - %s"
|
||||
},
|
||||
"button": {
|
||||
"confirm": "I Agree"
|
||||
|
@ -160,7 +160,7 @@ extension SceneCoordinator {
|
||||
case welcome
|
||||
case mastodonPickServer(viewMode: MastodonPickServerViewModel)
|
||||
case mastodonRegister(viewModel: MastodonRegisterViewModel)
|
||||
case mastodonPrivacyPolicies(viewModel: PrivacyViewModel)
|
||||
case mastodonPrivacyPolicies(viewModel: PolicyViewModel)
|
||||
case mastodonServerRules(viewModel: MastodonServerRulesView.ViewModel)
|
||||
case mastodonConfirmEmail(viewModel: MastodonConfirmEmailViewModel)
|
||||
case mastodonResendEmail(viewModel: MastodonResendEmailViewModel)
|
||||
@ -408,8 +408,8 @@ private extension SceneCoordinator {
|
||||
|
||||
viewController = loginViewController
|
||||
case .mastodonPrivacyPolicies(let viewModel):
|
||||
let privacyViewController = PrivacyTableViewController(coordinator: self, viewModel: viewModel)
|
||||
viewController = privacyViewController
|
||||
let policyViewController = PolicyTableViewController(coordinator: self, viewModel: viewModel)
|
||||
viewController = policyViewController
|
||||
case .mastodonResendEmail(let viewModel):
|
||||
let _viewController = MastodonResendEmailViewController()
|
||||
_viewController.viewModel = viewModel
|
||||
@ -651,11 +651,7 @@ extension SceneCoordinator: SettingsCoordinatorDelegate {
|
||||
|
||||
@MainActor
|
||||
func openPrivacyURL(_ settingsCoordinator: SettingsCoordinator) {
|
||||
guard let authenticationBox else { return }
|
||||
|
||||
let domain = authenticationBox.domain
|
||||
let privacyURL = Mastodon.API.privacyURL(domain: domain)
|
||||
|
||||
guard let privacyURL = URL(string: "https://joinmastodon.org/ios/privacy") else { return }
|
||||
_ = present(scene: .safari(url: privacyURL),
|
||||
from: settingsCoordinator.navigationController,
|
||||
transition: .safariPresent(animated: true))
|
||||
|
@ -11,37 +11,48 @@ import MastodonCore
|
||||
import MastodonSDK
|
||||
import MastodonLocalization
|
||||
import MastodonAsset
|
||||
import Combine
|
||||
|
||||
enum PrivacyRow {
|
||||
case iOSApp
|
||||
case server(domain: String)
|
||||
enum PolicyRow {
|
||||
case iosAppPrivacy
|
||||
case serverPrivacy(domain: String)
|
||||
case serverTermsOfService(domain: String, confirmedReachable: Bool)
|
||||
|
||||
var url: URL? {
|
||||
switch self {
|
||||
case .iOSApp:
|
||||
return URL(string: "https://joinmastodon.org/ios/privacy")
|
||||
case .server(let domain):
|
||||
return URL(string: "https://\(domain)/privacy-policy")
|
||||
case .iosAppPrivacy:
|
||||
return URL(string: "https://joinmastodon.org/ios/privacy")
|
||||
case .serverPrivacy(let domain):
|
||||
return URL(string: "https://\(domain)/privacy-policy")
|
||||
case .serverTermsOfService(let domain, _):
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/terms-of-service")
|
||||
}
|
||||
}
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .iOSApp:
|
||||
case .iosAppPrivacy:
|
||||
return L10n.Scene.Privacy.Policy.ios
|
||||
case .server(let domain):
|
||||
case .serverPrivacy(let domain):
|
||||
return L10n.Scene.Privacy.Policy.server(domain)
|
||||
case .serverTermsOfService(let domain, let fetched):
|
||||
if fetched {
|
||||
return L10n.Scene.Privacy.Policy.termsOfService(domain)
|
||||
} else {
|
||||
return "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PrivacyTableViewController: UIViewController {
|
||||
class PolicyTableViewController: UIViewController {
|
||||
|
||||
private let coordinator: SceneCoordinator
|
||||
private let tableView: UITableView
|
||||
let viewModel: PrivacyViewModel
|
||||
let viewModel: PolicyViewModel
|
||||
var disposeBag = Set<AnyCancellable>()
|
||||
|
||||
init(coordinator: SceneCoordinator, viewModel: PrivacyViewModel) {
|
||||
init(coordinator: SceneCoordinator, viewModel: PolicyViewModel) {
|
||||
self.coordinator = coordinator
|
||||
self.viewModel = viewModel
|
||||
|
||||
@ -58,9 +69,16 @@ class PrivacyTableViewController: UIViewController {
|
||||
view.addSubview(tableView)
|
||||
setupConstraints()
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: L10n.Scene.Privacy.Button.confirm, style: .done, target: self, action: #selector(PrivacyTableViewController.nextButtonPressed(_:)))
|
||||
|
||||
navigationItem.rightBarButtonItem = UIBarButtonItem(title: L10n.Scene.Privacy.Button.confirm, style: .done, target: self, action: #selector(PolicyTableViewController.nextButtonPressed(_:)))
|
||||
|
||||
title = L10n.Scene.Privacy.title
|
||||
|
||||
viewModel.$sections.receive(on: DispatchQueue.main)
|
||||
.sink { [weak self] newSections in
|
||||
self?.title = newSections.count > 1 ? L10n.Scene.Privacy.termsOfServiceTitle : L10n.Scene.Privacy.title
|
||||
self?.tableView.reloadData()
|
||||
}
|
||||
.store(in: &disposeBag)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) won't been implemented, please don't use Storyboards.") }
|
||||
@ -86,16 +104,33 @@ class PrivacyTableViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
extension PrivacyTableViewController: UITableViewDataSource {
|
||||
extension PolicyTableViewController: UITableViewDataSource {
|
||||
|
||||
private func rows(forSection sectionIndex: Int) -> [PolicyRow] {
|
||||
let section = viewModel.sections[sectionIndex]
|
||||
switch section {
|
||||
case .termsOfService(let rows), .privacy(let rows):
|
||||
return rows
|
||||
}
|
||||
}
|
||||
|
||||
private func row(at indexPath: IndexPath) -> PolicyRow {
|
||||
return rows(forSection: indexPath.section)[indexPath.row]
|
||||
}
|
||||
|
||||
func numberOfSections(in tableView: UITableView) -> Int {
|
||||
return viewModel.sections.count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
return viewModel.rows.count
|
||||
return rows(forSection: section).count
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
guard let cell = tableView.dequeueReusableCell(withIdentifier: PrivacyTableViewCell.reuseIdentifier, for: indexPath) as? PrivacyTableViewCell else { fatalError("Wrong cell?") }
|
||||
|
||||
let row = viewModel.rows[indexPath.row]
|
||||
|
||||
let row = row(at: indexPath)
|
||||
|
||||
var contentConfiguration = cell.defaultContentConfiguration()
|
||||
contentConfiguration.textProperties.color = Asset.Colors.Brand.blurple.color
|
||||
contentConfiguration.text = row.title
|
||||
@ -107,21 +142,24 @@ extension PrivacyTableViewController: UITableViewDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
extension PrivacyTableViewController: UITableViewDelegate {
|
||||
extension PolicyTableViewController: UITableViewDelegate {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
let row = viewModel.rows[indexPath.row]
|
||||
let row = row(at: indexPath)
|
||||
guard let url = row.url else { return }
|
||||
|
||||
_ = coordinator.present(scene: .safari(url: url), from: self, transition: .safariPresent(animated: true))
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||||
let sectionItem = viewModel.sections[section]
|
||||
|
||||
let wrapper = UIView()
|
||||
let controller = UIHostingController(
|
||||
rootView: HeaderTextView(
|
||||
text: LocalizedStringKey(L10n.Scene.Privacy.description(viewModel.domain))
|
||||
title: section == 0 ? nil : LocalizedStringKey(sectionItem.title),
|
||||
text: LocalizedStringKey(sectionItem.description(viewModel.domain) ?? "")
|
||||
)
|
||||
)
|
||||
guard let label = controller.view else { return nil }
|
||||
@ -141,15 +179,26 @@ extension PrivacyTableViewController: UITableViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
extension PrivacyTableViewController: OnboardingViewControllerAppearance { }
|
||||
extension PolicyTableViewController: OnboardingViewControllerAppearance { }
|
||||
|
||||
private struct HeaderTextView: View {
|
||||
let title: LocalizedStringKey?
|
||||
let text: LocalizedStringKey
|
||||
|
||||
var body: some View {
|
||||
Text(text)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundStyle(Asset.Colors.Label.primary.swiftUIColor)
|
||||
.padding(.bottom, 16)
|
||||
VStack(alignment: .leading) {
|
||||
if let title {
|
||||
Text(title)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundStyle(Asset.Colors.Label.primary.swiftUIColor)
|
||||
.font(.title)
|
||||
.padding(.bottom, 16)
|
||||
}
|
||||
Text(text)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
.foregroundStyle(Asset.Colors.Label.primary.swiftUIColor)
|
||||
.padding(.bottom, 16)
|
||||
.padding(.leading, 5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,13 +7,38 @@
|
||||
|
||||
import Foundation
|
||||
import MastodonSDK
|
||||
import MastodonLocalization
|
||||
import SwiftUICore
|
||||
|
||||
final class PrivacyViewModel {
|
||||
enum PolicySection {
|
||||
case termsOfService([PolicyRow])
|
||||
case privacy([PolicyRow])
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
case .termsOfService:
|
||||
return L10n.Scene.Privacy.termsOfServiceTitle
|
||||
case .privacy:
|
||||
return L10n.Scene.Privacy.title
|
||||
}
|
||||
}
|
||||
|
||||
func description(_ domain: String) -> String? {
|
||||
switch self {
|
||||
case .termsOfService:
|
||||
return L10n.Scene.Privacy.termsOfServiceDescription(domain)
|
||||
case .privacy:
|
||||
return L10n.Scene.Privacy.description(domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PolicyViewModel: ObservableObject {
|
||||
|
||||
// input
|
||||
let domain: String
|
||||
let authenticateInfo: AuthenticationViewModel.AuthenticateInfo
|
||||
let rows: [PrivacyRow]
|
||||
@Published var sections: [PolicySection]
|
||||
let instance: RegistrationInstance
|
||||
let applicationToken: Mastodon.Entity.Token
|
||||
let didAccept: ()->()
|
||||
@ -21,16 +46,53 @@ final class PrivacyViewModel {
|
||||
init(
|
||||
domain: String,
|
||||
authenticateInfo: AuthenticationViewModel.AuthenticateInfo,
|
||||
rows: [PrivacyRow],
|
||||
instance: RegistrationInstance,
|
||||
applicationToken: Mastodon.Entity.Token,
|
||||
didAccept: @escaping ()->()
|
||||
) {
|
||||
self.domain = domain
|
||||
self.authenticateInfo = authenticateInfo
|
||||
self.rows = rows
|
||||
self.instance = instance
|
||||
self.applicationToken = applicationToken
|
||||
self.didAccept = didAccept
|
||||
self.sections = [
|
||||
.termsOfService([.serverTermsOfService(domain: domain, confirmedReachable: false)]),
|
||||
.privacy([.iosAppPrivacy, .serverPrivacy(domain: domain)])
|
||||
]
|
||||
|
||||
checkForTermsOfService(domain)
|
||||
}
|
||||
|
||||
func checkForTermsOfService(_ domain: String) {
|
||||
guard let termsOfService = instance.termsOfService else { removeTermsOfServiceSection(); return }
|
||||
|
||||
var request = URLRequest(url: termsOfService)
|
||||
request.httpMethod = "HEAD"
|
||||
URLSession(configuration: .default)
|
||||
.dataTask(with: request) { (_, response, error) -> Void in
|
||||
guard error == nil else {
|
||||
self.removeTermsOfServiceSection()
|
||||
return
|
||||
}
|
||||
|
||||
guard (response as? HTTPURLResponse)?
|
||||
.statusCode == 200 else {
|
||||
self.removeTermsOfServiceSection()
|
||||
return
|
||||
}
|
||||
|
||||
self.sections = [
|
||||
.termsOfService([.serverTermsOfService(domain: domain, confirmedReachable: true)]),
|
||||
.privacy([.iosAppPrivacy, .serverPrivacy(domain: domain)])
|
||||
]
|
||||
}
|
||||
.resume()
|
||||
|
||||
}
|
||||
|
||||
func removeTermsOfServiceSection() {
|
||||
sections = [
|
||||
.privacy([.iosAppPrivacy, .serverPrivacy(domain: domain)])
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -513,6 +513,8 @@ protocol RegistrationInstance {
|
||||
var isBeyondVersion1: Bool { get }
|
||||
var isOpenToNewRegistrations: Bool? { get }
|
||||
var rules: [Mastodon.Entity.Instance.Rule]? { get }
|
||||
var termsOfService: URL? { get }
|
||||
var privacyPolicy: URL? { get }
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.Instance: RegistrationInstance {
|
||||
@ -524,6 +526,14 @@ extension Mastodon.Entity.Instance: RegistrationInstance {
|
||||
var reasonRequired: Bool {
|
||||
return approvalRequired ?? false
|
||||
}
|
||||
|
||||
var termsOfService: URL? {
|
||||
return nil
|
||||
}
|
||||
|
||||
var privacyPolicy: URL? {
|
||||
return URL(string: "https://\(uri)/privacy-policy")
|
||||
}
|
||||
}
|
||||
|
||||
extension Mastodon.Entity.V2.Instance: RegistrationInstance {
|
||||
@ -534,4 +544,27 @@ extension Mastodon.Entity.V2.Instance: RegistrationInstance {
|
||||
var reasonRequired: Bool {
|
||||
return registrations?.reasonRequired ?? approvalRequired ?? false
|
||||
}
|
||||
|
||||
var termsOfService: URL? {
|
||||
if version?.serverVersionGreaterThanOrEqual(toMajorVersion: 4, minorVersion: 4) ?? false {
|
||||
if let string = urls?.termsOfService {
|
||||
return URL(string: string)
|
||||
} else {
|
||||
guard let domain else { return nil }
|
||||
return URL(string: "https://\(domain)/terms-of-service")
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var privacyPolicy: URL? {
|
||||
if version?.serverVersionGreaterThanOrEqual(toMajorVersion: 4, minorVersion: 4) ?? false {
|
||||
guard let string = urls?.privacyPolicy else { return nil }
|
||||
return URL(string: string)
|
||||
} else {
|
||||
guard let domain else { return nil }
|
||||
return URL(string: "https://\(domain)/privacy-policy")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ extension AuthenticationViewModel {
|
||||
disclaimer: LocalizedStringKey(L10n.Scene.ServerRules.subtitle(server.domain)),
|
||||
rules: rules.map({ $0.text }),
|
||||
onAgree: { [weak self] in
|
||||
let privacyViewModel = PrivacyViewModel(domain: server.domain, authenticateInfo: authenticateInfo, rows: [.iOSApp, .server(domain: server.domain)], instance: instance, applicationToken: applicationToken, didAccept: { doStartRegistration() })
|
||||
let privacyViewModel = PolicyViewModel(domain: server.domain, authenticateInfo: authenticateInfo, instance: instance, applicationToken: applicationToken, didAccept: { doStartRegistration() })
|
||||
self?.stateStreamContinuation.yield(.showingPrivacyPolicy(privacyViewModel))
|
||||
},
|
||||
onDisagree: { [weak self] in self?.stateStreamContinuation.yield(.showingRules(nil)) })
|
||||
@ -281,7 +281,7 @@ extension AuthenticationViewModel {
|
||||
case joiningServer(Mastodon.Entity.Server)
|
||||
case showingRules(MastodonServerRulesView.ViewModel?) // nil when we're returning to a previously configured state
|
||||
case registering(MastodonRegisterViewModel)
|
||||
case showingPrivacyPolicy(PrivacyViewModel)
|
||||
case showingPrivacyPolicy(PolicyViewModel)
|
||||
case confirmingEmail(MastodonConfirmEmailViewModel)
|
||||
case authenticatingUser
|
||||
case authenticatedUser(MastodonAuthenticationBox)
|
||||
|
@ -70,4 +70,13 @@ public extension String {
|
||||
|
||||
return majorVersionInt >= comparedVersion
|
||||
}
|
||||
func serverVersionGreaterThanOrEqual(toMajorVersion majorThreshold: Int, minorVersion minorThreshold: Int?) -> Bool {
|
||||
let majorAndMinor = split(separator: ".").prefix(2)
|
||||
let major = majorAndMinor.first
|
||||
let minor = majorAndMinor.count > 1 ? majorAndMinor[1] : "0"
|
||||
guard let major, let majorVersionInt = Int(major) else { return false }
|
||||
guard let minorThreshold, minorThreshold > 0 else { return majorVersionInt >= majorThreshold }
|
||||
guard let minorVersionInt = Int(minor) else { return false }
|
||||
return majorVersionInt >= majorThreshold && minorVersionInt >= minorThreshold
|
||||
}
|
||||
}
|
||||
|
@ -1195,6 +1195,12 @@ public enum L10n {
|
||||
public static func description(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Privacy.Description", String(describing: p1), fallback: "Although the Mastodon app does not collect any data, the server you sign up through may have a different policy.\n\nIf you disagree with the policy for **%@**, you can go back and pick a different server.")
|
||||
}
|
||||
/// Please review the terms of service for **%@**. If you disagree, you can go back and pick a different server.
|
||||
public static func termsOfServiceDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Privacy.TermsOfServiceDescription", String(describing: p1), fallback: "Please review the terms of service for **%@**. If you disagree, you can go back and pick a different server.")
|
||||
}
|
||||
/// Terms of Service
|
||||
public static let termsOfServiceTitle = L10n.tr("Localizable", "Scene.Privacy.TermsOfServiceTitle", fallback: "Terms of Service")
|
||||
/// Your Privacy
|
||||
public static let title = L10n.tr("Localizable", "Scene.Privacy.Title", fallback: "Your Privacy")
|
||||
public enum Button {
|
||||
@ -1208,6 +1214,10 @@ public enum L10n {
|
||||
public static func server(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Privacy.Policy.Server", String(describing: p1), fallback: "Privacy Policy - %@")
|
||||
}
|
||||
/// Terms of Service - %@
|
||||
public static func termsOfService(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "Scene.Privacy.Policy.TermsOfService", String(describing: p1), fallback: "Terms of Service - %@")
|
||||
}
|
||||
}
|
||||
}
|
||||
public enum Profile {
|
||||
|
@ -418,12 +418,15 @@ Please retry in a few minutes.";
|
||||
"Scene.Preview.Keyboard.ShowNext" = "Show Next";
|
||||
"Scene.Preview.Keyboard.ShowPrevious" = "Show Previous";
|
||||
"Scene.Privacy.Button.Confirm" = "I Agree";
|
||||
"Scene.Privacy.TermsOfServiceDescription" = "Please review the terms of service for **%@**. If you disagree, you can go back and pick a different server.";
|
||||
"Scene.Privacy.Policy.TermsOfService" = "Terms of Service - %@";
|
||||
"Scene.Privacy.Description" = "Although the Mastodon app does not collect any data, the server you sign up through may have a different policy.
|
||||
|
||||
If you disagree with the policy for **%@**, you can go back and pick a different server.";
|
||||
"Scene.Privacy.Policy.Ios" = "Privacy Policy - Mastodon for iOS";
|
||||
"Scene.Privacy.Policy.Server" = "Privacy Policy - %@";
|
||||
"Scene.Privacy.Title" = "Your Privacy";
|
||||
"Scene.Privacy.TermsOfServiceTitle" = "Terms of Service";
|
||||
"Scene.Profile.Accessibility.DoubleTapToOpenTheList" = "Double tap to open the list";
|
||||
"Scene.Profile.Accessibility.EditAvatarImage" = "Edit avatar image";
|
||||
"Scene.Profile.Accessibility.ShowAvatarImage" = "Show avatar image";
|
||||
|
@ -87,14 +87,6 @@ extension Mastodon.API {
|
||||
public static func resendEmailURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/auth/confirmation/new")!
|
||||
}
|
||||
|
||||
public static func serverRulesURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/about/more")!
|
||||
}
|
||||
|
||||
public static func privacyURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/terms")!
|
||||
}
|
||||
|
||||
public static func profileSettingsURL(domain: String) -> URL {
|
||||
return URL(string: "\(URL.httpScheme(domain: domain))://" + domain + "/auth/edit")!
|
||||
|
@ -83,9 +83,15 @@ extension Mastodon.Entity {
|
||||
extension Mastodon.Entity.Instance {
|
||||
public struct InstanceURL: Codable {
|
||||
public let streamingAPI: String
|
||||
public let aboutPage: String?
|
||||
public let privacyPolicy: String? // added in 4.4.0
|
||||
public let termsOfService: String? // added in 4.4.0
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case streamingAPI = "streaming_api"
|
||||
case aboutPage = "about"
|
||||
case privacyPolicy = "privacy_policy"
|
||||
case termsOfService = "terms_of_service"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user