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.
import Combine
import UIKit
import SwiftUI
import MastodonSDK
@ -7,14 +8,24 @@ import MastodonCore
import MastodonLocalization
import MastodonAsset
final class PrivacySafetyViewController: UIHostingController<PrivacySafetyView>, NeedsDependency {
weak var context: AppContext!
weak var coordinator: SceneCoordinator!
final class PrivacySafetyViewController: UIHostingController<PrivacySafetyView> {
private let viewModel: PrivacySafetyViewModel
private var disposeBag = [AnyCancellable]()
init(context: AppContext, coordinator: SceneCoordinator) {
self.context = context
self.coordinator = coordinator
super.init(rootView: PrivacySafetyView(viewModel: PrivacySafetyViewModel()))
init(appContext: AppContext, authContext: AuthContext, coordinator: SceneCoordinator) {
self.viewModel = PrivacySafetyViewModel(
appContext: appContext, authContext: authContext, coordinator: coordinator
)
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) {
@ -72,6 +83,7 @@ struct PrivacySafetyView: View {
}
}
.onAppear(perform: viewModel.viewDidAppear)
.onDisappear(perform: viewModel.saveSettings)
}
}

View File

@ -1,5 +1,6 @@
// Copyright © 2024 Mastodon gGmbH. All rights reserved.
import Combine
import Foundation
import MastodonLocalization
import MastodonCore
@ -23,9 +24,41 @@ class PrivacySafetyViewModel: ObservableObject {
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 {
didSet { applyPreset(preset) }
@ -52,6 +85,7 @@ class PrivacySafetyViewModel: ObservableObject {
private var doNotEvaluate = true
@Published var isInitialized = false
let onDismiss = PassthroughSubject<Void, Never>()
func viewDidAppear() {
doNotEvaluate = false
@ -96,7 +130,26 @@ extension PrivacySafetyViewModel {
func loadSettings() {
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
}
@ -104,15 +157,36 @@ extension PrivacySafetyViewModel {
func saveSettings() {
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
extension PrivacySafetyViewModel {
static let openPublic: PrivacySafetyViewModel = {
let vm = PrivacySafetyViewModel()
let vm = PrivacySafetyViewModel(appContext: nil, authContext: nil, coordinator: nil)
vm.visibility = .public
vm.manuallyApproveFollowRequests = false
vm.showFollowersAndFollowing = true
@ -122,7 +196,7 @@ extension PrivacySafetyViewModel {
}()
static let privateRestricted: PrivacySafetyViewModel = {
let vm = PrivacySafetyViewModel()
let vm = PrivacySafetyViewModel(appContext: nil, authContext: nil, coordinator: nil)
vm.visibility = .followersOnly
vm.manuallyApproveFollowRequests = true
vm.showFollowersAndFollowing = false

View File

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

View File

@ -159,6 +159,7 @@ extension Mastodon.API.Account {
public let source: Mastodon.Entity.Source?
public let fieldsAttributes: [Mastodon.Entity.Field]?
public let indexable: Bool?
public let hideCollections: Bool?
enum CodingKeys: String, CodingKey {
case discoverable
@ -172,6 +173,7 @@ extension Mastodon.API.Account {
case source
case fieldsAttributes = "fields_attributes"
case indexable
case hideCollections = "hide_collections"
}
public init(
@ -184,7 +186,8 @@ extension Mastodon.API.Account {
locked: Bool? = nil,
source: Mastodon.Entity.Source? = nil,
fieldsAttributes: [Mastodon.Entity.Field]? = nil,
indexable: Bool? = nil
indexable: Bool? = nil,
hideCollections: Bool? = nil
) {
self.discoverable = discoverable
self.bot = bot
@ -196,6 +199,7 @@ extension Mastodon.API.Account {
self.source = source
self.fieldsAttributes = fieldsAttributes
self.indexable = indexable
self.hideCollections = hideCollections
}
var contentType: String? {
@ -209,6 +213,7 @@ extension Mastodon.API.Account {
var body: 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)) }
bot.flatMap { data.append(Data.multipart(key: "bot", 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 emojis: [Emoji]
public let discoverable: Bool?
public let hideCollections: Bool?
public let indexable: Bool?
// Statistical
public let createdAt: Date
@ -71,6 +73,8 @@ extension Mastodon.Entity.Account: Codable {
case locked
case emojis
case discoverable
case hideCollections = "hide_collections"
case indexable
case createdAt = "created_at"
case lastStatusAt = "last_status_at"

View File

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