diff --git a/IceCubesActionExtension/ActionRequestHandler.swift b/IceCubesActionExtension/ActionRequestHandler.swift index 4a04bad0..6e7aa21b 100644 --- a/IceCubesActionExtension/ActionRequestHandler.swift +++ b/IceCubesActionExtension/ActionRequestHandler.swift @@ -5,100 +5,100 @@ // Created by Thomas Durand on 26/01/2023. // -import UIKit import MobileCoreServices +import UIKit import UniformTypeIdentifiers import Models import Network // Sample code was sending this from a thread to another, let asume @Sendable for this -extension NSExtensionContext: @unchecked Sendable { } +extension NSExtensionContext: @unchecked Sendable {} class ActionRequestHandler: NSObject, NSExtensionRequestHandling { - enum Error: Swift.Error { - case inputProviderNotFound - case loadedItemHasWrongType - case urlNotFound - case noHost - case notMastodonInstance - } + enum Error: Swift.Error { + case inputProviderNotFound + case loadedItemHasWrongType + case urlNotFound + case noHost + case notMastodonInstance + } - func beginRequest(with context: NSExtensionContext) { - // Do not call super in an Action extension with no user interface - Task { - do { - let url = try await url(from: context) - guard await url.isMastodonInstance else { - throw Error.notMastodonInstance - } - await MainActor.run { - let deeplink = url.iceCubesAppDeepLink - let output = output(wrapping: deeplink) - context.completeRequest(returningItems: output) - } - } catch { - await MainActor.run { - context.completeRequest(returningItems: []) - } - } + func beginRequest(with context: NSExtensionContext) { + // Do not call super in an Action extension with no user interface + Task { + do { + let url = try await url(from: context) + guard await url.isMastodonInstance else { + throw Error.notMastodonInstance } + await MainActor.run { + let deeplink = url.iceCubesAppDeepLink + let output = output(wrapping: deeplink) + context.completeRequest(returningItems: output) + } + } catch { + await MainActor.run { + context.completeRequest(returningItems: []) + } + } } + } } extension URL { - var isMastodonInstance: Bool { - get async { - do { - guard let host = host() else { - throw ActionRequestHandler.Error.noHost - } - let _: Instance = try await Client(server: host).get(endpoint: Instances.instance) - return true - } catch { - return false - } + var isMastodonInstance: Bool { + get async { + do { + guard let host = host() else { + throw ActionRequestHandler.Error.noHost } + let _: Instance = try await Client(server: host).get(endpoint: Instances.instance) + return true + } catch { + return false + } } + } - var iceCubesAppDeepLink: URL { - var components = URLComponents(url: self, resolvingAgainstBaseURL: false)! - components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"]) - return components.url! - } + var iceCubesAppDeepLink: URL { + var components = URLComponents(url: self, resolvingAgainstBaseURL: false)! + components.scheme = AppInfo.scheme.trimmingCharacters(in: [":", "/"]) + return components.url! + } } extension ActionRequestHandler { - /// Will look for an input item that might provide the property list that Javascript sent us - private func url(from context: NSExtensionContext) async throws -> URL { - for item in context.inputItems as! [NSExtensionItem] { - guard let attachments = item.attachments else { - continue - } - for itemProvider in attachments { - guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else { - continue - } - guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else { - throw Error.loadedItemHasWrongType - } - let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:] - guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else { - throw Error.urlNotFound - } - return url - } + /// Will look for an input item that might provide the property list that Javascript sent us + private func url(from context: NSExtensionContext) async throws -> URL { + for item in context.inputItems as! [NSExtensionItem] { + guard let attachments = item.attachments else { + continue + } + for itemProvider in attachments { + guard itemProvider.hasItemConformingToTypeIdentifier(UTType.propertyList.identifier) else { + continue } - throw Error.inputProviderNotFound + guard let dictionary = try await itemProvider.loadItem(forTypeIdentifier: UTType.propertyList.identifier) as? [String: Any] else { + throw Error.loadedItemHasWrongType + } + let input = dictionary[NSExtensionJavaScriptPreprocessingResultsKey] as! [String: Any]? ?? [:] + guard let absoluteStringUrl = input["url"] as? String, let url = URL(string: absoluteStringUrl) else { + throw Error.urlNotFound + } + return url + } } + throw Error.inputProviderNotFound + } - /// Wrap the output to the expected object so we send back results to JS - private func output(wrapping deeplink: URL) -> [NSExtensionItem] { - let results = ["deeplink": deeplink.absoluteString] - let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results] - let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier) - let item = NSExtensionItem() - item.attachments = [provider] - return [item] - } + /// Wrap the output to the expected object so we send back results to JS + private func output(wrapping deeplink: URL) -> [NSExtensionItem] { + let results = ["deeplink": deeplink.absoluteString] + let dictionary = [NSExtensionJavaScriptFinalizeArgumentKey: results] + let provider = NSItemProvider(item: dictionary as NSDictionary, typeIdentifier: UTType.propertyList.identifier) + let item = NSExtensionItem() + item.attachments = [provider] + return [item] + } } diff --git a/IceCubesApp/App/SideBarView.swift b/IceCubesApp/App/SideBarView.swift index 3c56f4f7..2ab16117 100644 --- a/IceCubesApp/App/SideBarView.swift +++ b/IceCubesApp/App/SideBarView.swift @@ -2,8 +2,8 @@ import Account import AppAccount import DesignSystem import Env -import SwiftUI import Models +import SwiftUI struct SideBarView: View { @EnvironmentObject private var appAccounts: AppAccountsManager diff --git a/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift b/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift index 39cd7c43..94bd4013 100644 --- a/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift +++ b/IceCubesApp/App/Tabs/Settings/AccountSettingView.swift @@ -1,25 +1,25 @@ -import SwiftUI import Account +import AppAccount import DesignSystem import Env import Models -import AppAccount +import SwiftUI struct AccountSettingsView: View { @Environment(\.dismiss) private var dismiss - + @EnvironmentObject private var pushNotifications: PushNotificationsService @EnvironmentObject private var currentAccount: CurrentAccount @EnvironmentObject private var currentInstance: CurrentInstance @EnvironmentObject private var theme: Theme @EnvironmentObject private var appAccountsManager: AppAccountsManager - + @State private var isEditingAccount: Bool = false @State private var isEditingFilters: Bool = false - + let account: Account let appAccount: AppAccount - + var body: some View { Form { Section { @@ -54,7 +54,6 @@ struct AccountSettingsView: View { } label: { Text("account.action.logout") } - } .listRowBackground(theme.primaryBackgroundColor) } diff --git a/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift b/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift index 6d59b0d0..f75df72c 100644 --- a/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/ContentSettingsView.swift @@ -8,10 +8,9 @@ import SwiftUI import UserNotifications struct ContentSettingsView: View { - @EnvironmentObject private var userPreferences: UserPreferences @EnvironmentObject private var theme: Theme - + var body: some View { Form { Section { @@ -19,7 +18,7 @@ struct ContentSettingsView: View { Text("settings.content.use-instance-settings") } } footer: { - Text("settings.content.main-toggle.description") + Text("settings.content.main-toggle.description") } .listRowBackground(theme.primaryBackgroundColor) .onChange(of: userPreferences.useInstanceContentSettings) { newVal in @@ -29,15 +28,14 @@ struct ContentSettingsView: View { userPreferences.appDefaultPostsSensitive = userPreferences.postIsSensitive userPreferences.appDefaultPostVisibility = userPreferences.postVisibility } - } - + Section("settings.content.reading") { Toggle(isOn: $userPreferences.appAutoExpandSpoilers) { Text("settings.content.expand-spoilers") } .disabled(userPreferences.useInstanceContentSettings) - + Picker("settings.content.expand-media", selection: $userPreferences.appAutoExpandMedia) { ForEach(ServerPreferences.AutoExpandMedia.allCases, id: \.rawValue) { media in Text(media.description).tag(media) @@ -45,7 +43,7 @@ struct ContentSettingsView: View { } .disabled(userPreferences.useInstanceContentSettings) }.listRowBackground(theme.primaryBackgroundColor) - + Section("settings.content.posting") { Picker("settings.content.default-visibility", selection: $userPreferences.appDefaultPostVisibility) { ForEach(Visibility.allCases, id: \.rawValue) { vis in @@ -53,23 +51,16 @@ struct ContentSettingsView: View { } } .disabled(userPreferences.useInstanceContentSettings) - Toggle(isOn: $userPreferences.appDefaultPostsSensitive) { Text("settings.content.default-sensitive") } .disabled(userPreferences.useInstanceContentSettings) - } .listRowBackground(theme.primaryBackgroundColor) - } .navigationTitle("settings.content.navigation-title") .scrollContentBackground(.hidden) .background(theme.secondaryBackgroundColor) - - - } - } diff --git a/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift b/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift index 9841caa9..9432e7f8 100644 --- a/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift +++ b/IceCubesApp/App/Tabs/Settings/PushNotificationsView.swift @@ -33,7 +33,7 @@ struct PushNotificationsView: View { Text("settings.push.main-toggle.description") } .listRowBackground(theme.primaryBackgroundColor) - + if subscription.isEnabled { Section { Toggle(isOn: .init(get: { @@ -87,7 +87,7 @@ struct PushNotificationsView: View { } .listRowBackground(theme.primaryBackgroundColor) } - + Section { Button("settings.push.duplicate.button.fix") { Task { @@ -101,7 +101,6 @@ struct PushNotificationsView: View { Text("settings.push.duplicate.footer") } .listRowBackground(theme.primaryBackgroundColor) - } .navigationTitle("settings.push.navigation-title") .scrollContentBackground(.hidden) @@ -116,7 +115,7 @@ struct PushNotificationsView: View { await subscription.updateSubscription() } } - + private func deleteSubscription() { Task { await subscription.deleteSubscription() diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 8ff3b9a9..442b8273 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -10,7 +10,7 @@ import Timeline struct SettingsTabs: View { @Environment(\.dismiss) private var dismiss - + @EnvironmentObject private var pushNotifications: PushNotificationsService @EnvironmentObject private var preferences: UserPreferences @EnvironmentObject private var client: Client @@ -75,7 +75,8 @@ struct SettingsTabs: View { if let index = indexSet.first { let account = appAccountsManager.availableAccounts[index] if let token = account.oauthToken, - let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token }) { + let sub = pushNotifications.subscriptions.first(where: { $0.account.token == token }) + { Task { await sub.deleteSubscription() appAccountsManager.delete(account: account) diff --git a/Packages/Account/Sources/Account/AccountDetailView.swift b/Packages/Account/Sources/Account/AccountDetailView.swift index 5f760de7..51403736 100644 --- a/Packages/Account/Sources/Account/AccountDetailView.swift +++ b/Packages/Account/Sources/Account/AccountDetailView.swift @@ -25,7 +25,7 @@ public struct AccountDetailView: View { @State private var isCurrentUser: Bool = false @State private var isCreateListAlertPresented: Bool = false @State private var createListTitle: String = "" - + @State private var isEditingAccount: Bool = false @State private var isEditingFilters: Bool = false @@ -513,7 +513,7 @@ public struct AccountDetailView: View { } label: { Label("account.action.edit-info", systemImage: "pencil") } - + if curretnInstance.isFiltersSupported { Button { isEditingFilters = true @@ -521,7 +521,7 @@ public struct AccountDetailView: View { Label("account.action.edit-filters", systemImage: "line.3.horizontal.decrease.circle") } } - + Button { routerPath.presentedSheet = .accountPushNotficationsSettings } label: { diff --git a/Packages/Account/Sources/Account/Edit/EditAccountView.swift b/Packages/Account/Sources/Account/Edit/EditAccountView.swift index 1b6dbf2f..72e1d250 100644 --- a/Packages/Account/Sources/Account/Edit/EditAccountView.swift +++ b/Packages/Account/Sources/Account/Edit/EditAccountView.swift @@ -9,8 +9,8 @@ public struct EditAccountView: View { @EnvironmentObject private var theme: Theme @StateObject private var viewModel = EditAccountViewModel() - - public init() { } + + public init() {} public var body: some View { NavigationStack { diff --git a/Packages/Account/Sources/Account/Filters/EditFilterView.swift b/Packages/Account/Sources/Account/Filters/EditFilterView.swift index 42035419..546ffbb9 100644 --- a/Packages/Account/Sources/Account/Filters/EditFilterView.swift +++ b/Packages/Account/Sources/Account/Filters/EditFilterView.swift @@ -1,16 +1,16 @@ -import SwiftUI -import Models -import Env import DesignSystem +import Env +import Models import Network +import SwiftUI struct EditFilterView: View { @Environment(\.dismiss) private var dismiss - + @EnvironmentObject private var theme: Theme @EnvironmentObject private var account: CurrentAccount @EnvironmentObject private var client: Client - + @State private var isSavingFilter: Bool = false @State private var filter: ServerFilter? @State private var title: String @@ -18,20 +18,20 @@ struct EditFilterView: View { @State private var newKeyword: String = "" @State private var contexts: [ServerFilter.Context] @State private var filterAction: ServerFilter.Action - + @FocusState private var isTitleFocused: Bool - + private var data: ServerFilterData { .init(title: title, context: contexts, filterAction: filterAction, expireIn: nil) } - + private var canSave: Bool { !title.isEmpty } - + init(filter: ServerFilter?) { _filter = .init(initialValue: filter) _title = .init(initialValue: filter?.title ?? "") @@ -39,7 +39,7 @@ struct EditFilterView: View { _contexts = .init(initialValue: filter?.context ?? [.home]) _filterAction = .init(initialValue: filter?.filterAction ?? .warn) } - + var body: some View { Form { titleSection @@ -55,7 +55,7 @@ struct EditFilterView: View { .background(theme.secondaryBackgroundColor) .onAppear { if filter == nil { - isTitleFocused = true + isTitleFocused = true } } .toolbar { @@ -64,7 +64,7 @@ struct EditFilterView: View { } } } - + private var titleSection: some View { Section("filter.edit.title") { TextField("filter.edit.title", text: $title) @@ -76,9 +76,8 @@ struct EditFilterView: View { } } .listRowBackground(theme.primaryBackgroundColor) - } - + private var keywordsSection: some View { Section("filter.edit.keywords") { ForEach(keywords) { keyword in @@ -113,7 +112,7 @@ struct EditFilterView: View { } .listRowBackground(theme.primaryBackgroundColor) } - + private var contextsSection: some View { Section("filter.edit.contexts") { ForEach(ServerFilter.Context.allCases, id: \.self) { context in @@ -136,7 +135,7 @@ struct EditFilterView: View { .listRowBackground(theme.primaryBackgroundColor) } } - + private var filterActionView: some View { Section("filter.edit.action") { Picker(selection: $filterAction) { @@ -156,7 +155,7 @@ struct EditFilterView: View { } .listRowBackground(theme.primaryBackgroundColor) } - + private var saveButton: some View { Button { Task { @@ -172,7 +171,7 @@ struct EditFilterView: View { } .disabled(!canSave) } - + private func saveFilter() async { do { isSavingFilter = true @@ -182,26 +181,26 @@ struct EditFilterView: View { } else { let newFilter: ServerFilter = try await client.post(endpoint: ServerFilters.createFilter(json: data), forceVersion: .v2) - self.filter = newFilter + filter = newFilter } } catch {} isSavingFilter = false } - + private func addKeyword(name: String) async { guard let filterId = filter?.id else { return } isSavingFilter = true do { let keyword: ServerFilter.Keyword = try await - client.post(endpoint: ServerFilters.addKeyword(filter: filterId, - keyword: name, - wholeWord: true), - forceVersion: .v2) - self.keywords.append(keyword) - } catch { } + client.post(endpoint: ServerFilters.addKeyword(filter: filterId, + keyword: name, + wholeWord: true), + forceVersion: .v2) + keywords.append(keyword) + } catch {} isSavingFilter = false } - + private func deleteKeyword(keyword: ServerFilter.Keyword) async { isSavingFilter = true do { @@ -210,7 +209,7 @@ struct EditFilterView: View { if response?.statusCode == 200 { keywords.removeAll(where: { $0.id == keyword.id }) } - } catch { } + } catch {} isSavingFilter = false } } diff --git a/Packages/Account/Sources/Account/Filters/FiltersListView.swift b/Packages/Account/Sources/Account/Filters/FiltersListView.swift index 346584f5..b6438af8 100644 --- a/Packages/Account/Sources/Account/Filters/FiltersListView.swift +++ b/Packages/Account/Sources/Account/Filters/FiltersListView.swift @@ -1,21 +1,21 @@ -import SwiftUI -import Env -import Network import DesignSystem +import Env import Models +import Network +import SwiftUI public struct FiltersListView: View { @Environment(\.dismiss) private var dismiss - + @EnvironmentObject private var theme: Theme @EnvironmentObject private var account: CurrentAccount @EnvironmentObject private var client: Client - + @State private var isLoading: Bool = true @State private var filters: [ServerFilter] = [] - - public init() { } - + + public init() {} + public var body: some View { NavigationStack { Form { @@ -31,7 +31,7 @@ public struct FiltersListView: View { VStack(alignment: .leading) { Text(filter.title) .font(.scaledSubheadline) - Text("\(filter.context.map{ $0.name }.joined(separator: ", "))") + Text("\(filter.context.map { $0.name }.joined(separator: ", "))") .font(.scaledBody) .foregroundColor(.gray) } @@ -44,7 +44,7 @@ public struct FiltersListView: View { } .listRowBackground(theme.primaryBackgroundColor) } - + Section { NavigationLink(destination: EditFilterView(filter: nil)) { Label("filter.new", systemImage: "plus") @@ -70,7 +70,7 @@ public struct FiltersListView: View { } } } - + private func deleteFilter(indexes: IndexSet) { if let index = indexes.first { Task { @@ -84,7 +84,7 @@ public struct FiltersListView: View { } } } - + @ToolbarContentBuilder private var toolbarContent: some ToolbarContent { ToolbarItem(placement: .navigationBarTrailing) { diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccount.swift b/Packages/AppAccount/Sources/AppAccount/AppAccount.swift index f4c4bf5e..1130f474 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccount.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccount.swift @@ -4,7 +4,7 @@ import Models import Network import SwiftUI -extension AppAccount { +public extension AppAccount { private static var keychain: KeychainSwift { let keychain = KeychainSwift() #if !DEBUG && !targetEnvironment(simulator) @@ -13,17 +13,17 @@ extension AppAccount { return keychain } - public func save() throws { + func save() throws { let encoder = JSONEncoder() let data = try encoder.encode(self) Self.keychain.set(data, forKey: key) } - public func delete() { + func delete() { Self.keychain.delete(key) } - public static func retrieveAll() -> [AppAccount] { + static func retrieveAll() -> [AppAccount] { migrateLegacyAccounts() let keychain = Self.keychain let decoder = JSONDecoder() @@ -39,7 +39,7 @@ extension AppAccount { return accounts } - public static func migrateLegacyAccounts() { + static func migrateLegacyAccounts() { let keychain = KeychainSwift() let decoder = JSONDecoder() let keys = keychain.allKeys @@ -52,7 +52,7 @@ extension AppAccount { } } - public static func deleteAll() { + static func deleteAll() { let keychain = Self.keychain let keys = keychain.allKeys for key in keys { diff --git a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift index ddd4aa59..758cc146 100644 --- a/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift +++ b/Packages/AppAccount/Sources/AppAccount/AppAccountsSelectorView.swift @@ -101,7 +101,7 @@ public struct AppAccountsSelectorView: View { Label("app-account.button.add", systemImage: "person.badge.plus") } } - + if UIDevice.current.userInterfaceIdiom == .phone { Divider() Button { diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift index 9b706e98..c2af7961 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationDetailView.swift @@ -39,8 +39,8 @@ public struct ConversationDetailView: View { ForEach(viewModel.messages) { message in ConversationMessageView(message: message, conversation: viewModel.conversation) - .padding(.vertical, 4) - .id(message.id) + .padding(.vertical, 4) + .id(message.id) } bottomAnchorView } @@ -129,7 +129,7 @@ public struct ConversationDetailView: View { .overlay( RoundedRectangle(cornerRadius: 14) .stroke(.gray, lineWidth: 1) - ) + ) .font(.scaledBody) if !viewModel.newMessageText.isEmpty { Button { diff --git a/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift b/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift index f9b4ada7..64b6fcf6 100644 --- a/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift +++ b/Packages/Conversations/Sources/Conversations/Detail/ConversationMessageView.swift @@ -68,7 +68,7 @@ struct ConversationMessageView: View { } Group { Text(message.createdAt.shortDateFormatted) + - Text(" ") + Text(" ") Text(message.createdAt.asDate, style: .time) } .font(.scaledFootnote) diff --git a/Packages/Env/Sources/Env/CurrentInstance.swift b/Packages/Env/Sources/Env/CurrentInstance.swift index a92d4d49..d4343a1b 100644 --- a/Packages/Env/Sources/Env/CurrentInstance.swift +++ b/Packages/Env/Sources/Env/CurrentInstance.swift @@ -9,11 +9,11 @@ public class CurrentInstance: ObservableObject { private var client: Client? public static let shared = CurrentInstance() - + public var isFiltersSupported: Bool { instance?.version.hasPrefix("4") == true } - + public var isEditSupported: Bool { instance?.version.hasPrefix("4") == true } diff --git a/Packages/Env/Sources/Env/PushNotificationsService.swift b/Packages/Env/Sources/Env/PushNotificationsService.swift index 0e0a3c31..73f3c675 100644 --- a/Packages/Env/Sources/Env/PushNotificationsService.swift +++ b/Packages/Env/Sources/Env/PushNotificationsService.swift @@ -27,7 +27,7 @@ public class PushNotificationsService: ObservableObject { } public static let shared = PushNotificationsService() - + public private(set) var subscriptions: [PushNotificationSubscriptionSettings] = [] @Published public var pushToken: Data? @@ -47,7 +47,7 @@ public class PushNotificationsService: ObservableObject { } } } - + public func setAccounts(accounts: [PushAccount]) { subscriptions = [] for account in accounts { @@ -58,13 +58,13 @@ public class PushNotificationsService: ObservableObject { subscriptions.append(sub) } } - + public func updateSubscriptions(forceCreate: Bool) async { for subscription in subscriptions { await withTaskGroup(of: Void.self, body: { group in group.addTask { await subscription.fetchSubscription() - if await subscription.subscription != nil && !forceCreate { + if await subscription.subscription != nil, !forceCreate { await subscription.deleteSubscription() await subscription.updateSubscription() } else if forceCreate { @@ -136,23 +136,23 @@ public class PushNotificationSubscriptionSettings: ObservableObject { @Published public var isMentionNotificationEnabled: Bool = true @Published public var isPollNotificationEnabled: Bool = true @Published public var isNewPostsNotificationEnabled: Bool = true - + public let account: PushAccount private let key: Data private let authKey: Data - + public var pushToken: Data? - + public private(set) var subscription: PushSubscription? - + public init(account: PushAccount, key: Data, authKey: Data, pushToken: Data?) { self.account = account self.key = key self.authKey = authKey self.pushToken = pushToken } - + private func refreshSubscriptionsUI() { if let subscription { isFollowNotificationEnabled = subscription.alerts.follow @@ -163,7 +163,7 @@ public class PushNotificationSubscriptionSettings: ObservableObject { isNewPostsNotificationEnabled = subscription.alerts.status } } - + public func updateSubscription() async { guard let pushToken = pushToken else { return } let client = Client(server: account.server, oauthToken: account.token) @@ -176,23 +176,23 @@ public class PushNotificationSubscriptionSettings: ObservableObject { listenerURL += "?sandbox=true" #endif subscription = - try await client.post(endpoint: Push.createSub(endpoint: listenerURL, - p256dh: key, - auth: authKey, - mentions: isMentionNotificationEnabled, - status: isNewPostsNotificationEnabled, - reblog: isReblogNotificationEnabled, - follow: isFollowNotificationEnabled, - favorite: isFavoriteNotificationEnabled, - poll: isPollNotificationEnabled)) + try await client.post(endpoint: Push.createSub(endpoint: listenerURL, + p256dh: key, + auth: authKey, + mentions: isMentionNotificationEnabled, + status: isNewPostsNotificationEnabled, + reblog: isReblogNotificationEnabled, + follow: isFollowNotificationEnabled, + favorite: isFavoriteNotificationEnabled, + poll: isPollNotificationEnabled)) isEnabled = subscription != nil - + } catch { isEnabled = false } refreshSubscriptionsUI() } - + public func deleteSubscription() async { let client = Client(server: account.server, oauthToken: account.token) do { @@ -206,7 +206,7 @@ public class PushNotificationSubscriptionSettings: ObservableObject { isEnabled = false } catch {} } - + public func fetchSubscription() async { let client = Client(server: account.server, oauthToken: account.token) do { diff --git a/Packages/Env/Sources/Env/Router.swift b/Packages/Env/Sources/Env/Router.swift index bd7cabab..549a198b 100644 --- a/Packages/Env/Sources/Env/Router.swift +++ b/Packages/Env/Sources/Env/Router.swift @@ -35,7 +35,7 @@ public enum SheetDestinations: Identifiable { public var id: String { switch self { case .editStatusEditor, .newStatusEditor, .replyToStatusEditor, .quoteStatusEditor, - .mentionStatusEditor, .settings, .accountPushNotficationsSettings: + .mentionStatusEditor, .settings, .accountPushNotficationsSettings: return "statusEditor" case .listEdit: return "listEdit" @@ -67,8 +67,8 @@ public class RouterPath: ObservableObject { public func handleStatus(status: AnyStatus, url: URL) -> OpenURLAction.Result { if url.pathComponents.count == 3 && url.pathComponents[1] == "tags" && - url.host() == status.account.url?.host(), - let tag = url.pathComponents.last + url.host() == status.account.url?.host(), + let tag = url.pathComponents.last { // OK this test looks weird but it's // A 3 component path i.e. ["/", "tags", "tagname"] @@ -110,7 +110,8 @@ public class RouterPath: ObservableObject { } else if let client = client, client.isAuth, client.hasConnection(with: url), - let id = Int(url.lastPathComponent) { + let id = Int(url.lastPathComponent) + { if url.absoluteString.contains(client.server) { navigate(to: .statusDetail(id: String(id))) } else { diff --git a/Packages/Env/Sources/Env/UserPreferences.swift b/Packages/Env/Sources/Env/UserPreferences.swift index fdf0d48c..230422bd 100644 --- a/Packages/Env/Sources/Env/UserPreferences.swift +++ b/Packages/Env/Sources/Env/UserPreferences.swift @@ -19,59 +19,44 @@ public class UserPreferences: ObservableObject { @AppStorage("recently_used_languages") public var recentlyUsedLanguages: [String] = [] @AppStorage("social_keyboard_composer") public var isSocialKeyboardEnabled: Bool = true - + @AppStorage("use_instance_content_settings") public var useInstanceContentSettings: Bool = true @AppStorage("app_auto_expand_spoilers") public var appAutoExpandSpoilers = false - @AppStorage("app_auto_expand_media") public var appAutoExpandMedia:ServerPreferences.AutoExpandMedia = .hideSensitive - @AppStorage("app_default_post_visibility") public var appDefaultPostVisibility:Models.Visibility = .pub + @AppStorage("app_auto_expand_media") public var appAutoExpandMedia: ServerPreferences.AutoExpandMedia = .hideSensitive + @AppStorage("app_default_post_visibility") public var appDefaultPostVisibility: Models.Visibility = .pub @AppStorage("app_default_posts_sensitive") public var appDefaultPostsSensitive = false + public var postVisibility: Models.Visibility { + if useInstanceContentSettings { + return serverPreferences?.postVisibility ?? .pub + } else { + return appDefaultPostVisibility + } + } - public var postVisibility:Models.Visibility { - get{ - if useInstanceContentSettings { - return serverPreferences?.postVisibility ?? .pub - } - else { - return appDefaultPostVisibility - } + public var postIsSensitive: Bool { + if useInstanceContentSettings { + return serverPreferences?.postIsSensitive ?? false + } else { + return appDefaultPostsSensitive } } - - public var postIsSensitive:Bool { - get { - if useInstanceContentSettings { - return serverPreferences?.postIsSensitive ?? false - } - else { - return appDefaultPostsSensitive - } - } - } - - + public var autoExpandSpoilers: Bool { - get { - if useInstanceContentSettings { - return serverPreferences?.autoExpandSpoilers ?? true - } - else { - return appAutoExpandSpoilers - } + if useInstanceContentSettings { + return serverPreferences?.autoExpandSpoilers ?? true + } else { + return appAutoExpandSpoilers } } - + public var autoExpandMedia: ServerPreferences.AutoExpandMedia { - get { - if useInstanceContentSettings { - return serverPreferences?.autoExpandMedia ?? .hideSensitive - } - else { - return appAutoExpandMedia - } + if useInstanceContentSettings { + return serverPreferences?.autoExpandMedia ?? .hideSensitive + } else { + return appAutoExpandMedia } } - public var pushNotificationsCount: Int { get { diff --git a/Packages/Models/Sources/Models/Alias/ServerDate.swift b/Packages/Models/Sources/Models/Alias/ServerDate.swift index 99a70b61..9d0f7a72 100644 --- a/Packages/Models/Sources/Models/Alias/ServerDate.swift +++ b/Packages/Models/Sources/Models/Alias/ServerDate.swift @@ -33,7 +33,7 @@ extension ServerDate { public var relativeFormatted: String { return Self.createdAtRelativeFormatter.localizedString(for: asDate, relativeTo: Date()) } - + public var shortDateFormatted: String { return Self.createdAtShortDateFormatted.string(from: asDate) } diff --git a/Packages/Models/Sources/Models/AppAccount.swift b/Packages/Models/Sources/Models/AppAccount.swift index 8232fd37..77070673 100644 --- a/Packages/Models/Sources/Models/AppAccount.swift +++ b/Packages/Models/Sources/Models/AppAccount.swift @@ -5,7 +5,7 @@ public struct AppAccount: Codable, Identifiable, Hashable { public let server: String public var accountName: String? public let oauthToken: OauthToken? - + public var key: String { if let oauthToken { return "\(server):\(oauthToken.createdAt)" @@ -13,14 +13,15 @@ public struct AppAccount: Codable, Identifiable, Hashable { return "\(server):anonymous" } } - + public var id: String { key } - + public init(server: String, accountName: String?, - oauthToken: OauthToken? = nil) { + oauthToken: OauthToken? = nil) + { self.server = server self.accountName = accountName self.oauthToken = oauthToken diff --git a/Packages/Models/Sources/Models/Poll.swift b/Packages/Models/Sources/Models/Poll.swift index e1ae83e3..b3921ebc 100644 --- a/Packages/Models/Sources/Models/Poll.swift +++ b/Packages/Models/Sources/Models/Poll.swift @@ -31,13 +31,13 @@ public struct Poll: Codable, Equatable, Hashable { public struct NullableString: Codable, Equatable, Hashable { public let value: String? - + public init(from decoder: Decoder) throws { do { let container = try decoder.singleValueContainer() - self.value = try container.decode(String.self) + value = try container.decode(String.self) } catch { - self.value = nil + value = nil } } } diff --git a/Packages/Models/Sources/Models/ServerFilter.swift b/Packages/Models/Sources/Models/ServerFilter.swift index c6584634..443f6def 100644 --- a/Packages/Models/Sources/Models/ServerFilter.swift +++ b/Packages/Models/Sources/Models/ServerFilter.swift @@ -6,15 +6,15 @@ public struct ServerFilter: Codable, Identifiable, Hashable { public let keyword: String public let wholeWord: Bool } - + public enum Context: String, Codable, CaseIterable { case home, notifications, `public`, thread, account } - + public enum Action: String, Codable, CaseIterable { case warn, hide } - + public let id: String public let title: String public let keywords: [Keyword] @@ -23,8 +23,8 @@ public struct ServerFilter: Codable, Identifiable, Hashable { public let expireIn: Int? } -extension ServerFilter.Context { - public var iconName: String { +public extension ServerFilter.Context { + var iconName: String { switch self { case .home: return "rectangle.on.rectangle" @@ -38,8 +38,8 @@ extension ServerFilter.Context { return "person.crop.circle" } } - - public var name: String { + + var name: String { switch self { case .home: return "Home and lists" @@ -55,8 +55,8 @@ extension ServerFilter.Context { } } -extension ServerFilter.Action { - public var label: String { +public extension ServerFilter.Action { + var label: String { switch self { case .warn: return "Hide with a warning" diff --git a/Packages/Models/Sources/Models/ServerPreferences.swift b/Packages/Models/Sources/Models/ServerPreferences.swift index 9c37ea5c..53a7ff1f 100644 --- a/Packages/Models/Sources/Models/ServerPreferences.swift +++ b/Packages/Models/Sources/Models/ServerPreferences.swift @@ -12,7 +12,7 @@ public struct ServerPreferences: Decodable { case showAll = "show_all" case hideAll = "hide_all" case hideSensitive = "default" - + public var description: LocalizedStringKey { switch self { case .showAll: diff --git a/Packages/Network/Sources/Network/Endpoint/ServerFilters.swift b/Packages/Network/Sources/Network/Endpoint/ServerFilters.swift index 793e30a5..055870c5 100644 --- a/Packages/Network/Sources/Network/Endpoint/ServerFilters.swift +++ b/Packages/Network/Sources/Network/Endpoint/ServerFilters.swift @@ -30,12 +30,12 @@ public enum ServerFilters: Endpoint { switch self { case let .addKeyword(_, keyword, wholeWord): return [.init(name: "keyword", value: keyword), - .init(name: "whole_word", value: wholeWord ? "true": "false")] + .init(name: "whole_word", value: wholeWord ? "true" : "false")] default: return nil } } - + public var jsonValue: Encodable? { switch self { case let .createFilter(json): @@ -53,11 +53,12 @@ public struct ServerFilterData: Encodable { public let context: [ServerFilter.Context] public let filterAction: ServerFilter.Action public let expireIn: Int? - + public init(title: String, context: [ServerFilter.Context], filterAction: ServerFilter.Action, - expireIn: Int?) { + expireIn: Int?) + { self.title = title self.context = context self.filterAction = filterAction diff --git a/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift b/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift index 5bf7948f..4ccb147f 100644 --- a/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift +++ b/Packages/Notifications/Sources/Notifications/NotificationsViewModel.swift @@ -76,7 +76,7 @@ class NotificationsViewModel: ObservableObject { newNotifications = newNotifications.filter { notification in !consolidatedNotifications.contains(where: { $0.id == notification.id }) } - self.notifications.append(contentsOf: newNotifications) + notifications.append(contentsOf: newNotifications) consolidatedNotifications.insert(contentsOf: newNotifications.consolidated(), at: 0) } withAnimation { @@ -98,7 +98,7 @@ class NotificationsViewModel: ObservableObject { maxId: lastId, types: queryTypes)) consolidatedNotifications.append(contentsOf: newNotifications.consolidated()) - self.notifications.append(contentsOf: newNotifications) + notifications.append(contentsOf: newNotifications) state = .display(notifications: consolidatedNotifications, nextPageState: newNotifications.count < 15 ? .none : .hasNextPage) } catch { state = .error(error: error) diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift index 1db0199c..09088980 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorMediaView.swift @@ -9,7 +9,7 @@ struct StatusEditorMediaView: View { @EnvironmentObject private var theme: Theme @ObservedObject var viewModel: StatusEditorViewModel @State private var editingContainer: StatusEditorMediaContainer? - + @State private var isErrorDisplayed: Bool = false var body: some View { @@ -142,7 +142,7 @@ struct StatusEditorMediaView: View { Label("action.view.error", systemImage: "exclamationmark.triangle") } } - + Button(role: .destructive) { withAnimation { viewModel.mediasImages.removeAll(where: { $0.id == container.id }) @@ -151,7 +151,7 @@ struct StatusEditorMediaView: View { Label("action.delete", systemImage: "trash") } } - + private func makeErrorView(error: ServerError) -> some View { ZStack { placeholderView @@ -159,11 +159,10 @@ struct StatusEditorMediaView: View { .foregroundColor(.red) } .alert("alert.error", isPresented: $isErrorDisplayed) { - Button("Ok", action: { }) + Button("Ok", action: {}) } message: { Text(error.error ?? "") } - } private var altMarker: some View { diff --git a/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift b/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift index c0acfc8e..7a21a1c0 100644 --- a/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift +++ b/Packages/Status/Sources/Status/Editor/Components/StatusEditorUTTypeSupported.swift @@ -1,9 +1,9 @@ +import AVFoundation import Foundation import PhotosUI import SwiftUI import UIKit import UniformTypeIdentifiers -import AVFoundation @MainActor enum StatusEditorUTTypeSupported: String, CaseIterable { @@ -74,7 +74,7 @@ struct MovieFileTranseferable: Transferable { private let url: URL var compressedVideoURL: URL? { get async { - return await withCheckedContinuation { continuation in + await withCheckedContinuation { continuation in let urlAsset = AVURLAsset(url: url, options: nil) guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else { continuation.resume(returning: nil) @@ -84,7 +84,7 @@ struct MovieFileTranseferable: Transferable { exportSession.outputURL = outputURL exportSession.outputFileType = .mp4 exportSession.shouldOptimizeForNetworkUse = true - exportSession.exportAsynchronously { () -> Void in + exportSession.exportAsynchronously { () in continuation.resume(returning: outputURL) } } diff --git a/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift b/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift index 0ef72f89..bc61cab8 100644 --- a/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift +++ b/Packages/Status/Sources/Status/Editor/StatusEditorViewModel.swift @@ -56,6 +56,7 @@ public class StatusEditorViewModel: ObservableObject { inflateSelectedMedias() } } + @Published var isMediasLoading: Bool = false @Published var mediasImages: [StatusEditorMediaContainer] = [] diff --git a/Packages/Status/Sources/Status/Row/StatusRowView.swift b/Packages/Status/Sources/Status/Row/StatusRowView.swift index 9d12403b..fa2c1ae5 100644 --- a/Packages/Status/Sources/Status/Row/StatusRowView.swift +++ b/Packages/Status/Sources/Status/Row/StatusRowView.swift @@ -22,7 +22,7 @@ public struct StatusRowView: View { var contextMenu: some View { StatusRowContextMenu(viewModel: viewModel) } - + public var body: some View { if viewModel.isFiltered, let filter = viewModel.filter { switch filter.filter.filterAction { @@ -225,13 +225,12 @@ public struct StatusRowView: View { private func makeStatusContentView(status: AnyStatus) -> some View { Group { if !status.spoilerText.asRawText.isEmpty { - HStack(alignment: .top) { Text("⚠︎") - .font(.system(.subheadline , weight:.bold)) + .font(.system(.subheadline, weight: .bold)) .foregroundColor(.secondary) EmojiTextApp(status.spoilerText, emojis: status.emojis, language: status.language) - .font(.system(.subheadline , weight:.bold)) + .font(.system(.subheadline, weight: .bold)) .foregroundColor(.secondary) .multilineTextAlignment(.leading) Spacer() @@ -247,7 +246,7 @@ public struct StatusRowView: View { .accessibility(label: viewModel.displaySpoiler ? Text("status.show-more") : Text("status.show-less")) .accessibilityHidden(true) } - .onTapGesture { // make whole row tapable to make up for smaller button size + .onTapGesture { // make whole row tapable to make up for smaller button size withAnimation { viewModel.displaySpoiler.toggle() } @@ -376,7 +375,8 @@ public struct StatusRowView: View { !viewModel.isCompact, theme.statusDisplayStyle == .large, status.content.statusesURLs.isEmpty, - status.mediaAttachments.isEmpty { + status.mediaAttachments.isEmpty + { StatusCardView(card: card) } }