From 12ecbe42948a6a7bf64e48b31eeadbdf23d55d57 Mon Sep 17 00:00:00 2001 From: Daniil Vinogradov Date: Sun, 30 Jun 2024 23:24:33 +0200 Subject: [PATCH] LiveActivity pause button added --- .../ProgressWidgetLiveActivity.swift | 65 +++++++--- iTorrent.xcodeproj/project.pbxproj | 42 ++++++- iTorrent/Core/Assets/Localizable.xcstrings | 51 ++++++++ .../Core/SceneDelegate/SceneDelegate.swift | 1 + .../Intents/PauseTorrentIntent.swift | 26 ++++ .../IntentsService/IntentsService.swift | 25 ++++ iTorrent/Services/LiveActivityService.swift | 17 ++- .../LiveActivityService.swift | 112 ++++++++++++++++++ iTorrent/Utils/NSUserDefaultItem.swift | 2 +- iTorrent/Utils/UserDefaultItem.swift | 2 +- iTorrent/Utils/UserDefaults+AppGroup.swift | 14 +++ 11 files changed, 334 insertions(+), 23 deletions(-) create mode 100644 iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift create mode 100644 iTorrent/Services/IntentsService/IntentsService.swift create mode 100644 iTorrent/Services/LiveActivityService/LiveActivityService.swift create mode 100644 iTorrent/Utils/UserDefaults+AppGroup.swift diff --git a/ProgressWidget/ProgressWidgetLiveActivity.swift b/ProgressWidget/ProgressWidgetLiveActivity.swift index e068913e..fa42630d 100644 --- a/ProgressWidget/ProgressWidgetLiveActivity.swift +++ b/ProgressWidget/ProgressWidgetLiveActivity.swift @@ -7,14 +7,13 @@ #if canImport(ActivityKit) import ActivityKit +import AppIntents +import MarqueeText import SwiftUI import WidgetKit -import MarqueeText - - struct ProgressWidgetLiveActivity: Widget { - static var userDefaults: UserDefaults { UserDefaults(suiteName: "group.itorrent.life-activity") ?? .standard } + static var userDefaults: UserDefaults { .itorrentGroup } static var tintColor: UIColor { guard let data = Self.userDefaults.data(forKey: "preferencesTintColor") @@ -26,7 +25,7 @@ struct ProgressWidgetLiveActivity: Widget { let config = ActivityConfiguration(for: ProgressWidgetAttributes.self) { context in // Lock screen/banner UI goes here - if #available(iOSApplicationExtension 18, *) { + if #available(iOS 18, *) { #if XCODE16 ProgressWidgetLiveActivityWatchSupportContent(context: context) .tint(Color(uiColor: ProgressWidgetLiveActivity.tintColor)) @@ -37,7 +36,7 @@ struct ProgressWidgetLiveActivity: Widget { .padding() #endif - } else { + } else { ProgressWidgetLiveActivityContent(context: context) .tint(Color(uiColor: Self.tintColor)) .padding() @@ -64,6 +63,17 @@ struct ProgressWidgetLiveActivity: Widget { if context.state.state == .downloading { Text(context.state.timeRemainig) } + + if #available(iOS 17.0, *), + context.state.state == .seeding + { + let intent = { + let intent = PauseTorrentIntent() + intent.torrentHash = context.attributes.hash + return intent + }() + PauseButton(intent: intent) + } } ProgressView(value: context.state.progress) .progressViewStyle(.linear) @@ -92,7 +102,6 @@ struct ProgressWidgetLiveActivity: Widget { return config } } - } #if XCODE16 @@ -124,9 +133,9 @@ struct ProgressWidgetLiveActivityWatchSupportContent: View { Text(context.state.state.name) Spacer() case .downloading: - Text(String("\(context.state.downSpeed.bitrateToHumanReadable)/s ↓")) - Spacer() - Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s ↑")) + Text(String("\(context.state.downSpeed.bitrateToHumanReadable)/s ↓")) + Spacer() + Text(String("\(context.state.upSpeed.bitrateToHumanReadable)/s ↑")) case .finished: Text(context.state.state.name) Spacer() @@ -167,6 +176,17 @@ struct ProgressWidgetLiveActivityContent: View { HStack { Text(context.attributes.name) Spacer() + + if #available(iOS 17.0, *), + context.state.state == .seeding + { + let intent = { + let intent = PauseTorrentIntent() + intent.torrentHash = context.attributes.hash + return intent + }() + PauseButton(intent: intent) + } } HStack { switch context.state.state { @@ -203,6 +223,17 @@ struct ProgressWidgetLiveActivityContent: View { } } +@available(iOS 17.0, *) +struct PauseButton: View { + let intent: any LiveActivityIntent + + var body: some View { + Button(intent: intent) { + Image(systemName: "pause.fill") + } + } +} + struct LeadingView: View { @State var context: ActivityViewContext @@ -240,19 +271,19 @@ struct TrailingView: View { } } -//#Preview("Progress", +// #Preview("Progress", // as: .dynamicIsland(.compact), // using: ProgressWidgetAttributes(name: "Test torrent", hash: "") -//) { +// ) { // ProgressWidgetLiveActivity() -//} contentStates: { +// } contentStates: { // ProgressWidgetAttributes.ContentState(progress: 0.2, downSpeed: 2000, upSpeed: 1000, timeRemainig: "Осталось САСАТБ", timeStamp: .now) -//} +// } -//#Preview("Notification", as: .content, using: ProgressWidgetAttributes(name: "Test torrent", hash: "")) { +// #Preview("Notification", as: .content, using: ProgressWidgetAttributes(name: "Test torrent", hash: "")) { // ProgressWidgetLiveActivity() -//} contentStates: { +// } contentStates: { // ProgressWidgetAttributes.ContentState(progress: 0.2, downSpeed: 2000, upSpeed: 1000, timeRemainig: "Осталось САСАТБ", timeStamp: .now) // ProgressWidgetAttributes.ContentState(progress: 0.7, downSpeed: 12000000, upSpeed: 1000000, timeRemainig: "Осталось САСАТБ", timeStamp: .now) -//} +// } #endif diff --git a/iTorrent.xcodeproj/project.pbxproj b/iTorrent.xcodeproj/project.pbxproj index c08a4633..506b93ed 100644 --- a/iTorrent.xcodeproj/project.pbxproj +++ b/iTorrent.xcodeproj/project.pbxproj @@ -10,6 +10,9 @@ 7C013DE32C28AA3C0026A11B /* sound.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 7C013DE22C28A97F0026A11B /* sound.m4a */; }; 7C013DE42C2F38AA0026A11B /* LocalizationAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1352D1D2BBC2F7F00104E7B /* LocalizationAttribute.swift */; }; 7C013DE52C2F41760026A11B /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D1AA00CF2AFAC7D200B74629 /* Localizable.xcstrings */; }; + 7C1C08AA2C31F2F800569B45 /* PauseTorrentIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1C08A92C31F2F000569B45 /* PauseTorrentIntent.swift */; }; + 7C1C08AB2C31F31900569B45 /* PauseTorrentIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1C08A92C31F2F000569B45 /* PauseTorrentIntent.swift */; }; + 7C1C08AD2C31FEA400569B45 /* IntentsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C1C08AC2C31FEA000569B45 /* IntentsService.swift */; }; 7C3142D02C317E6400397E82 /* MarqueeText in Frameworks */ = {isa = PBXBuildFile; productRef = 7C3142CF2C317E6400397E82 /* MarqueeText */; }; 7C4CF2FA2BDE712D0078FEA1 /* UnityAdsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4CF2F92BDE712D0078FEA1 /* UnityAdsManager.swift */; }; 7C4CF2FC2BDE78F50078FEA1 /* AdView+Unity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C4CF2FB2BDE78F50078FEA1 /* AdView+Unity.swift */; }; @@ -55,6 +58,8 @@ 7CB2639C2C0671320083C052 /* Publisher+UI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB2639B2C0671320083C052 /* Publisher+UI.swift */; }; 7CB2639E2C0A5B420083C052 /* BaseSafariViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB2639D2C0A5B420083C052 /* BaseSafariViewController.swift */; }; 7CB6F6CE2BD82BAB00D0813B /* FileSharingPreferencesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CB6F6CD2BD82BAB00D0813B /* FileSharingPreferencesViewModel.swift */; }; + 7CBDBAAD2C31EF0C008C986B /* UserDefaults+AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */; }; + 7CBDBAAE2C31EF52008C986B /* UserDefaults+AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */; }; 7CC411582BD2DCF800CA8B13 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 7CC411572BD2DCF800CA8B13 /* GoogleService-Info.plist */; }; 7CC4115B2BD2DE3800CA8B13 /* FirebaseAnalyticsWithoutAdIdSupport in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, xros, ); productRef = 7CC4115A2BD2DE3800CA8B13 /* FirebaseAnalyticsWithoutAdIdSupport */; }; 7CC4115D2BD2DE3800CA8B13 /* FirebaseCrashlytics in Frameworks */ = {isa = PBXBuildFile; platformFilters = (ios, xros, ); productRef = 7CC4115C2BD2DE3800CA8B13 /* FirebaseCrashlytics */; }; @@ -239,6 +244,8 @@ /* Begin PBXFileReference section */ 7C013DE22C28A97F0026A11B /* sound.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = sound.m4a; sourceTree = ""; }; + 7C1C08A92C31F2F000569B45 /* PauseTorrentIntent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PauseTorrentIntent.swift; sourceTree = ""; }; + 7C1C08AC2C31FEA000569B45 /* IntentsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntentsService.swift; sourceTree = ""; }; 7C4CF2F22BDD586C0078FEA1 /* UnityAds.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = UnityAds.xcframework; path = Submodules/UnityAds.xcframework; sourceTree = ""; }; 7C4CF2F92BDE712D0078FEA1 /* UnityAdsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnityAdsManager.swift; sourceTree = ""; }; 7C4CF2FB2BDE78F50078FEA1 /* AdView+Unity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AdView+Unity.swift"; sourceTree = ""; }; @@ -286,6 +293,7 @@ 7CB2639B2C0671320083C052 /* Publisher+UI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Publisher+UI.swift"; sourceTree = ""; }; 7CB2639D2C0A5B420083C052 /* BaseSafariViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseSafariViewController.swift; sourceTree = ""; }; 7CB6F6CD2BD82BAB00D0813B /* FileSharingPreferencesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileSharingPreferencesViewModel.swift; sourceTree = ""; }; + 7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+AppGroup.swift"; sourceTree = ""; }; 7CC411572BD2DCF800CA8B13 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 7CC411612BD319AE00CA8B13 /* AppDelegate+RemoteConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+RemoteConfig.swift"; sourceTree = ""; }; 7CC411632BD326C300CA8B13 /* AppDelegate+Firebase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+Firebase.swift"; sourceTree = ""; }; @@ -465,6 +473,31 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 7C1C08AE2C31FEF700569B45 /* IntentsService */ = { + isa = PBXGroup; + children = ( + 7C1C08AF2C31FEFD00569B45 /* Intents */, + 7C1C08AC2C31FEA000569B45 /* IntentsService.swift */, + ); + path = IntentsService; + sourceTree = ""; + }; + 7C1C08AF2C31FEFD00569B45 /* Intents */ = { + isa = PBXGroup; + children = ( + 7C1C08A92C31F2F000569B45 /* PauseTorrentIntent.swift */, + ); + path = Intents; + sourceTree = ""; + }; + 7C3142D42C31ED4600397E82 /* LiveActivityService */ = { + isa = PBXGroup; + children = ( + 7CF6DA3D2C0F9DC40033D03F /* LiveActivityService.swift */, + ); + path = LiveActivityService; + sourceTree = ""; + }; 7C4CF2F82BDE711A0078FEA1 /* Ads */ = { isa = PBXGroup; children = ( @@ -958,6 +991,7 @@ D1AA00CA2AFA8A9200B74629 /* UserDefaultItem.swift */, 7C5FBE222BBDD1B60069E5A0 /* NSUserDefaultItem.swift */, 7CE25BA62C24A848007B2FD7 /* CircularAnimation.swift */, + 7CBDBAAC2C31EF0C008C986B /* UserDefaults+AppGroup.swift */, ); path = Utils; sourceTree = ""; @@ -1059,6 +1093,8 @@ D1A226F02AEF018500669D6D /* Services */ = { isa = PBXGroup; children = ( + 7C1C08AE2C31FEF700569B45 /* IntentsService */, + 7C3142D42C31ED4600397E82 /* LiveActivityService */, D1B99D842BEE5E4100F51514 /* Patreon */, 7C4CF2F82BDE711A0078FEA1 /* Ads */, D1DB71892BD915F4007F9267 /* ImageCache */, @@ -1070,7 +1106,6 @@ 7C5FBE182BBC91E70069E5A0 /* NetworkMonitoringService.swift */, D1352D2B2BBD6E0E00104E7B /* MemorySpaceManager.swift */, D1352D3B2BBD7F8800104E7B /* TorrentMonitoringService.swift */, - 7CF6DA3D2C0F9DC40033D03F /* LiveActivityService.swift */, ); path = Services; sourceTree = ""; @@ -1447,7 +1482,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7C1C08AA2C31F2F800569B45 /* PauseTorrentIntent.swift in Sources */, 7C5FBE442BC0ADC90069E5A0 /* ProgressWidgetLiveActivity.swift in Sources */, + 7CBDBAAE2C31EF52008C986B /* UserDefaults+AppGroup.swift in Sources */, 7C5FBE422BC0ADC90069E5A0 /* ProgressWidgetBundle.swift in Sources */, 7C5FBE532BC0B2780069E5A0 /* SpeedFormat.swift in Sources */, 7C5FBE562BC0B35C0069E5A0 /* ProgressWidgetAttributes.swift in Sources */, @@ -1493,6 +1530,7 @@ 7C5FBE702BC2EF8B0069E5A0 /* EditTextViewController.swift in Sources */, 7CFEBEA02BC6F3CD0013233F /* Date+Extensions.swift in Sources */, D1DB718B2BD91606007F9267 /* ImageCache.swift in Sources */, + 7CBDBAAD2C31EF0C008C986B /* UserDefaults+AppGroup.swift in Sources */, D1B1BEC92AFE25AE0030C2A4 /* TorrentTrackersViewController.swift in Sources */, D1DB718D2BD9165C007F9267 /* ImageLoader.swift in Sources */, 7C5FBE292BBDD4030069E5A0 /* PRColorPickerViewModel.swift in Sources */, @@ -1548,6 +1586,7 @@ D135C5992AEFB96100440680 /* TorrentDetailsViewController.swift in Sources */, D1048D8B2BBEB6DE0027EF2F /* CombineLatest.swift in Sources */, D11138572AF976CE008907F7 /* TorrentAddDirectoryItemViewModel.swift in Sources */, + 7C1C08AB2C31F31900569B45 /* PauseTorrentIntent.swift in Sources */, D1CAB8872AF3B52E00EB6AFF /* ToggleCellViewModel.swift in Sources */, D11BE5492AFBA03D00780C1B /* PRButtonView.swift in Sources */, 7CFEBE8B2BC439CF0013233F /* RssChannelItemCellViewModel.swift in Sources */, @@ -1556,6 +1595,7 @@ D1352D322BBD720C00104E7B /* PRStorageCell.swift in Sources */, 7C5FBE232BBDD1B60069E5A0 /* NSUserDefaultItem.swift in Sources */, D1EFCD092AF56A4C00D33A7A /* TorrentFilesViewController.swift in Sources */, + 7C1C08AD2C31FEA400569B45 /* IntentsService.swift in Sources */, 7CFEBE832BC434A10013233F /* BaseCollectionViewController.swift in Sources */, D1A5D0CE2BE52728003E05D5 /* AdView+Google.swift in Sources */, D1352D3A2BBD747100104E7B /* PortionBarLabels.swift in Sources */, diff --git a/iTorrent/Core/Assets/Localizable.xcstrings b/iTorrent/Core/Assets/Localizable.xcstrings index 7f4c3040..21c622bd 100644 --- a/iTorrent/Core/Assets/Localizable.xcstrings +++ b/iTorrent/Core/Assets/Localizable.xcstrings @@ -1,6 +1,9 @@ { "sourceLanguage" : "en", "strings" : { + "" : { + + }, "%@ of %@ (%@)" : { "localizations" : { "en" : { @@ -1156,6 +1159,54 @@ } } }, + "intent.pauseTorrent.hash.description" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Hash of the Torrent that needs to be stopped" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хэш торрента который требуется остановить" + } + } + } + }, + "intent.pauseTorrent.hash.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Torrent hash" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Хэш торрента" + } + } + } + }, + "intent.pauseTorrent.title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pause Torrent" + } + }, + "ru" : { + "stringUnit" : { + "state" : "translated", + "value" : "Остановить торрент" + } + } + } + }, "iTorrent" : { "localizations" : { "en" : { diff --git a/iTorrent/Core/SceneDelegate/SceneDelegate.swift b/iTorrent/Core/SceneDelegate/SceneDelegate.swift index 0da85f3c..d0676f74 100644 --- a/iTorrent/Core/SceneDelegate/SceneDelegate.swift +++ b/iTorrent/Core/SceneDelegate/SceneDelegate.swift @@ -29,6 +29,7 @@ class SceneDelegate: MvvmSceneDelegate { container.registerDaemon(factory: RssFeedProvider.init) container.registerDaemon(factory: WebServerService.init) container.registerDaemon(factory: LiveActivityService.init) + container.registerDaemon(factory: IntentsService.init) container.registerDaemon(factory: AdsManager.init) } diff --git a/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift b/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift new file mode 100644 index 00000000..bab23b19 --- /dev/null +++ b/iTorrent/Services/IntentsService/Intents/PauseTorrentIntent.swift @@ -0,0 +1,26 @@ +// +// PauseTorrentIntent.swift +// iTorrent +// +// Created by Даниил Виноградов on 30.06.2024. +// + +import AppIntents + +extension NSNotification.Name { + static var pauseTorrent: Self { + .init("pauseTorrent") + } +} + +struct PauseTorrentIntent: LiveActivityIntent { + static var title: LocalizedStringResource = "intent.pauseTorrent.title" + + @Parameter(title: "intent.pauseTorrent.hash.title", description: "intent.pauseTorrent.hash.description") + var torrentHash: String + + func perform() async throws -> some IntentResult { + NotificationCenter.default.post(name: .pauseTorrent, object: torrentHash) + return .result() + } +} diff --git a/iTorrent/Services/IntentsService/IntentsService.swift b/iTorrent/Services/IntentsService/IntentsService.swift new file mode 100644 index 00000000..4a1a61df --- /dev/null +++ b/iTorrent/Services/IntentsService/IntentsService.swift @@ -0,0 +1,25 @@ +// +// IntentsService.swift +// iTorrent +// +// Created by Даниил Виноградов on 30.06.2024. +// + +import MvvmFoundation +import Foundation + +actor IntentsService { + init() { + disposeBag.bind { + NotificationCenter.default.publisher(for: .pauseTorrent).sink { notification in + guard let hash = notification.object as? String, + let torrentHandle = TorrentService.shared.torrents.first(where: { $0.snapshot.infoHashes.best.hex == hash }) + else { return } + + torrentHandle.pause() + } + } + } + + private let disposeBag = DisposeBag() +} diff --git a/iTorrent/Services/LiveActivityService.swift b/iTorrent/Services/LiveActivityService.swift index 70882bac..90b0b2a4 100644 --- a/iTorrent/Services/LiveActivityService.swift +++ b/iTorrent/Services/LiveActivityService.swift @@ -13,16 +13,27 @@ import MvvmFoundation import UIKit #endif +extension UserDefaults { + @objc dynamic var greetingsCount: Int { + return integer(forKey: "greetingsCount") + } +} + actor LiveActivityService { init() { -#if canImport(ActivityKit) disposeBag.bind { +#if canImport(ActivityKit) TorrentService.shared.updateNotifier .sink { [unowned self] updateModel in Task { await updateLiveActivity(with: updateModel.handle.snapshot) } } - } + + UserDefaults.standard.publisher(for: <#T##KeyPath#>) + NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { notification in + notification. + } #endif + } } private let disposeBag = DisposeBag() @@ -105,7 +116,7 @@ private extension TorrentHandle.State { } var shouldShowLiveActivity: Bool { - let notShow: [Self] = [ .finished, .paused ] + let notShow: [Self] = [.finished, .paused] return !notShow.contains(self) } } diff --git a/iTorrent/Services/LiveActivityService/LiveActivityService.swift b/iTorrent/Services/LiveActivityService/LiveActivityService.swift new file mode 100644 index 00000000..d88dc60d --- /dev/null +++ b/iTorrent/Services/LiveActivityService/LiveActivityService.swift @@ -0,0 +1,112 @@ +// +// LiveActivityService.swift +// iTorrent +// +// Created by Даниил Виноградов on 06.04.2024. +// + +#if canImport(ActivityKit) +import ActivityKit +import Combine +import LibTorrent +import MvvmFoundation +import UIKit +#endif + +actor LiveActivityService { + init() { + disposeBag.bind { +#if canImport(ActivityKit) + TorrentService.shared.updateNotifier + .sink { [unowned self] updateModel in + Task { await updateLiveActivity(with: updateModel.handle.snapshot) } + } +#endif + } + } + + private let disposeBag = DisposeBag() + @Injected private var torrentService: TorrentService +} + +#if canImport(ActivityKit) +private extension LiveActivityService { + func updateLiveActivity(with snapshot: TorrentHandle.Snapshot) async { + if #available(iOS 16.1, *) { + guard ActivityAuthorizationInfo().areActivitiesEnabled + else { return } + + for activity in Activity.activities { + if activity.attributes.name == snapshot.name { + if snapshot.friendlyState.shouldShowLiveActivity { + if #available(iOS 16.2, *) { + await activity.update(.init(state: snapshot.toLiveActivityState, staleDate: .now + 10)) + } else { + await activity.update(using: snapshot.toLiveActivityState) + } + return + } else { + await activity.end(dismissalPolicy: .immediate) + return + } + } + } + + if snapshot.friendlyState.shouldShowLiveActivity { + showLiveActivity(with: snapshot) + } + } + } + + func showLiveActivity(with snapshot: TorrentHandle.Snapshot) { + if #available(iOS 16.1, *) { + let attributes = ProgressWidgetAttributes(name: snapshot.name, hash: snapshot.infoHashes.best.hex) + + do { + _ = try Activity.request(attributes: attributes, contentState: snapshot.toLiveActivityState, pushType: .none) + } catch { + print(error.localizedDescription) + } + } + } +} + +private extension TorrentHandle.Snapshot { + var toLiveActivityState: ProgressWidgetAttributes.ContentState { + .init(state: friendlyState.toState, + progress: progress, + downSpeed: downloadRate, + upSpeed: uploadRate, + timeRemainig: timeRemains, + timeStamp: Date()) + } +} + +private extension TorrentHandle.State { + var toState: ProgressWidgetAttributes.State { + switch self { + case .checkingFiles: + return .checkingFiles + case .downloadingMetadata: + return .downloadingMetadata + case .downloading: + return .downloading + case .finished: + return .finished + case .seeding: + return .seeding + case .checkingResumeData: + return .checkingResumeData + case .paused: + return .paused + @unknown default: + fatalError("\(ProgressWidgetAttributes.State.self) has no such case \(self)") + } + } + + var shouldShowLiveActivity: Bool { + let notShow: [Self] = [.finished, .paused] + return !notShow.contains(self) + } +} +#endif diff --git a/iTorrent/Utils/NSUserDefaultItem.swift b/iTorrent/Utils/NSUserDefaultItem.swift index 49c4c00f..971f0987 100644 --- a/iTorrent/Utils/NSUserDefaultItem.swift +++ b/iTorrent/Utils/NSUserDefaultItem.swift @@ -33,7 +33,7 @@ struct NSUserDefaultItem { } private extension NSUserDefaultItem { - static var userDefaults: UserDefaults { UserDefaults(suiteName: "group.itorrent.life-activity") ?? .standard } + static var userDefaults: UserDefaults { .itorrentGroup } static func value(for key: String) -> Value? { guard let data = userDefaults.data(forKey: key) diff --git a/iTorrent/Utils/UserDefaultItem.swift b/iTorrent/Utils/UserDefaultItem.swift index b70cff21..beea181d 100644 --- a/iTorrent/Utils/UserDefaultItem.swift +++ b/iTorrent/Utils/UserDefaultItem.swift @@ -39,7 +39,7 @@ struct UserDefaultItem { } private extension UserDefaultItem { - static var userDefaults: UserDefaults { UserDefaults(suiteName: "group.itorrent.life-activity") ?? .standard } + static var userDefaults: UserDefaults { .itorrentGroup } static func get(by key: String) -> T? { guard let decoded = userDefaults.data(forKey: key), diff --git a/iTorrent/Utils/UserDefaults+AppGroup.swift b/iTorrent/Utils/UserDefaults+AppGroup.swift new file mode 100644 index 00000000..7c13feb0 --- /dev/null +++ b/iTorrent/Utils/UserDefaults+AppGroup.swift @@ -0,0 +1,14 @@ +// +// UserDefaults+AppGroup.swift +// iTorrent +// +// Created by Даниил Виноградов on 30.06.2024. +// + +import Foundation + +extension UserDefaults { + static var itorrentGroup: UserDefaults { + UserDefaults(suiteName: "group.itorrent.life-activity") ?? .standard + } +}