2019-06-14 00:53:17 +00:00
// A d v a n c e d P r e f s V i e w . s w i f t
// T u s k e r
//
// C r e a t e d b y S h a d o w f a c t s o n 6 / 1 3 / 1 9 .
// C o p y r i g h t © 2 0 1 9 S h a d o w f a c t s . A l l r i g h t s r e s e r v e d .
//
import SwiftUI
import Pachyderm
2022-10-29 14:18:38 +00:00
import CoreData
2022-12-31 16:24:42 +00:00
import CloudKit
2023-03-05 19:35:25 +00:00
import UserAccounts
2019-06-14 00:53:17 +00:00
struct AdvancedPrefsView : View {
2019-11-15 00:53:00 +00:00
@ ObservedObject var preferences = Preferences . shared
2022-11-28 19:05:35 +00:00
@ State private var imageCacheSize : Int64 = 0
@ State private var mastodonCacheSize : Int64 = 0
2022-12-31 16:24:42 +00:00
@ State private var cloudKitStatus : CKAccountStatus ?
2023-09-05 03:35:40 +00:00
@ State private var isShowingFeatureFlagAlert = false
@ State private var featureFlagName = " "
2022-11-28 19:05:35 +00:00
2019-06-14 00:53:17 +00:00
var body : some View {
List {
2019-07-28 01:43:08 +00:00
formattingSection
2022-12-31 16:24:42 +00:00
cloudKitSection
2022-10-30 22:17:53 +00:00
errorReportingSection
2020-05-13 22:58:11 +00:00
cachingSection
2023-09-05 03:35:40 +00:00
featureFlagSection
2020-09-16 00:46:49 +00:00
}
2023-02-03 04:29:44 +00:00
. listStyle ( . insetGrouped )
2023-02-06 23:42:55 +00:00
. appGroupedListBackground ( container : PreferencesNavigationController . self )
2023-09-05 03:35:40 +00:00
. onTapGesture ( count : 3 ) {
featureFlagName = " "
isShowingFeatureFlagAlert = true
}
. alert ( " Enable Feature Flag " , isPresented : $ isShowingFeatureFlagAlert ) {
2023-09-03 21:39:58 +00:00
TextField ( " Flag Name " , text : $ featureFlagName )
2023-09-05 03:35:40 +00:00
. textInputAutocapitalization ( . never )
. autocorrectionDisabled ( )
Button ( " Cancel " , role : . cancel ) { }
Button ( " Enable " ) {
if let flag = Preferences . FeatureFlag ( rawValue : featureFlagName ) {
preferences . enabledFeatureFlags . insert ( flag )
}
}
2023-09-03 21:39:58 +00:00
} message : {
Text ( " Warning: Feature flags are intended for development and debugging use only. They are experimental and subject to change at any time. " )
2023-09-05 03:35:40 +00:00
}
2020-09-16 00:46:49 +00:00
. navigationBarTitle ( Text ( " Advanced " ) )
2019-07-28 01:43:08 +00:00
}
var formattingFooter : some View {
2022-11-22 16:39:47 +00:00
var s : AttributedString = " This option is only supported with Pleroma and some compatible Mastodon instances (such as Glitch). \n "
2023-03-05 19:35:25 +00:00
if let account = UserAccountsManager . shared . getMostRecentAccount ( ) {
2022-11-02 02:43:02 +00:00
let mastodonController = MastodonController . getForAccount ( account )
// s h o u l d n ' t n e e d t o l o a d t h e i n s t a n c e h e r e , b e c a u s e l o a d i n g i t i s k i c k e d o f f m y t h e s c e n e d e l e g a t e
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 )
2019-07-28 01:43:08 +00:00
}
var formattingSection : some View {
Section ( footer : formattingFooter ) {
2019-11-15 00:53:00 +00:00
Picker ( selection : $ preferences . statusContentType , label : Text ( " Post Content Type " ) ) {
2019-07-28 01:43:08 +00:00
ForEach ( StatusContentType . allCases , id : \ . self ) { type in
Text ( type . displayName ) . tag ( type )
} // . n a v i g a t i o n B a r T i t l e ( " P o s t C o n t e n t T y p e " )
// s e e F B 6 8 3 8 2 9 1
2019-06-14 00:53:17 +00:00
}
2019-07-28 01:43:08 +00:00
}
2023-02-06 23:42:55 +00:00
. appGroupedListRowBackground ( )
2019-07-28 01:43:08 +00:00
}
2022-12-31 16:24:42 +00:00
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 ! ) )
}
}
2023-02-03 04:29:44 +00:00
}
2023-02-06 23:42:55 +00:00
. appGroupedListRowBackground ( )
2023-02-03 04:29:44 +00:00
. task {
2023-02-19 20:23:25 +00:00
do {
let status = try await CKContainer . default ( ) . accountStatus ( )
self . cloudKitStatus = status
} catch {
Logging . general . error ( " Unable to get CloudKit status: \( String ( describing : error ) ) " )
2022-12-31 16:24:42 +00:00
}
}
}
2022-10-30 22:17:53 +00:00
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 )
}
}
2023-02-06 23:42:55 +00:00
. appGroupedListRowBackground ( )
2022-10-30 22:17:53 +00:00
}
2020-05-13 22:58:11 +00:00
var cachingSection : some View {
2022-11-28 19:05:35 +00:00
Section {
2020-05-13 22:58:11 +00:00
Button ( action : clearCache ) {
2020-09-12 14:49:08 +00:00
Text ( " Clear Mastodon Cache " )
} . foregroundColor ( . red )
Button ( action : clearImageCaches ) {
Text ( " Clear Image Caches " )
2020-05-13 22:58:11 +00:00
} . foregroundColor ( . red )
2022-11-28 19:05:35 +00:00
} header : {
Text ( " Caching " )
} footer : {
var s : AttributedString = " Clearing caches will restart the app. "
if imageCacheSize != 0 {
s += AttributedString ( " \n Image cache size: \( ByteCountFormatter ( ) . string ( fromByteCount : imageCacheSize ) ) " )
}
if mastodonCacheSize != 0 {
s += AttributedString ( " \n Mastodon cache size: \( ByteCountFormatter ( ) . string ( fromByteCount : mastodonCacheSize ) ) " )
}
return Text ( s )
2023-02-03 04:29:44 +00:00
}
2023-02-06 23:42:55 +00:00
. appGroupedListRowBackground ( )
2023-02-03 04:29:44 +00:00
. task {
2022-11-28 19:05:35 +00:00
imageCacheSize = [
ImageCache . avatars ,
. headers ,
. attachments ,
. emojis ,
] . map {
$0 . getDiskSizeInBytes ( ) ? ? 0
} . reduce ( 0 , + )
2023-03-05 19:35:25 +00:00
mastodonCacheSize = UserAccountsManager . shared . accounts . map {
2022-11-28 19:05:35 +00:00
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 , + )
2020-05-13 22:58:11 +00:00
}
}
2023-09-05 03:35:40 +00:00
@ 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 " )
}
}
}
2020-09-12 14:49:08 +00:00
private func clearCache ( ) {
2023-03-05 19:35:25 +00:00
for account in UserAccountsManager . shared . accounts {
2020-05-13 22:58:11 +00:00
let controller = MastodonController . getForAccount ( account )
2022-10-29 14:18:38 +00:00
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 ) " )
2020-05-13 22:58:11 +00:00
}
}
2020-09-12 14:49:08 +00:00
resetUI ( )
}
private func clearImageCaches ( ) {
[
ImageCache . avatars ,
ImageCache . headers ,
ImageCache . attachments ,
ImageCache . emojis ,
] . forEach {
try ! $0 . reset ( )
}
resetUI ( )
}
private func resetUI ( ) {
2023-03-05 19:35:25 +00:00
let mostRecent = UserAccountsManager . shared . getMostRecentAccount ( ) !
2020-05-13 22:58:11 +00:00
NotificationCenter . default . post ( name : . activateAccount , object : nil , userInfo : [ " account " : mostRecent ] )
}
2019-07-28 01:43:08 +00:00
}
extension StatusContentType {
var displayName : String {
switch self {
case . plain :
return " Plain "
case . markdown :
return " Markdown "
case . html :
return " HTML "
}
2019-06-14 00:53:17 +00:00
}
}
#if DEBUG
struct AdvancedPrefsView_Previews : PreviewProvider {
static var previews : some View {
AdvancedPrefsView ( )
}
}
#endif