diff --git a/IceCubesApp.xcodeproj/project.pbxproj b/IceCubesApp.xcodeproj/project.pbxproj index 04b91711..a7933098 100644 --- a/IceCubesApp.xcodeproj/project.pbxproj +++ b/IceCubesApp.xcodeproj/project.pbxproj @@ -686,7 +686,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1420; - LastUpgradeCheck = 1420; + LastUpgradeCheck = 1500; TargetAttributes = { 9F2A5415296AB631009B2D7C = { CreatedOnToolsVersion = 14.2; @@ -1089,6 +1089,7 @@ baseConfigurationReference = DD31E2E5297FB68B00A4BE29 /* IceCubesApp.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1123,6 +1124,7 @@ DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -1142,6 +1144,7 @@ ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_STRICT_CONCURRENCY = complete; }; name = Debug; }; @@ -1150,6 +1153,7 @@ baseConfigurationReference = 9F7D939529800B0300EE6B7A /* IceCubesApp-release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -1184,6 +1188,7 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; @@ -1196,6 +1201,7 @@ MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_STRICT_CONCURRENCY = complete; }; name = Release; }; @@ -1246,7 +1252,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -1299,7 +1305,7 @@ SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_STRICT_CONCURRENCY = targeted; + SWIFT_STRICT_CONCURRENCY = complete; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 70d53bd7..87ce942b 100644 --- a/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/IceCubesApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -50,8 +50,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/RevenueCat/purchases-ios.git", "state" : { - "revision" : "4601c1e0c246f3d74094229737e894a9f2339e6a", - "version" : "4.25.7" + "revision" : "78d2f159d6d647f26ae5a9dd38276ea06403e7ec", + "version" : "4.25.9" } }, { @@ -86,8 +86,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/siteline/SwiftUI-Introspect.git", "state" : { - "revision" : "9da0f9b7bffe96a7c98a0128f1e214f62728a39a", - "version" : "0.11.1" + "revision" : "3ba734dd20faada0e3234b68e78db97005315f0e", + "version" : "1.0.0" } }, { diff --git a/IceCubesApp.xcodeproj/xcshareddata/xcschemes/IceCubesApp.xcscheme b/IceCubesApp.xcodeproj/xcshareddata/xcschemes/IceCubesApp.xcscheme index 61077478..ad66ca1b 100644 --- a/IceCubesApp.xcodeproj/xcshareddata/xcschemes/IceCubesApp.xcscheme +++ b/IceCubesApp.xcodeproj/xcshareddata/xcschemes/IceCubesApp.xcscheme @@ -1,6 +1,6 @@ Int { + nonisolated func numberOfPreviewItems(in _: QLPreviewController) -> Int { return urls.count } - func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { + nonisolated func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem { return urls[index] as QLPreviewItem } } extension AppQLPreviewController: QLPreviewControllerDelegate { - func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode { + nonisolated func previewController(_: QLPreviewController, editingModeFor _: QLPreviewItem) -> QLPreviewItemEditingMode { .createCopy } - func previewControllerWillDismiss(_: QLPreviewController) { - dismiss(animated: true) + nonisolated func previewControllerWillDismiss(_: QLPreviewController) { + Task { @MainActor in + dismiss(animated: true) + } } - func previewControllerDidDismiss(_: QLPreviewController) { - dismiss(animated: true) + nonisolated func previewControllerDidDismiss(_: QLPreviewController) { + Task { @MainActor in + dismiss(animated: true) + } } } diff --git a/IceCubesApp/App/SafariRouter.swift b/IceCubesApp/App/SafariRouter.swift index 3a1ebe49..bd5088a0 100644 --- a/IceCubesApp/App/SafariRouter.swift +++ b/IceCubesApp/App/SafariRouter.swift @@ -4,11 +4,12 @@ import SafariServices import SwiftUI extension View { - func withSafariRouter() -> some View { + @MainActor func withSafariRouter() -> some View { modifier(SafariRouter()) } } +@MainActor private struct SafariRouter: ViewModifier { @EnvironmentObject private var theme: Theme @EnvironmentObject private var preferences: UserPreferences @@ -56,6 +57,7 @@ private struct SafariRouter: ViewModifier { } } +@MainActor private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewControllerDelegate { var windowScene: UIWindowScene? let viewController: UIViewController = .init() @@ -99,10 +101,12 @@ private class InAppSafariManager: NSObject, ObservableObject, SFSafariViewContro return window } - func safariViewControllerDidFinish(_: SFSafariViewController) { - window?.resignKey() - window?.isHidden = false - window = nil + nonisolated func safariViewControllerDidFinish(_: SFSafariViewController) { + Task { @MainActor in + window?.resignKey() + window?.isHidden = false + window = nil + } } } diff --git a/IceCubesApp/App/SideBarView.swift b/IceCubesApp/App/SideBarView.swift index d3eb90a5..1e6ee060 100644 --- a/IceCubesApp/App/SideBarView.swift +++ b/IceCubesApp/App/SideBarView.swift @@ -176,7 +176,7 @@ private struct SideBarIcon: View { } extension View { - func hideKeyboard() { + @MainActor func hideKeyboard() { let resign = #selector(UIResponder.resignFirstResponder) UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil) } diff --git a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift index 345b1203..ebb3dedf 100644 --- a/IceCubesApp/App/Tabs/Settings/SettingsTab.swift +++ b/IceCubesApp/App/Tabs/Settings/SettingsTab.swift @@ -307,7 +307,9 @@ struct SettingsTabs: View { _ = preferences.remoteLocalTimelines.remove(at: index) } } - .onMove(perform: moveTimelineItems) + .onMove(perform: { indices, newOffset in + moveTimelineItems(from: indices, to: newOffset) + }) .listRowBackground(theme.primaryBackgroundColor) Button { routerPath.presentedSheet = .addRemoteLocalTimeline @@ -324,6 +326,7 @@ struct SettingsTabs: View { } } + private func moveTimelineItems(from source: IndexSet, to destination: Int) { preferences.remoteLocalTimelines.move(fromOffsets: source, toOffset: destination) } diff --git a/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift b/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift index 22f08b57..7b31bb92 100644 --- a/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift +++ b/IceCubesApp/App/Tabs/Settings/TranslationSettingsView.swift @@ -25,7 +25,9 @@ struct TranslationSettingsView: View { SecureField("settings.translation.user-api-key", text: $apiKey) .textContentType(.password) } - .onAppear(perform: readValue) + .onAppear { + readValue() + } .listRowBackground(theme.primaryBackgroundColor) if apiKey.isEmpty { diff --git a/IceCubesApp/App/Tabs/Tabs.swift b/IceCubesApp/App/Tabs/Tabs.swift index f65e3df3..289600bf 100644 --- a/IceCubesApp/App/Tabs/Tabs.swift +++ b/IceCubesApp/App/Tabs/Tabs.swift @@ -5,12 +5,13 @@ import Foundation import Status import SwiftUI +@MainActor enum Tab: Int, Identifiable, Hashable { case timeline, notifications, mentions, explore, messages, settings, other case trending, federated, local case profile - var id: Int { + nonisolated var id: Int { rawValue } diff --git a/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift b/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift index 29459ace..9994c686 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/DesignSystem.swift @@ -1,7 +1,8 @@ import Foundation +@MainActor public extension CGFloat { - static var layoutPadding: CGFloat = 20 + static let layoutPadding: CGFloat = 20 static let dividerPadding: CGFloat = 2 static let statusColumnsSpacing: CGFloat = 8 static let secondaryColumnWidth: CGFloat = 400 diff --git a/Packages/DesignSystem/Sources/DesignSystem/Resources/Colors.swift b/Packages/DesignSystem/Sources/DesignSystem/Resources/Colors.swift index 67e1bc0b..85866722 100644 --- a/Packages/DesignSystem/Sources/DesignSystem/Resources/Colors.swift +++ b/Packages/DesignSystem/Sources/DesignSystem/Resources/Colors.swift @@ -14,7 +14,7 @@ public extension Color { } static var label: Color { - Color("label", bundle: .module) + Color(.label) } } diff --git a/Packages/Env/Sources/Env/SoundEffectManager.swift b/Packages/Env/Sources/Env/SoundEffectManager.swift index 1dc69096..fb626e0f 100644 --- a/Packages/Env/Sources/Env/SoundEffectManager.swift +++ b/Packages/Env/Sources/Env/SoundEffectManager.swift @@ -1,32 +1,75 @@ import AVKit import CoreHaptics import UIKit +import AudioToolbox +@MainActor public class SoundEffectManager { public static let shared: SoundEffectManager = .init() - - public enum SoundEffect: String { - case pull, refresh - case tootSent - case tabSelection - case bookmark, boost, favorite, share + + public enum SoundEffect: String, CaseIterable { + case pull, refresh, tootSent, tabSelection, bookmark, boost, favorite, share + } + + var pullId: SystemSoundID = 0 + var refreshId: SystemSoundID = 1 + var tootSentId: SystemSoundID = 2 + var tabSelectionId: SystemSoundID = 3 + var bookmarkId: SystemSoundID = 4 + var boostId: SystemSoundID = 5 + var favoriteId: SystemSoundID = 6 + var shareId: SystemSoundID = 7 + + private let userPreferences = UserPreferences.shared + + private init() { + registerSounds() } - private let userPreferences = UserPreferences.shared - - private var currentPlayer: AVAudioPlayer? - - private init() {} - - @MainActor + private func registerSounds() { + for effect in SoundEffect.allCases { + if let url = Bundle.main.url(forResource: effect.rawValue, withExtension: "wav") { + switch effect { + case .pull: + AudioServicesCreateSystemSoundID(url as CFURL, &pullId) + case .refresh: + AudioServicesCreateSystemSoundID(url as CFURL, &refreshId) + case .tootSent: + AudioServicesCreateSystemSoundID(url as CFURL, &tootSentId) + case .tabSelection: + AudioServicesCreateSystemSoundID(url as CFURL, &tabSelectionId) + case .bookmark: + AudioServicesCreateSystemSoundID(url as CFURL, &bookmarkId) + case .boost: + AudioServicesCreateSystemSoundID(url as CFURL, &boostId) + case .favorite: + AudioServicesCreateSystemSoundID(url as CFURL, &favoriteId) + case .share: + AudioServicesCreateSystemSoundID(url as CFURL, &shareId) + } + } + } + } + public func playSound(of type: SoundEffect) { guard userPreferences.soundEffectEnabled else { return } - if let url = Bundle.main.url(forResource: type.rawValue, withExtension: "wav") { - try? AVAudioSession.sharedInstance().setCategory(.ambient) - try? AVAudioSession.sharedInstance().setActive(true) - currentPlayer = try? .init(contentsOf: url) - currentPlayer?.prepareToPlay() - currentPlayer?.play() + switch type { + case .pull: + AudioServicesPlaySystemSound(pullId) + case .refresh: + AudioServicesPlaySystemSound(refreshId) + case .tootSent: + AudioServicesPlaySystemSound(tootSentId) + case .tabSelection: + AudioServicesPlaySystemSound(tabSelectionId) + case .bookmark: + AudioServicesPlaySystemSound(bookmarkId) + case .boost: + AudioServicesPlaySystemSound(boostId) + case .favorite: + AudioServicesPlaySystemSound(favoriteId) + case .share: + AudioServicesPlaySystemSound(shareId) } } } diff --git a/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme b/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme index d245aea2..31a6f207 100644 --- a/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme +++ b/Packages/Status/.swiftpm/xcode/xcshareddata/xcschemes/Status.xcscheme @@ -1,6 +1,6 @@