2
2
mirror of https://github.com/mastodon/mastodon-ios synced 2025-04-11 22:58:02 +02:00

Implement loading and saving of privacy & safety preferences (IOS-168)

This commit is contained in:
Marcus Kida 2024-05-30 11:57:59 +02:00
parent f31fd74abe
commit d77e8d1eac
No known key found for this signature in database
GPG Key ID: 19FF64E08013CA40
7 changed files with 118 additions and 23 deletions

View File

@ -1,5 +1,6 @@
// Copyright © 2024 Mastodon gGmbH. All rights reserved. // Copyright © 2024 Mastodon gGmbH. All rights reserved.
import Combine
import UIKit import UIKit
import SwiftUI import SwiftUI
import MastodonSDK import MastodonSDK
@ -7,14 +8,24 @@ import MastodonCore
import MastodonLocalization import MastodonLocalization
import MastodonAsset import MastodonAsset
final class PrivacySafetyViewController: UIHostingController<PrivacySafetyView>, NeedsDependency { final class PrivacySafetyViewController: UIHostingController<PrivacySafetyView> {
weak var context: AppContext! private let viewModel: PrivacySafetyViewModel
weak var coordinator: SceneCoordinator! private var disposeBag = [AnyCancellable]()
init(context: AppContext, coordinator: SceneCoordinator) { init(appContext: AppContext, authContext: AuthContext, coordinator: SceneCoordinator) {
self.context = context self.viewModel = PrivacySafetyViewModel(
self.coordinator = coordinator appContext: appContext, authContext: authContext, coordinator: coordinator
super.init(rootView: PrivacySafetyView(viewModel: PrivacySafetyViewModel())) )
super.init(
rootView: PrivacySafetyView(
viewModel: self.viewModel
)
)
self.viewModel.onDismiss.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.dismiss(animated: true)
}
.store(in: &disposeBag)
} }
@MainActor required dynamic init?(coder aDecoder: NSCoder) { @MainActor required dynamic init?(coder aDecoder: NSCoder) {
@ -72,6 +83,7 @@ struct PrivacySafetyView: View {
} }
} }
.onAppear(perform: viewModel.viewDidAppear) .onAppear(perform: viewModel.viewDidAppear)
.onDisappear(perform: viewModel.saveSettings)
} }
} }

View File

