Use atproto actions with ease in iOS shortcuts
at main 10 kB view raw
1// 2// SettingsView.swift 3// shortcut 4// 5// Created by Bailey Townsend on 7/7/25. 6// 7 8import Foundation 9import StoreKit 10import SwiftUI 11 12struct SettingsView: View { 13 @State private var showTipJar = false 14 @State private var tmpDirectorySize: String? 15 @State private var deleteTmpLoading = false 16 17 let openSourceProjects = [ 18 ("MasterJ93/ATProtoKit", "https://github.com/MasterJ93/ATProtoKit"), 19 ("ATProtoKit/ATIdentityTools", "https://github.com/ATProtoKit/ATIdentityTools"), 20 ("hyperoslo/Cache", "https://github.com/hyperoslo/Cache"), 21 ] 22 23 var body: some View { 24 NavigationStack { 25 VStack { 26 List { 27 // Tip Jar Section 28 Section { 29 Button(action: { 30 showTipJar = true 31 }) { 32 HStack { 33 Image(systemName: "heart.fill") 34 .foregroundColor(.pink) 35 .frame(width: 28) 36 Text("Tip Jar") 37 .foregroundColor(.primary) 38 Spacer() 39 Image(systemName: "chevron.right") 40 .foregroundColor(.secondary) 41 .font(.caption) 42 } 43 } 44 } 45 46 // About Section 47 Section { 48 49 Link( 50 destination: URL( 51 string: "https://attoolbox.baileytownsend.dev/#privacy")! 52 ) { 53 Label( 54 "Privacy Policy", 55 systemImage: "lock.fill" 56 ) 57 .font(.body) 58 .foregroundColor(.blue) 59 } 60 Link( 61 destination: URL( 62 string: 63 "https://apps.apple.com/app/at-toolbox/id6747999688?action=write-review" 64 )! 65 ) { 66 Label( 67 "Leave A Review", 68 systemImage: "star.fill" 69 ) 70 .font(.body) 71 .foregroundColor(.blue) 72 } 73 74 Link( 75 destination: URL( 76 string: "https://attoolbox.baileytownsend.dev/")! 77 ) { 78 Label( 79 "Website", 80 systemImage: "globe" 81 ) 82 .font(.body) 83 .foregroundColor(.blue) 84 } 85 86 Link( 87 destination: URL(string: "https://bsky.app/profile/baileytownsend.dev")! 88 ) { 89 Label( 90 "Created by @baileytownsend.dev", 91 systemImage: "person.text.rectangle" 92 ) 93 .font(.body) 94 .foregroundColor(.blue) 95 } 96 97 } 98 99 // Special Thanks Section 100 Section(header: Text("Made possible Thanks to these projects")) { 101 102 ForEach(openSourceProjects, id: \.0) { project in 103 104 HStack { 105 Link(destination: URL(string: project.1)!) { 106 Label( 107 project.0, 108 systemImage: "chevron.left.forwardslash.chevron.right" 109 ) 110 .font(.body) 111 .foregroundColor(.blue) 112 } 113 114 } 115 116 } 117 118 } 119 120 Section { 121 if let tmpSize = self.tmpDirectorySize { 122 Button { 123 withAnimation { 124 self.deleteTmpLoading = true 125 } 126 127 Task { 128 defer { 129 Task { @MainActor in 130 withAnimation { 131 self.deleteTmpLoading = false 132 } 133 } 134 } 135 136 let tmpDirectory = FileManager.default.temporaryDirectory 137 do { 138 try DirectoryCleaner.clearDirectoryCompletely(tmpDirectory) 139 140 let totalSize = DirectoryCleaner.getTotalSize( 141 of: tmpDirectory) 142 await MainActor.run { 143 self.tmpDirectorySize = ByteCountFormatter.string( 144 fromByteCount: totalSize, countStyle: .file) 145 } 146 } catch { 147 print("Error: \(error)") 148 } 149 } 150 } label: { 151 152 Text("Clear temp folder: \(tmpSize)") 153 .frame(maxWidth: .infinity) 154 } 155 .buttonStyle(LoadingButtonStyle(isLoading: self.deleteTmpLoading)) 156 .disabled(deleteTmpLoading) 157 } 158 159 } 160 161 } 162 .listStyle(InsetGroupedListStyle()) 163 164 } 165 .onAppear { 166 let tmpDirectory = FileManager.default.temporaryDirectory 167 let totalSize = DirectoryCleaner.getTotalSize( 168 of: tmpDirectory) 169 self.tmpDirectorySize = ByteCountFormatter.string( 170 fromByteCount: totalSize, countStyle: .file) 171 } 172 .navigationTitle("Info") 173 .sheet(isPresented: $showTipJar) { 174 TipJarView() 175 } 176 } 177 } 178} 179 180struct TipJarView: View { 181 @Environment(\.dismiss) var dismiss 182 @State var showThankYouAlert: Bool = false 183 @State private var transactionListener: Task<Void, Error>? = nil 184 185 var body: some View { 186 NavigationView { 187 VStack(spacing: 20) { 188 // Header 189 VStack(spacing: 8) { 190 Text("❤️") 191 .font(.system(size: 60)) 192 Text("Support the Developer") 193 .font(.title2) 194 .fontWeight(.semibold) 195 Text("Your support helps keep the app updated and new features added!") 196 .font(.subheadline) 197 .foregroundColor(.secondary) 198 .multilineTextAlignment(.center) 199 .padding(.horizontal) 200 } 201 .padding(.top, 20) 202 203 // Tip Options 204 List { 205 ProductView(id: "tip1") 206 .listRowSeparator(.hidden) 207 208 ProductView(id: "tip2") 209 .listRowSeparator(.hidden) 210 211 ProductView(id: "tip3") 212 .listRowSeparator(.hidden) 213 214 } 215 .listStyle(.plain) 216 .padding(.horizontal) 217 .alert("Thank you!", isPresented: $showThankYouAlert) { 218 // 219 // Link( 220 // "Post about it to Bluesky", 221 // destination: URL( 222 // string: 223 // "https://bsky.app/intent/compose?text=I just left AT Toolbox a tip!\nhttps://attoolbox.baileytownsend.dev" 224 // )!) 225 226 Button("Close") { 227 self.showThankYouAlert = false 228 } 229 } message: { 230 Text( 231 "Thank you so much for your support! Your tips help fund new features and to keep the app updated!" 232 ) 233 } 234 Spacer() 235 236 } 237 .navigationBarTitle("Tip Jar", displayMode: .inline) 238 .navigationBarItems(trailing: Button("Done") { dismiss() }) 239 .onAppear { 240 transactionListener = createTransactionTask() 241 } 242 .onDisappear { 243 transactionListener?.cancel() 244 } 245 246 } 247 } 248 249 private func createTransactionTask() -> Task<Void, Error> { 250 return Task { 251 for await update in Transaction.updates { 252 253 switch update { 254 case .verified(let transaction): 255 //TODO show alert 256 //Consume tip right away so another can be made 257 await transaction.finish() 258 self.showThankYouAlert = true 259 260 break 261 262 case .unverified: 263 //guess we will not do anything here? 264 break 265 266 } 267 268 } 269 } 270 } 271 272} 273 274#Preview { 275 SettingsView() 276}