diff --git a/iTorrent.xcodeproj/project.pbxproj b/iTorrent.xcodeproj/project.pbxproj index 76cfc999..3895b58c 100644 --- a/iTorrent.xcodeproj/project.pbxproj +++ b/iTorrent.xcodeproj/project.pbxproj @@ -1859,6 +1859,16 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = minimal; + SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES; + SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES; + SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES; + SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES; + SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES; + SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES; + SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES; + SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES; + SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES; + SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = NO; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; @@ -2001,6 +2011,16 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = ""; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_STRICT_CONCURRENCY = minimal; + SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES; + SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES; + SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES; + SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES; + SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES; + SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES; + SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES; + SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES; + SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES; + SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = NO; SWIFT_VERSION = 5.0; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -2150,6 +2170,16 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_STRICT_CONCURRENCY = minimal; + SWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES; + SWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES; + SWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES; + SWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES; + SWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES; + SWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES; + SWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES; + SWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES; + SWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES; + SWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = NO; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; diff --git a/iTorrent/Base/BaseNavigationController.swift b/iTorrent/Base/BaseNavigationController.swift index 8a6a8fba..a8e4dca5 100644 --- a/iTorrent/Base/BaseNavigationController.swift +++ b/iTorrent/Base/BaseNavigationController.swift @@ -8,6 +8,15 @@ import UIKit class BaseNavigationController: SANavigationController { + init() { + super.init(rootViewController: UIViewController()) + } + + @available(*, unavailable) + @MainActor required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func viewDidLoad() { super.viewDidLoad() navigationBar.prefersLargeTitles = true diff --git a/iTorrent/Core/AppDelegate/AppDelegate+RemoteConfig.swift b/iTorrent/Core/AppDelegate/AppDelegate+RemoteConfig.swift index 1f3c146c..10950e06 100644 --- a/iTorrent/Core/AppDelegate/AppDelegate+RemoteConfig.swift +++ b/iTorrent/Core/AppDelegate/AppDelegate+RemoteConfig.swift @@ -8,7 +8,7 @@ import UIKit #if canImport(FirebaseCore) import FirebaseRemoteConfig -var remoteConfig = RemoteConfig.remoteConfig() +let remoteConfig = RemoteConfig.remoteConfig() #endif extension AppDelegate { diff --git a/iTorrent/Core/SceneDelegate/SceneDelegate+AVPlayer.swift b/iTorrent/Core/SceneDelegate/SceneDelegate+AVPlayer.swift index 651c99c4..c819b39c 100644 --- a/iTorrent/Core/SceneDelegate/SceneDelegate+AVPlayer.swift +++ b/iTorrent/Core/SceneDelegate/SceneDelegate+AVPlayer.swift @@ -50,7 +50,7 @@ extension SceneDelegate { } private enum Keys { - static var avPlayerDeledates: Void? + nonisolated(unsafe) static var avPlayerDeledates: Void? } private var avPlayerDeledates: AVPlayerDelegates { diff --git a/iTorrent/Screens/Rss/Details/RssDetailsViewController.swift b/iTorrent/Screens/Rss/Details/RssDetailsViewController.swift index 66cdbeb1..9220a7f5 100644 --- a/iTorrent/Screens/Rss/Details/RssDetailsViewController.swift +++ b/iTorrent/Screens/Rss/Details/RssDetailsViewController.swift @@ -127,7 +127,7 @@ private extension RssDetailsViewController { else { return .allow } guard let url = navigationAction.request.url, - await UIApplication.shared.canOpenURL(url) + UIApplication.shared.canOpenURL(url) else { return .allow } Task { @MainActor in diff --git a/iTorrent/Screens/TorrentList/TorrentListViewController.swift b/iTorrent/Screens/TorrentList/TorrentListViewController.swift index 5ea3249d..46b57dfc 100644 --- a/iTorrent/Screens/TorrentList/TorrentListViewController.swift +++ b/iTorrent/Screens/TorrentList/TorrentListViewController.swift @@ -124,29 +124,56 @@ class TorrentListViewController: BaseViewController 0 else { return nil } + + if indexPaths.count == 1 { + guard let indexPath = indexPaths.first, + let torrentHandle = (viewModel.sections[indexPath.section].items[indexPath.item] as? TorrentListItemViewModel)?.torrentHandle + else { return nil } + + return UIContextMenuConfiguration { + TorrentDetailsViewModel.resolveVC(with: torrentHandle) + } actionProvider: { _ in + let start = UIAction(title: %"details.start", image: .init(systemName: "play.fill"), attributes: torrentHandle.snapshot.canResume ? [] : .hidden, handler: { _ in + torrentHandle.resume() + }) + let pause = UIAction(title: %"details.pause", image: .init(systemName: "pause.fill"), attributes: torrentHandle.snapshot.canPause ? [] : .hidden, handler: { _ in + torrentHandle.pause() + }) + let delete = UIAction(title: %"common.delete", image: UIImage(systemName: "trash.fill"), attributes: .destructive) { [unowned self] _ in + viewModel.removeTorrent(torrentHandle) + } + + return UIMenu(title: torrentHandle.snapshot.name, children: [ + start, + pause, + UIMenu(options: .displayInline, + children: [delete]) + ]) + } + } else { + let handles = indexPaths.compactMap { indexPath in (viewModel.sections[indexPath.section].items[indexPath.item] as? TorrentListItemViewModel)?.torrentHandle } + return UIContextMenuConfiguration { + nil + } actionProvider: { _ in + let start = UIAction(title: %"details.start", image: .init(systemName: "play.fill"), handler: { _ in + handles.forEach { $0.resume() } + }) + let pause = UIAction(title: %"details.pause", image: .init(systemName: "pause.fill"), handler: { _ in + handles.forEach { $0.pause() } + }) +// let delete = UIAction(title: %"common.delete", image: UIImage(systemName: "trash.fill"), attributes: .destructive) { [unowned self] _ in +// viewModel.removeTorrent(torrentHandle) +// } + + return UIMenu(children: [ + start, + pause, +// UIMenu(options: .displayInline, +// children: [delete]) + ]) } - return UIMenu(title: torrentHandle.snapshot.name, children: [ - start, - pause, - UIMenu(options: .displayInline, - children: [delete]) - ]) } } diff --git a/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift b/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift index bab23b19..af1d871e 100644 --- a/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift +++ b/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift @@ -14,7 +14,7 @@ extension NSNotification.Name { } struct PauseTorrentIntent: LiveActivityIntent { - static var title: LocalizedStringResource = "intent.pauseTorrent.title" + static let title: LocalizedStringResource = "intent.pauseTorrent.title" @Parameter(title: "intent.pauseTorrent.hash.title", description: "intent.pauseTorrent.hash.description") var torrentHash: String diff --git a/iTorrent/Services/TorrentService/Extensions/TorrentHandle+Extension.swift b/iTorrent/Services/TorrentService/Extensions/TorrentHandle+Extension.swift index 21a60bb3..65b3eb2e 100644 --- a/iTorrent/Services/TorrentService/Extensions/TorrentHandle+Extension.swift +++ b/iTorrent/Services/TorrentService/Extensions/TorrentHandle+Extension.swift @@ -9,35 +9,38 @@ import Combine import LibTorrent import MvvmFoundation -private var TorrentHandleUnthrottledUpdatePublisherKey: UInt8 = 0 -private var TorrentHandleUpdatePublisherKey: UInt8 = 0 -private var TorrentHandleRemovePublisherKey: UInt8 = 0 -private var TorrentHandleDisposeBagKey: UInt8 = 0 extension TorrentHandle { + private enum Keys { + nonisolated(unsafe) static var TorrentHandleUnthrottledUpdatePublisherKey: Void? + nonisolated(unsafe) static var TorrentHandleUpdatePublisherKey: Void? + nonisolated(unsafe) static var TorrentHandleRemovePublisherKey: Void? + nonisolated(unsafe) static var TorrentHandleDisposeBagKey: Void? + } + var disposeBag: DisposeBag { - guard let obj = objc_getAssociatedObject(self, &TorrentHandleDisposeBagKey) as? DisposeBag + guard let obj = objc_getAssociatedObject(self, &Keys.TorrentHandleDisposeBagKey) as? DisposeBag else { - objc_setAssociatedObject(self, &TorrentHandleDisposeBagKey, DisposeBag(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) - return objc_getAssociatedObject(self, &TorrentHandleDisposeBagKey) as! DisposeBag + objc_setAssociatedObject(self, &Keys.TorrentHandleDisposeBagKey, DisposeBag(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + return objc_getAssociatedObject(self, &Keys.TorrentHandleDisposeBagKey) as! DisposeBag } return obj } var __unthrottledUpdatePublisher: PassthroughSubject { - guard let obj = objc_getAssociatedObject(self, &TorrentHandleUnthrottledUpdatePublisherKey) as? PassthroughSubject + guard let obj = objc_getAssociatedObject(self, &Keys.TorrentHandleUnthrottledUpdatePublisherKey) as? PassthroughSubject else { - objc_setAssociatedObject(self, &TorrentHandleUnthrottledUpdatePublisherKey, PassthroughSubject(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) - return objc_getAssociatedObject(self, &TorrentHandleUnthrottledUpdatePublisherKey) as! PassthroughSubject + objc_setAssociatedObject(self, &Keys.TorrentHandleUnthrottledUpdatePublisherKey, PassthroughSubject(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + return objc_getAssociatedObject(self, &Keys.TorrentHandleUnthrottledUpdatePublisherKey) as! PassthroughSubject } return obj } var __updatePublisher: PassthroughSubject { - guard let obj = objc_getAssociatedObject(self, &TorrentHandleUpdatePublisherKey) as? PassthroughSubject + guard let obj = objc_getAssociatedObject(self, &Keys.TorrentHandleUpdatePublisherKey) as? PassthroughSubject else { - objc_setAssociatedObject(self, &TorrentHandleUpdatePublisherKey, PassthroughSubject(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) - return objc_getAssociatedObject(self, &TorrentHandleUpdatePublisherKey) as! PassthroughSubject + objc_setAssociatedObject(self, &Keys.TorrentHandleUpdatePublisherKey, PassthroughSubject(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + return objc_getAssociatedObject(self, &Keys.TorrentHandleUpdatePublisherKey) as! PassthroughSubject } return obj } @@ -55,10 +58,10 @@ extension TorrentHandle { } var removePublisher: PassthroughSubject { - guard let obj = objc_getAssociatedObject(self, &TorrentHandleRemovePublisherKey) as? PassthroughSubject + guard let obj = objc_getAssociatedObject(self, &Keys.TorrentHandleRemovePublisherKey) as? PassthroughSubject else { - objc_setAssociatedObject(self, &TorrentHandleRemovePublisherKey, PassthroughSubject(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) - return objc_getAssociatedObject(self, &TorrentHandleRemovePublisherKey) as! PassthroughSubject + objc_setAssociatedObject(self, &Keys.TorrentHandleRemovePublisherKey, PassthroughSubject(), objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN) + return objc_getAssociatedObject(self, &Keys.TorrentHandleRemovePublisherKey) as! PassthroughSubject } return obj } @@ -165,12 +168,12 @@ extension TorrentHandle { // MARK: - Metadata cache private extension TorrentHandle { - private enum Keys { - static var metadataKey: Void? + private enum MetaKeys { + nonisolated(unsafe) static var metadataKey: Void? } var _metadata: Metadata? { - get { objc_getAssociatedObject(self, &Keys.metadataKey) as? Metadata } - set { objc_setAssociatedObject(self, &Keys.metadataKey, newValue, .OBJC_ASSOCIATION_RETAIN) } + get { objc_getAssociatedObject(self, &MetaKeys.metadataKey) as? Metadata } + set { objc_setAssociatedObject(self, &MetaKeys.metadataKey, newValue, .OBJC_ASSOCIATION_RETAIN) } } } diff --git a/iTorrent/Utils/Extensions/Combine/Published+Codable.swift b/iTorrent/Utils/Extensions/Combine/Published+Codable.swift index faab6945..c46bdb7f 100644 --- a/iTorrent/Utils/Extensions/Combine/Published+Codable.swift +++ b/iTorrent/Utils/Extensions/Combine/Published+Codable.swift @@ -11,13 +11,13 @@ fileprivate enum CodingKeys: String, CodingKey { case value } -extension Published: Decodable where Value: Decodable { +extension Published: @retroactive Decodable where Value: Decodable { public init(from decoder: Decoder) throws { self.init(wrappedValue: try .init(from: decoder)) } } -extension Published: Encodable where Value: Encodable { +extension Published: @retroactive Encodable where Value: Encodable { public func encode(to encoder: Encoder) throws { try unofficialValue.encode(to: encoder) } diff --git a/iTorrent/Utils/Extensions/UIKit/UIView+LayerColors.swift b/iTorrent/Utils/Extensions/UIKit/UIView+LayerColors.swift index 4c179837..997425ac 100644 --- a/iTorrent/Utils/Extensions/UIKit/UIView+LayerColors.swift +++ b/iTorrent/Utils/Extensions/UIKit/UIView+LayerColors.swift @@ -9,8 +9,8 @@ import UIKit public extension UIView { private enum Keys { - static var borderColorAssociateKey: Void? - static var shadowColorAssociateKey: Void? + nonisolated(unsafe) static var borderColorAssociateKey: Void? + nonisolated(unsafe) static var shadowColorAssociateKey: Void? } var borderColor: UIColor? { diff --git a/iTorrent/Utils/Extensions/UIViewController/UIViewController+BottomSheet.swift b/iTorrent/Utils/Extensions/UIViewController/UIViewController+BottomSheet.swift index 4ca5dd97..335954e7 100644 --- a/iTorrent/Utils/Extensions/UIViewController/UIViewController+BottomSheet.swift +++ b/iTorrent/Utils/Extensions/UIViewController/UIViewController+BottomSheet.swift @@ -13,7 +13,7 @@ public extension UIViewController { func applyBottomSheetDetents(with scrollView: UIScrollView? = nil) -> AnyCancellable? { #if !os(visionOS) guard let sheet = sheetPresentationController else { return nil } - sheet.prefersGrabberVisible = true + sheet.prefersGrabberVisible = false /// If UIScrollView is not presented, /// or iOS 16 is not available, than set default detents