254 lines
9.5 KiB
Swift
254 lines
9.5 KiB
Swift
// AdvancedPrefsView.swift
|
|
// Tusker
|
|
//
|
|
// Created by Shadowfacts on 6/13/19.
|
|
// Copyright © 2019 Shadowfacts. All rights reserved.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Pachyderm
|
|
import CoreData
|
|
import CloudKit
|
|
import UserAccounts
|
|
|
|
struct AdvancedPrefsView : View {
|
|
@ObservedObject var preferences = Preferences.shared
|
|
@State private var imageCacheSize: Int64 = 0
|
|
@State private var mastodonCacheSize: Int64 = 0
|
|
@State private var cloudKitStatus: CKAccountStatus?
|
|
@State private var isShowingFeatureFlagAlert = false
|
|
@State private var featureFlagName = ""
|
|
|
|
var body: some View {
|
|
List {
|
|
formattingSection
|
|
cloudKitSection
|
|
errorReportingSection
|
|
cachingSection
|
|
featureFlagSection
|
|
|
|
}
|
|
.listStyle(.insetGrouped)
|
|
.appGroupedListBackground(container: PreferencesNavigationController.self)
|
|
.onTapGesture(count: 3) {
|
|
featureFlagName = ""
|
|
isShowingFeatureFlagAlert = true
|
|
}
|
|
.alert("Enable Feature Flag", isPresented: $isShowingFeatureFlagAlert) {
|
|
TextField("Flag Name", text: $featureFlagName)
|
|
.textInputAutocapitalization(.never)
|
|
.autocorrectionDisabled()
|
|
|
|
Button("Cancel", role: .cancel) {}
|
|
Button("Enable") {
|
|
if let flag = Preferences.FeatureFlag(rawValue: featureFlagName) {
|
|
preferences.enabledFeatureFlags.insert(flag)
|
|
}
|
|
}
|
|
} message: {
|
|
Text("Warning: Feature flags are intended for development and debugging use only. They are experimental and subject to change at any time.")
|
|
}
|
|
.navigationBarTitle(Text("Advanced"))
|
|
}
|
|
|
|
var formattingFooter: some View {
|
|
var s: AttributedString = "This option is only supported with Pleroma and some compatible Mastodon instances (such as Glitch).\n"
|
|
if let account = UserAccountsManager.shared.getMostRecentAccount() {
|
|
let mastodonController = MastodonController.getForAccount(account)
|
|
// shouldn't need to load the instance here, because loading it is kicked off my the scene delegate
|
|
if !mastodonController.instanceFeatures.probablySupportsMarkdown {
|
|
var warning = AttributedString("\(account.instanceURL.host!) does not appear to support formatting. Using formatting symbols may not have an effect.")
|
|
warning[AttributeScopes.SwiftUIAttributes.FontAttribute.self] = .caption.bold()
|
|
s += warning
|
|
}
|
|
}
|
|
return Text(s).lineLimit(nil)
|
|
}
|
|
|
|
var formattingSection: some View {
|
|
Section(footer: formattingFooter) {
|
|
Picker(selection: $preferences.statusContentType, label: Text("Post Content Type")) {
|
|
ForEach(StatusContentType.allCases, id: \.self) { type in
|
|
Text(type.displayName).tag(type)
|
|
}//.navigationBarTitle("Post Content Type")
|
|
// see FB6838291
|
|
}
|
|
}
|
|
.appGroupedListRowBackground()
|
|
}
|
|
|
|
var cloudKitSection: some View {
|
|
Section {
|
|
HStack {
|
|
Text("iCloud Status")
|
|
Spacer()
|
|
switch cloudKitStatus {
|
|
case nil:
|
|
EmptyView()
|
|
case .available:
|
|
Text("Available")
|
|
case .couldNotDetermine:
|
|
Text("Could not determine")
|
|
case .noAccount:
|
|
Text("No account")
|
|
case .restricted:
|
|
Text("Restricted")
|
|
case .temporarilyUnavailable:
|
|
Text("Temporarily Unavailable")
|
|
@unknown default:
|
|
Text(String(describing: cloudKitStatus!))
|
|
}
|
|
}
|
|
}
|
|
.appGroupedListRowBackground()
|
|
.task {
|
|
do {
|
|
let status = try await CKContainer.default().accountStatus()
|
|
self.cloudKitStatus = status
|
|
} catch {
|
|
Logging.general.error("Unable to get CloudKit status: \(String(describing: error))")
|
|
}
|
|
}
|
|
}
|
|
|
|
var errorReportingSection: some View {
|
|
Section {
|
|
Toggle("Report Errors Automatically", isOn: $preferences.reportErrorsAutomatically)
|
|
} footer: {
|
|
var privacyPolicy: AttributedString = "Privacy Policy"
|
|
let _ = privacyPolicy.link = URL(string: "https://vaccor.space/tusker#privacy")!
|
|
if preferences.reportErrorsAutomatically {
|
|
Text(AttributedString("App crashes and errors will be automatically reported to the developer. You may be prompted to add additional information.\n") + privacyPolicy)
|
|
.lineLimit(nil)
|
|
} else {
|
|
Text(AttributedString("Errors will not be reported automatically. When a crash occurs, you may be asked to report it manually.\n") + privacyPolicy)
|
|
.lineLimit(nil)
|
|
}
|
|
}
|
|
.appGroupedListRowBackground()
|
|
}
|
|
|
|
var cachingSection: some View {
|
|
Section {
|
|
Button(action: clearCache) {
|
|
Text("Clear Mastodon Cache")
|
|
}.foregroundColor(.red)
|
|
Button(action: clearImageCaches) {
|
|
Text("Clear Image Caches")
|
|
}.foregroundColor(.red)
|
|
} header: {
|
|
Text("Caching")
|
|
} footer: {
|
|
var s: AttributedString = "Clearing caches will restart the app."
|
|
if imageCacheSize != 0 {
|
|
s += AttributedString("\nImage cache size: \(ByteCountFormatter().string(fromByteCount: imageCacheSize))")
|
|
}
|
|
if mastodonCacheSize != 0 {
|
|
s += AttributedString("\nMastodon cache size: \(ByteCountFormatter().string(fromByteCount: mastodonCacheSize))")
|
|
}
|
|
return Text(s)
|
|
}
|
|
.appGroupedListRowBackground()
|
|
.task {
|
|
imageCacheSize = [
|
|
ImageCache.avatars,
|
|
.headers,
|
|
.attachments,
|
|
.emojis,
|
|
].map {
|
|
$0.getDiskSizeInBytes() ?? 0
|
|
}.reduce(0, +)
|
|
mastodonCacheSize = UserAccountsManager.shared.accounts.map {
|
|
let descriptions = MastodonController.getForAccount($0).persistentContainer.persistentStoreDescriptions
|
|
return descriptions.map {
|
|
guard let url = $0.url else {
|
|
return 0
|
|
}
|
|
return FileManager.default.recursiveSize(url: url) ?? 0
|
|
}.reduce(0, +)
|
|
}.reduce(0, +)
|
|
}
|
|
}
|
|
|
|
@ViewBuilder
|
|
var featureFlagSection: some View {
|
|
if !preferences.enabledFeatureFlags.isEmpty {
|
|
Section {
|
|
ForEach(preferences.enabledFeatureFlags.map(\.rawValue).sorted(), id: \.self) { name in
|
|
Text(verbatim: name)
|
|
.contextMenu {
|
|
Button(role: .destructive) {
|
|
preferences.enabledFeatureFlags.remove(.init(rawValue: name)!)
|
|
} label: {
|
|
Label("Remove", systemImage: "trash")
|
|
}
|
|
}
|
|
}
|
|
.onDelete { indices in
|
|
let sortedFlags = preferences.enabledFeatureFlags.sorted(by: { $0.rawValue < $1.rawValue })
|
|
let removed = indices.map { sortedFlags[$0] }
|
|
preferences.enabledFeatureFlags.subtract(removed)
|
|
}
|
|
} header: {
|
|
Text("Feature Flags")
|
|
}
|
|
}
|
|
}
|
|
|
|
private func clearCache() {
|
|
for account in UserAccountsManager.shared.accounts {
|
|
let controller = MastodonController.getForAccount(account)
|
|
let container = controller.persistentContainer
|
|
do {
|
|
let statusesReq = NSBatchDeleteRequest(fetchRequest: StatusMO.fetchRequest())
|
|
try container.viewContext.execute(statusesReq)
|
|
let accountsReq = NSBatchDeleteRequest(fetchRequest: AccountMO.fetchRequest())
|
|
try container.viewContext.execute(accountsReq)
|
|
let relationshipsReq = NSBatchDeleteRequest(fetchRequest: RelationshipMO.fetchRequest())
|
|
try container.viewContext.execute(relationshipsReq)
|
|
} catch {
|
|
Logging.general.error("Error while clearing Mastodon cache: \(String(describing: error), privacy: .public)")
|
|
}
|
|
}
|
|
resetUI()
|
|
}
|
|
|
|
private func clearImageCaches() {
|
|
[
|
|
ImageCache.avatars,
|
|
ImageCache.headers,
|
|
ImageCache.attachments,
|
|
ImageCache.emojis,
|
|
].forEach {
|
|
try! $0.reset()
|
|
}
|
|
resetUI()
|
|
}
|
|
|
|
private func resetUI() {
|
|
let mostRecent = UserAccountsManager.shared.getMostRecentAccount()!
|
|
NotificationCenter.default.post(name: .activateAccount, object: nil, userInfo: ["account": mostRecent])
|
|
}
|
|
}
|
|
|
|
extension StatusContentType {
|
|
var displayName: String {
|
|
switch self {
|
|
case .plain:
|
|
return "Plain"
|
|
case .markdown:
|
|
return "Markdown"
|
|
case .html:
|
|
return "HTML"
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
struct AdvancedPrefsView_Previews : PreviewProvider {
|
|
static var previews: some View {
|
|
AdvancedPrefsView()
|
|
}
|
|
}
|
|
#endif
|