@ -1,5 +1,6 @@
// Copyright © 2024 Mastodon gGmbH. All rights reserved. // Copyright © 2024 Mastodon gGmbH. All rights reserved.
import Combine
import Foundation import Foundation
import MastodonLocalization import MastodonLocalization
import MastodonCore import MastodonCore
@ -23,10 +24,42 @@ class PrivacySafetyViewModel: ObservableObject {
return L10n.Scene.Settings.PrivacySafety.DefaultPostVisibility.onlyPeopleMentioned return L10n.Scene.Settings.PrivacySafety.DefaultPostVisibility.onlyPeopleMentioned
} }
} }
static func from(_ privacy: Mastodon.Entity.Source.Privacy) -> Self {
switch privacy {
case .public:
return .public
case .unlisted:
return .followersOnly
case .private, .direct:
return .onlyPeopleMentioned
case ._other(_):
return .public
}
}
func toPrivacy() -> Mastodon.Entity.Source.Privacy {
switch self {
case .public:
return .public
case .followersOnly:
return .unlisted
case .onlyPeopleMentioned:
return .private
}
}
} }
weak var appContext: AppContext? private var appContext: AppContext?
private var authContext: AuthContext?
private var coordinator: SceneCoordinator?
init(appContext: AppContext?, authContext: AuthContext?, coordinator: SceneCoordinator?) {
self.appContext = appContext
self.authContext = authContext
self.coordinator = coordinator
}
@Published var preset: Preset = .openPublic { @Published var preset: Preset = .openPublic {
didSet { applyPreset(preset) } didSet { applyPreset(preset) }
} }
@ -52,6 +85,7 @@ class PrivacySafetyViewModel: ObservableObject {
private var doNotEvaluate = true private var doNotEvaluate = true
@Published var isInitialized = false @Published var isInitialized = false
let onDismiss = PassthroughSubject<Void, Never>()
func viewDidAppear() { func viewDidAppear() {
doNotEvaluate = false doNotEvaluate = false
@ -96,23 +130,63 @@ extension PrivacySafetyViewModel {
func loadSettings() { func loadSettings() {
Task { @MainActor in Task { @MainActor in
guard let appContext, let authContext else {
return dismiss()
}
let domain = authContext.mastodonAuthenticationBox.domain
let userAuthorization = authContext.mastodonAuthenticationBox.userAuthorization
let account = try await appContext.apiService.accountVerifyCredentials(
domain: domain,
authorization: userAuthorization
).singleOutput().value
if let privacy = account.source?.privacy {
visibility = .from(privacy)
}
manuallyApproveFollowRequests = account.locked == true
showFollowersAndFollowing = account.hideCollections == false
suggestMyAccountToOthers = account.discoverable == true
appearInSearches = account.indexable == true
isInitialized = true isInitialized = true
} }
} }
func saveSettings() { func saveSettings() {
Task { Task {
guard let appContext, let authContext else {
return
}
let domain = authContext.mastodonAuthenticationBox.domain
let userAuthorization = authContext.mastodonAuthenticationBox.userAuthorization
let _ = try await appContext.apiService.accountUpdateCredentials(
domain: domain,
query: .init(
discoverable: suggestMyAccountToOthers,
locked: manuallyApproveFollowRequests,
source: .withPrivacy(visibility.toPrivacy()),
indexable: appearInSearches,
hideCollections: !showFollowersAndFollowing
),
authorization: userAuthorization
).value
} }
} }
func dismiss() {
onDismiss.send(())
}
} }
// Preset Rules Definition // Preset Rules Definition
extension PrivacySafetyViewModel { extension PrivacySafetyViewModel {
static let openPublic: PrivacySafetyViewModel = { static let openPublic: PrivacySafetyViewModel = {
let vm = PrivacySafetyViewModel() let vm = PrivacySafetyViewModel(appContext: nil, authContext: nil, coordinator: nil)
vm.visibility = .public vm.visibility = .public
vm.manuallyApproveFollowRequests = false vm.manuallyApproveFollowRequests = false
vm.showFollowersAndFollowing = true vm.showFollowersAndFollowing = true
@ -122,7 +196,7 @@ extension PrivacySafetyViewModel {
}() }()
static let privateRestricted: PrivacySafetyViewModel = { static let privateRestricted: PrivacySafetyViewModel = {
let vm = PrivacySafetyViewModel() let vm = PrivacySafetyViewModel(appContext: nil, authContext: nil, coordinator: nil)
vm.visibility = .followersOnly vm.visibility = .followersOnly
vm.manuallyApproveFollowRequests = true vm.manuallyApproveFollowRequests = true
vm.showFollowersAndFollowing = false vm.showFollowersAndFollowing = false

View File

@ -72,7 +72,11 @@ extension SettingsCoordinator: SettingsViewControllerDelegate {
navigationController.pushViewController(notificationViewController, animated: true) navigationController.pushViewController(notificationViewController, animated: true)
case .privacySafety: case .privacySafety:
let privacySafetyViewController = PrivacySafetyViewController(context: appContext, coordinator: sceneCoordinator) let privacySafetyViewController = PrivacySafetyViewController(
appContext: appContext,
authContext: authContext,
coordinator: sceneCoordinator
)
navigationController.pushViewController(privacySafetyViewController, animated: true) navigationController.pushViewController(privacySafetyViewController, animated: true)
case .serverDetails(let domain): case .serverDetails(let domain):
let serverDetailsViewController = ServerDetailsViewController(domain: domain, appContext: appContext, authContext: authContext, sceneCoordinator: sceneCoordinator) let serverDetailsViewController = ServerDetailsViewController(domain: domain, appContext: appContext, authContext: authContext, sceneCoordinator: sceneCoordinator)

View File

@ -159,7 +159,8 @@ extension Mastodon.API.Account {
public let source: Mastodon.Entity.Source? public let source: Mastodon.Entity.Source?
public let fieldsAttributes: [Mastodon.Entity.Field]? public let fieldsAttributes: [Mastodon.Entity.Field]?
public let indexable: Bool? public let indexable: Bool?
public let hideCollections: Bool?
enum CodingKeys: String, CodingKey { enum CodingKeys: String, CodingKey {
case discoverable case discoverable
case bot case bot
@ -172,6 +173,7 @@ extension Mastodon.API.Account {
case source case source
case fieldsAttributes = "fields_attributes" case fieldsAttributes = "fields_attributes"
case indexable case indexable
case hideCollections = "hide_collections"
} }
public init( public init(
@ -184,7 +186,8 @@ extension Mastodon.API.Account {
locked: Bool? = nil, locked: Bool? = nil,
source: Mastodon.Entity.Source? = nil, source: Mastodon.Entity.Source? = nil,
fieldsAttributes: [Mastodon.Entity.Field]? = nil, fieldsAttributes: [Mastodon.Entity.Field]? = nil,
indexable: Bool? = nil indexable: Bool? = nil,
hideCollections: Bool? = nil
) { ) {
self.discoverable = discoverable self.discoverable = discoverable
self.bot = bot self.bot = bot
@ -196,6 +199,7 @@ extension Mastodon.API.Account {
self.source = source self.source = source
self.fieldsAttributes = fieldsAttributes self.fieldsAttributes = fieldsAttributes
self.indexable = indexable self.indexable = indexable
self.hideCollections = hideCollections
} }
var contentType: String? { var contentType: String? {
@ -209,6 +213,7 @@ extension Mastodon.API.Account {
var body: Data? { var body: Data? {
var data = Data() var data = Data()
hideCollections.flatMap { data.append(Data.multipart(key: "hide_collections", value: $0)) }
discoverable.flatMap { data.append(Data.multipart(key: "discoverable", value: $0)) } discoverable.flatMap { data.append(Data.multipart(key: "discoverable", value: $0)) }
bot.flatMap { data.append(Data.multipart(key: "bot", value: $0)) } bot.flatMap { data.append(Data.multipart(key: "bot", value: $0)) }
displayName.flatMap { data.append(Data.multipart(key: "display_name", value: $0)) } displayName.flatMap { data.append(Data.multipart(key: "display_name", value: $0)) }

View File

@ -1,8 +0,0 @@
//
// File.swift
//
//
// Created by Marcus Kida on 29.05.24.
//
import Foundation

View File

@ -37,6 +37,8 @@ extension Mastodon.Entity {
public let locked: Bool public let locked: Bool
public let emojis: [Emoji] public let emojis: [Emoji]
public let discoverable: Bool? public let discoverable: Bool?
public let hideCollections: Bool?
public let indexable: Bool?
// Statistical // Statistical
public let createdAt: Date public let createdAt: Date
@ -71,6 +73,8 @@ extension Mastodon.Entity.Account: Codable {
case locked case locked
case emojis case emojis
case discoverable case discoverable
case hideCollections = "hide_collections"
case indexable
case createdAt = "created_at" case createdAt = "created_at"
case lastStatusAt = "last_status_at" case lastStatusAt = "last_status_at"

View File

@ -36,6 +36,10 @@ extension Mastodon.Entity {
case language case language
case followRequestsCount = "follow_requests_count" case followRequestsCount = "follow_requests_count"
} }
public static func withPrivacy(_ privacy: Privacy) -> Self {
Source(note: "", fields: nil, privacy: privacy, sensitive: nil, language: nil, followRequestsCount: nil)
}
} }
} }