From e84deb13606d57d680db5a91956c36c0e95193c5 Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 09:25:54 +0100 Subject: [PATCH 1/7] Use progressbar with timer updates for live activities --- Views/LiveActivity/ActivityService.swift | 17 +++++---- Widgets/DownloadsLiveActivity.swift | 45 +++++++++++------------- 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/Views/LiveActivity/ActivityService.swift b/Views/LiveActivity/ActivityService.swift index e3380947..70816834 100644 --- a/Views/LiveActivity/ActivityService.swift +++ b/Views/LiveActivity/ActivityService.swift @@ -96,16 +96,21 @@ final class ActivityService { guard let activity, (now - lastUpdate) > updateFrequency else { return } - lastUpdate = now Task { let activityState = await activityState(from: state, downloadTimes: downloadTimes) - await activity.update( - ActivityContent( - state: activityState, - staleDate: nil - ) + let newContent = ActivityContent( + state: activityState, + staleDate: nil ) + if #available(iOS 17.2, *) { + // important to define a timestamp, this way iOS knows which updates + // can be dropped, if too many of them queues up + await activity.update(newContent, timestamp: .now) + } else { + await activity.update(newContent) + } } + lastUpdate = now } private func updatedDownloadTimes(from states: [UUID: DownloadState]) -> [UUID: CFTimeInterval] { diff --git a/Widgets/DownloadsLiveActivity.swift b/Widgets/DownloadsLiveActivity.swift index 565bf845..9a337fb5 100644 --- a/Widgets/DownloadsLiveActivity.swift +++ b/Widgets/DownloadsLiveActivity.swift @@ -21,50 +21,47 @@ struct DownloadsLiveActivity: Widget { // @Environment(\.isActivityFullscreen) var isActivityFullScreen has a bug, when min iOS is 16 // https://developer.apple.com/forums/thread/763594 + /// A start time from the creation of the activity, + /// this way the progress bar is not jumping back to 0 + private let startTime: Date = .now + var body: some WidgetConfiguration { ActivityConfiguration(for: DownloadActivityAttributes.self) { context in - // Lock screen/banner UI goes here + // Lock screen/banner UI + let timeInterval = startTime...Date( + timeInterval: context.state.estimatedTimeLeft, + since: .now + ) VStack { HStack { - KiwixLogo(maxHeight: 50) - .padding() VStack(alignment: .leading) { Text(context.state.title) .lineLimit(1) .multilineTextAlignment(.leading) .font(.headline) .bold() - HStack { - Text( - timerInterval: Date.now...Date( - timeInterval: context.state.estimatedTimeLeft, - since: .now - ) - ) - .lineLimit(1) - .multilineTextAlignment(.leading) - .font(.caption) - .tint(.secondary) + ProgressView(timerInterval: timeInterval, countsDown: false, label: { Text(context.state.progressDescription) .lineLimit(1) - .multilineTextAlignment(.leading) .font(.caption) .tint(.secondary) - } + }, currentValueLabel: { + Text(timerInterval: timeInterval) + .font(.caption) + .tint(.secondary) + }) + .tint(Color.primary) } - Spacer() - ProgressView(value: context.state.progress) - .progressViewStyle(CircularProgressGaugeStyle(lineWidth: 5.7)) - .frame(width: 24, height: 24) - .padding() + .padding() + KiwixLogo(maxHeight: 50) + .padding(.trailing) } } .modifier(WidgetBackgroundModifier()) } dynamicIsland: { context in DynamicIsland { - // Expanded UI goes here. Compose the expanded UI through - // various regions, like leading/trailing/center/bottom + // Expanded UI DynamicIslandExpandedRegion(.leading) { Spacer() KiwixLogo(maxHeight: 50) @@ -123,7 +120,7 @@ extension DownloadActivityAttributes.ContentState { description: "First item", downloaded: 128, total: 256, - timeRemaining: 3 + timeRemaining: 15 ), DownloadActivityAttributes.DownloadItem( uuid: UUID(), From 41992a810d4e913e8ec6e55ffdb44c4fceff8f54 Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 09:53:24 +0100 Subject: [PATCH 2/7] Update dynamic island --- Widgets/DownloadsLiveActivity.swift | 37 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/Widgets/DownloadsLiveActivity.swift b/Widgets/DownloadsLiveActivity.swift index 9a337fb5..1fe1b903 100644 --- a/Widgets/DownloadsLiveActivity.swift +++ b/Widgets/DownloadsLiveActivity.swift @@ -63,28 +63,35 @@ struct DownloadsLiveActivity: Widget { DynamicIsland { // Expanded UI DynamicIslandExpandedRegion(.leading) { - Spacer() - KiwixLogo(maxHeight: 50) - Spacer() - } - DynamicIslandExpandedRegion(.trailing) { - ProgressView(value: context.state.progress) - .progressViewStyle(CircularProgressGaugeStyle(lineWidth: 11.4)) - .padding(6.0) - } - DynamicIslandExpandedRegion(.center) { + let timeInterval = startTime...Date( + timeInterval: context.state.estimatedTimeLeft, + since: .now + ) VStack(alignment: .leading) { Text(context.state.title) .lineLimit(1) .multilineTextAlignment(.leading) .font(.headline) .bold() - Text(context.state.progressDescription) - .lineLimit(1) - .multilineTextAlignment(.leading) - .font(.caption) - .tint(.secondary) + ProgressView(timerInterval: timeInterval, countsDown: false, label: { + Text(context.state.progressDescription) + .lineLimit(1) + .font(.caption) + .tint(.secondary) + }, currentValueLabel: { + Text(timerInterval: timeInterval) + .font(.caption) + .tint(.secondary) + }) + .tint(Color.primary) } + .padding(.leading) + .dynamicIsland(verticalPlacement: .belowIfTooWide) + } + + DynamicIslandExpandedRegion(.trailing) { + KiwixLogo(maxHeight: 50) + .padding() } } compactLeading: { KiwixLogo() From ef693c1670f52221120afa99cb5bc8f68b10ccfe Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 11:31:27 +0100 Subject: [PATCH 3/7] Change logo proportions for live activities --- Widgets/KiwixLogo.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Widgets/KiwixLogo.swift b/Widgets/KiwixLogo.swift index d2af4ccb..b4be4174 100644 --- a/Widgets/KiwixLogo.swift +++ b/Widgets/KiwixLogo.swift @@ -31,7 +31,7 @@ struct KiwixLogo: View { Image("KiwixLogo") .resizable() .scaledToFit() - .frame(width: maxHeight / 1.6182, height: maxHeight / 1.6182) + .frame(width: maxHeight * 0.75, height: maxHeight * 0.75) } } } From f9ce6cd88682c39bbb54ff766b00bbe2c56b29d6 Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 11:49:34 +0100 Subject: [PATCH 4/7] Revert header change --- Views/LiveActivity/ActivityService.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Views/LiveActivity/ActivityService.swift b/Views/LiveActivity/ActivityService.swift index 70816834..7f9b9825 100644 --- a/Views/LiveActivity/ActivityService.swift +++ b/Views/LiveActivity/ActivityService.swift @@ -11,7 +11,7 @@ // General Public License for more details. // // You should have received a copy of the GNU General Public License -// along with Kiwix; If not, see https://www.gnu.orgllll/llicenses/. +// along with Kiwix; If not, see https://www.gnu.org/licenses/. #if os(iOS) From 8fb73a36adb6f150d1524fc70330bbef8e20b8ac Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 15:33:01 +0100 Subject: [PATCH 5/7] Handle paused state for live activities --- Common/DownloadActivityAttributes.swift | 14 +++- Model/DownloadService.swift | 4 ++ Views/LiveActivity/ActivityService.swift | 20 +++++- Widgets/DownloadsLiveActivity.swift | 92 ++++++++++++++---------- 4 files changed, 89 insertions(+), 41 deletions(-) diff --git a/Common/DownloadActivityAttributes.swift b/Common/DownloadActivityAttributes.swift index 0b3790e5..fe01a2ce 100644 --- a/Common/DownloadActivityAttributes.swift +++ b/Common/DownloadActivityAttributes.swift @@ -52,7 +52,15 @@ public struct DownloadActivityAttributes: ActivityAttributes { } public var estimatedTimeLeft: TimeInterval { - items.map(\.timeRemaining).max() ?? 0 + items.filter { (item: DownloadActivityAttributes.DownloadItem) in + !item.isPaused + }.map(\.timeRemaining).max() ?? 0 + } + + public var isAllPaused: Bool { + items.allSatisfy { (item: DownloadActivityAttributes.DownloadItem) in + item.isPaused + } } public var progress: Double { @@ -70,6 +78,7 @@ public struct DownloadActivityAttributes: ActivityAttributes { let downloaded: Int64 let total: Int64 let timeRemaining: TimeInterval + let isPaused: Bool var progress: Double { progressFor(items: [self]).fractionCompleted } @@ -77,12 +86,13 @@ public struct DownloadActivityAttributes: ActivityAttributes { progressFor(items: [self]).localizedAdditionalDescription } - public init(uuid: UUID, description: String, downloaded: Int64, total: Int64, timeRemaining: TimeInterval) { + public init(uuid: UUID, description: String, downloaded: Int64, total: Int64, timeRemaining: TimeInterval, isPaused: Bool) { self.uuid = uuid self.description = description self.downloaded = downloaded self.total = total self.timeRemaining = timeRemaining + self.isPaused = isPaused } } } diff --git a/Model/DownloadService.swift b/Model/DownloadService.swift index 647c325f..1a8413c0 100644 --- a/Model/DownloadService.swift +++ b/Model/DownloadService.swift @@ -26,6 +26,10 @@ struct DownloadState: Codable { let downloaded: Int64 let total: Int64 let resumeData: Data? + + var isPaused: Bool { + resumeData != nil + } static func empty() -> DownloadState { .init(downloaded: 0, total: 1, resumeData: nil) diff --git a/Views/LiveActivity/ActivityService.swift b/Views/LiveActivity/ActivityService.swift index 7f9b9825..d375b510 100644 --- a/Views/LiveActivity/ActivityService.swift +++ b/Views/LiveActivity/ActivityService.swift @@ -93,7 +93,14 @@ final class ActivityService { return } let now = CACurrentMediaTime() - guard let activity, (now - lastUpdate) > updateFrequency else { + // make sure we don't update too frequently + // unless there's a pause, we do want immediate update + let isTooEarlyToUpdate = if hasAnyPause(in: state) { + false + } else { + (now - lastUpdate) <= updateFrequency + } + guard let activity, !isTooEarlyToUpdate else { return } Task { @@ -176,9 +183,18 @@ final class ActivityService { description: titles[key] ?? key.uuidString, downloaded: download.downloaded, total: download.total, - timeRemaining: downloadTimes[key] ?? 0) + timeRemaining: downloadTimes[key] ?? 0, + isPaused: download.isPaused + ) }) } + + private func hasAnyPause(in state: [UUID: DownloadState]) -> Bool { + guard !state.isEmpty else { return false } + return !state.values.allSatisfy { (download: DownloadState) in + download.isPaused == false + } + } } #endif diff --git a/Widgets/DownloadsLiveActivity.swift b/Widgets/DownloadsLiveActivity.swift index 1fe1b903..e0a7ebca 100644 --- a/Widgets/DownloadsLiveActivity.swift +++ b/Widgets/DownloadsLiveActivity.swift @@ -35,22 +35,8 @@ struct DownloadsLiveActivity: Widget { VStack { HStack { VStack(alignment: .leading) { - Text(context.state.title) - .lineLimit(1) - .multilineTextAlignment(.leading) - .font(.headline) - .bold() - ProgressView(timerInterval: timeInterval, countsDown: false, label: { - Text(context.state.progressDescription) - .lineLimit(1) - .font(.caption) - .tint(.secondary) - }, currentValueLabel: { - Text(timerInterval: timeInterval) - .font(.caption) - .tint(.secondary) - }) - .tint(Color.primary) + titleFor(context.state.title) + progressFor(state: context.state, timeInterval: timeInterval) } .padding() KiwixLogo(maxHeight: 50) @@ -67,25 +53,13 @@ struct DownloadsLiveActivity: Widget { timeInterval: context.state.estimatedTimeLeft, since: .now ) + VStack(alignment: .leading) { - Text(context.state.title) - .lineLimit(1) - .multilineTextAlignment(.leading) - .font(.headline) - .bold() - ProgressView(timerInterval: timeInterval, countsDown: false, label: { - Text(context.state.progressDescription) - .lineLimit(1) - .font(.caption) - .tint(.secondary) - }, currentValueLabel: { - Text(timerInterval: timeInterval) - .font(.caption) - .tint(.secondary) - }) - .tint(Color.primary) + titleFor(context.state.title) + progressFor(state: context.state, timeInterval: timeInterval) + Spacer() } - .padding(.leading) + .padding() .dynamicIsland(verticalPlacement: .belowIfTooWide) } @@ -109,6 +83,46 @@ struct DownloadsLiveActivity: Widget { .keylineTint(Color.red) }.containerBackgroundRemovable() } + + @ViewBuilder + private func titleFor(_ title: String) -> some View { + Text(title) + .lineLimit(1) + .frame(minWidth: 150, alignment: .leading) + .font(.headline) + .bold() + } + + @ViewBuilder + private func progressText(_ description: String) -> some View { + Text(description) + .lineLimit(1) + .font(.caption) + .tint(.secondary) + } + + @ViewBuilder + private func progressFor(state: DownloadActivityAttributes.ContentState, timeInterval: ClosedRange) -> some View { + if !state.isAllPaused { + ProgressView(timerInterval: timeInterval, countsDown: false, label: { + progressText(state.progressDescription) + }, currentValueLabel: { + Text(timerInterval: timeInterval) + .font(.caption) + .tint(.secondary) + }) + .tint(Color.primary) + } else { + ProgressView(value: state.progress, label: { + progressText(state.progressDescription) + }, currentValueLabel: { + Label("", systemImage: "pause.fill") + .font(.caption) + .tint(.secondary) + }) + .tint(Color.primary) + } + } } extension DownloadActivityAttributes { @@ -127,14 +141,16 @@ extension DownloadActivityAttributes.ContentState { description: "First item", downloaded: 128, total: 256, - timeRemaining: 15 + timeRemaining: 15, + isPaused: true ), DownloadActivityAttributes.DownloadItem( uuid: UUID(), description: "2nd item", downloaded: 90, total: 124, - timeRemaining: 2 + timeRemaining: 2, + isPaused: true ) ] ) @@ -149,14 +165,16 @@ extension DownloadActivityAttributes.ContentState { description: "First item", downloaded: 256, total: 256, - timeRemaining: 0 + timeRemaining: 0, + isPaused: false ), DownloadActivityAttributes.DownloadItem( uuid: UUID(), description: "2nd item", downloaded: 110, total: 124, - timeRemaining: 2 + timeRemaining: 2, + isPaused: false ) ] ) From b966b9ff320a6648f46cb3aa9189bfb16f4396b5 Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 16:00:14 +0100 Subject: [PATCH 6/7] Re-organise downloads related files --- Common/DownloadActivityAttributes.swift | 9 +- Model/{ => Downloads}/DownloadService.swift | 95 -------------------- Model/Downloads/DownloadState.swift | 52 +++++++++++ Model/Downloads/DownloadTasksPublisher.swift | 73 +++++++++++++++ Views/LiveActivity/ActivityService.swift | 4 +- Widgets/DownloadsLiveActivity.swift | 5 +- 6 files changed, 139 insertions(+), 99 deletions(-) rename Model/{ => Downloads}/DownloadService.swift (82%) create mode 100644 Model/Downloads/DownloadState.swift create mode 100644 Model/Downloads/DownloadTasksPublisher.swift diff --git a/Common/DownloadActivityAttributes.swift b/Common/DownloadActivityAttributes.swift index fe01a2ce..e894c9b3 100644 --- a/Common/DownloadActivityAttributes.swift +++ b/Common/DownloadActivityAttributes.swift @@ -86,7 +86,14 @@ public struct DownloadActivityAttributes: ActivityAttributes { progressFor(items: [self]).localizedAdditionalDescription } - public init(uuid: UUID, description: String, downloaded: Int64, total: Int64, timeRemaining: TimeInterval, isPaused: Bool) { + public init( + uuid: UUID, + description: String, + downloaded: Int64, + total: Int64, + timeRemaining: TimeInterval, + isPaused: Bool + ) { self.uuid = uuid self.description = description self.downloaded = downloaded diff --git a/Model/DownloadService.swift b/Model/Downloads/DownloadService.swift similarity index 82% rename from Model/DownloadService.swift rename to Model/Downloads/DownloadService.swift index 1a8413c0..8c647265 100644 --- a/Model/DownloadService.swift +++ b/Model/Downloads/DownloadService.swift @@ -13,106 +13,11 @@ // You should have received a copy of the GNU General Public License // along with Kiwix; If not, see https://www.gnu.org/licenses/. -// -// DownloadService.swift -// Kiwix - import Combine import CoreData import UserNotifications import os -struct DownloadState: Codable { - let downloaded: Int64 - let total: Int64 - let resumeData: Data? - - var isPaused: Bool { - resumeData != nil - } - - static func empty() -> DownloadState { - .init(downloaded: 0, total: 1, resumeData: nil) - } - - init(downloaded: Int64, total: Int64, resumeData: Data?) { - guard total >= downloaded, total > 0 else { - assertionFailure("invalid download progress values: downloaded \(downloaded) total: \(total)") - self.downloaded = downloaded - self.total = downloaded - self.resumeData = resumeData - return - } - self.downloaded = downloaded - self.total = total - self.resumeData = resumeData - } - - func updatedWith(downloaded: Int64, total: Int64) -> DownloadState { - DownloadState(downloaded: downloaded, total: total, resumeData: resumeData) - } - - func updatedWith(resumeData: Data?) -> DownloadState { - DownloadState(downloaded: downloaded, total: total, resumeData: resumeData) - } -} - -@MainActor -final class DownloadTasksPublisher { - - let publisher: CurrentValueSubject<[UUID: DownloadState], Never> - private var states = [UUID: DownloadState]() - - init() { - publisher = CurrentValueSubject(states) - if let jsonData = UserDefaults.standard.object(forKey: "downloadStates") as? Data, - let storedStates = try? JSONDecoder().decode([UUID: DownloadState].self, from: jsonData) { - states = storedStates - publisher.send(states) - } - } - - func updateFor(uuid: UUID, downloaded: Int64, total: Int64) { - if let state = states[uuid] { - states[uuid] = state.updatedWith(downloaded: downloaded, total: total) - } else { - states[uuid] = DownloadState(downloaded: downloaded, total: total, resumeData: nil) - } - publisher.send(states) - saveState() - } - - func resetFor(uuid: UUID) { - states.removeValue(forKey: uuid) - publisher.send(states) - saveState() - } - - func isEmpty() -> Bool { - states.isEmpty - } - - func resumeDataFor(uuid: UUID) -> Data? { - states[uuid]?.resumeData - } - - func updateFor(uuid: UUID, withResumeData resumeData: Data?) { - if let state = states[uuid] { - states[uuid] = state.updatedWith(resumeData: resumeData) - publisher.send(states) - saveState() - } else { - assertionFailure("there should be a download task for: \(uuid)") - } - } - - private func saveState() { - if let jsonStates = try? JSONEncoder().encode(states) { - UserDefaults.standard.setValue(jsonStates, forKey: "downloadStates") - } - } -} - final class DownloadService: NSObject, URLSessionDelegate, URLSessionTaskDelegate, URLSessionDownloadDelegate { static let shared = DownloadService() private let queue = DispatchQueue(label: "downloads", qos: .background) diff --git a/Model/Downloads/DownloadState.swift b/Model/Downloads/DownloadState.swift new file mode 100644 index 00000000..60452b1d --- /dev/null +++ b/Model/Downloads/DownloadState.swift @@ -0,0 +1,52 @@ +// This file is part of Kiwix for iOS & macOS. +// +// Kiwix is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// any later version. +// +// Kiwix is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kiwix; If not, see https://www.gnu.org/licenses/. + +import Foundation +import Combine + +struct DownloadState: Codable { + let downloaded: Int64 + let total: Int64 + let resumeData: Data? + + var isPaused: Bool { + resumeData != nil + } + + static func empty() -> DownloadState { + .init(downloaded: 0, total: 1, resumeData: nil) + } + + init(downloaded: Int64, total: Int64, resumeData: Data?) { + guard total >= downloaded, total > 0 else { + assertionFailure("invalid download progress values: downloaded \(downloaded) total: \(total)") + self.downloaded = downloaded + self.total = downloaded + self.resumeData = resumeData + return + } + self.downloaded = downloaded + self.total = total + self.resumeData = resumeData + } + + func updatedWith(downloaded: Int64, total: Int64) -> DownloadState { + DownloadState(downloaded: downloaded, total: total, resumeData: resumeData) + } + + func updatedWith(resumeData: Data?) -> DownloadState { + DownloadState(downloaded: downloaded, total: total, resumeData: resumeData) + } +} diff --git a/Model/Downloads/DownloadTasksPublisher.swift b/Model/Downloads/DownloadTasksPublisher.swift new file mode 100644 index 00000000..5a7dd9e0 --- /dev/null +++ b/Model/Downloads/DownloadTasksPublisher.swift @@ -0,0 +1,73 @@ +// This file is part of Kiwix for iOS & macOS. +// +// Kiwix is free software; you can redistribute it and/or modify it +// under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 3 of the License, or +// any later version. +// +// Kiwix is distributed in the hope that it will be useful, but +// WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Kiwix; If not, see https://www.gnu.org/licenses/. + +import Foundation +import Combine + +@MainActor +final class DownloadTasksPublisher { + + let publisher: CurrentValueSubject<[UUID: DownloadState], Never> + private var states = [UUID: DownloadState]() + + init() { + publisher = CurrentValueSubject(states) + if let jsonData = UserDefaults.standard.object(forKey: "downloadStates") as? Data, + let storedStates = try? JSONDecoder().decode([UUID: DownloadState].self, from: jsonData) { + states = storedStates + publisher.send(states) + } + } + + func updateFor(uuid: UUID, downloaded: Int64, total: Int64) { + if let state = states[uuid] { + states[uuid] = state.updatedWith(downloaded: downloaded, total: total) + } else { + states[uuid] = DownloadState(downloaded: downloaded, total: total, resumeData: nil) + } + publisher.send(states) + saveState() + } + + func resetFor(uuid: UUID) { + states.removeValue(forKey: uuid) + publisher.send(states) + saveState() + } + + func isEmpty() -> Bool { + states.isEmpty + } + + func resumeDataFor(uuid: UUID) -> Data? { + states[uuid]?.resumeData + } + + func updateFor(uuid: UUID, withResumeData resumeData: Data?) { + if let state = states[uuid] { + states[uuid] = state.updatedWith(resumeData: resumeData) + publisher.send(states) + saveState() + } else { + assertionFailure("there should be a download task for: \(uuid)") + } + } + + private func saveState() { + if let jsonStates = try? JSONEncoder().encode(states) { + UserDefaults.standard.setValue(jsonStates, forKey: "downloadStates") + } + } +} diff --git a/Views/LiveActivity/ActivityService.swift b/Views/LiveActivity/ActivityService.swift index d375b510..0b842ae5 100644 --- a/Views/LiveActivity/ActivityService.swift +++ b/Views/LiveActivity/ActivityService.swift @@ -49,9 +49,9 @@ final class ActivityService { publisher().sink { [weak self] (state: [UUID: DownloadState]) in guard let self else { return } if state.isEmpty { - stop() + self.stop() } else { - update(state: state) + self.update(state: state) } }.store(in: &cancellables) } diff --git a/Widgets/DownloadsLiveActivity.swift b/Widgets/DownloadsLiveActivity.swift index e0a7ebca..4eaca6f5 100644 --- a/Widgets/DownloadsLiveActivity.swift +++ b/Widgets/DownloadsLiveActivity.swift @@ -102,7 +102,10 @@ struct DownloadsLiveActivity: Widget { } @ViewBuilder - private func progressFor(state: DownloadActivityAttributes.ContentState, timeInterval: ClosedRange) -> some View { + private func progressFor( + state: DownloadActivityAttributes.ContentState, + timeInterval: ClosedRange + ) -> some View { if !state.isAllPaused { ProgressView(timerInterval: timeInterval, countsDown: false, label: { progressText(state.progressDescription) From 9b65e7fc02f093dff49253493670be2e20ba6ab1 Mon Sep 17 00:00:00 2001 From: Balazs Perlaki-Horvath Date: Fri, 28 Feb 2025 16:07:11 +0100 Subject: [PATCH 7/7] Fix lint --- ViewModel/BrowserViewModel.swift | 3 +-- Views/LiveActivity/ActivityService.swift | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/ViewModel/BrowserViewModel.swift b/ViewModel/BrowserViewModel.swift index ab7c9e0e..2bcdbbe3 100644 --- a/ViewModel/BrowserViewModel.swift +++ b/ViewModel/BrowserViewModel.swift @@ -611,8 +611,7 @@ final class BrowserViewModel: NSObject, ObservableObject, ) actions.append( UIAction(title: LocalString.common_dialog_button_open_in_new_tab, - image: UIImage(systemName: "doc.badge.plus")) { [weak self] _ in - guard let self else { return } + image: UIImage(systemName: "doc.badge.plus")) { _ in Task { @MainActor in NotificationCenter.openURL(url, inNewTab: true) } diff --git a/Views/LiveActivity/ActivityService.swift b/Views/LiveActivity/ActivityService.swift index 0b842ae5..9f8e2354 100644 --- a/Views/LiveActivity/ActivityService.swift +++ b/Views/LiveActivity/ActivityService.swift @@ -112,7 +112,7 @@ final class ActivityService { if #available(iOS 17.2, *) { // important to define a timestamp, this way iOS knows which updates // can be dropped, if too many of them queues up - await activity.update(newContent, timestamp: .now) + await activity.update(newContent, timestamp: Date.now) } else { await activity.update(newContent) }