Circular Progress fixes + Continue watching fixes

This commit is contained in:
cranci1 2025-06-25 10:44:01 +02:00
parent 4ce60d957d
commit 9b29b40ff3
2 changed files with 60 additions and 55 deletions

View file

@ -11,49 +11,51 @@ class ContinueWatchingManager {
static let shared = ContinueWatchingManager() static let shared = ContinueWatchingManager()
private let storageKey = "continueWatchingItems" private let storageKey = "continueWatchingItems"
private let lastCleanupKey = "lastContinueWatchingCleanup" private let lastCleanupKey = "lastContinueWatchingCleanup"
private init() { private init() {
NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(handleiCloudSync), name: .iCloudSyncDidComplete, object: nil)
performCleanupIfNeeded() performCleanupIfNeeded()
} }
@objc private func handleiCloudSync() { @objc private func handleiCloudSync() {
NotificationCenter.default.post(name: .ContinueWatchingDidUpdate, object: nil) NotificationCenter.default.post(name: .ContinueWatchingDidUpdate, object: nil)
} }
private func performCleanupIfNeeded() { private func performCleanupIfNeeded() {
let lastCleanup = UserDefaults.standard.double(forKey: lastCleanupKey) let lastCleanup = UserDefaults.standard.double(forKey: lastCleanupKey)
let currentTime = Date().timeIntervalSince1970 let currentTime = Date().timeIntervalSince1970
if currentTime - lastCleanup > 86400 { if currentTime - lastCleanup > 86400 {
cleanupOldEpisodes() cleanupOldEpisodes()
UserDefaults.standard.set(currentTime, forKey: lastCleanupKey) UserDefaults.standard.set(currentTime, forKey: lastCleanupKey)
} }
} }
private func cleanupOldEpisodes() { private func cleanupOldEpisodes() {
var items = fetchItems() var items = fetchItems()
var itemsToRemove: Set<UUID> = [] var itemsToRemove: Set<UUID> = []
let groupedItems = Dictionary(grouping: items) { item in let groupedItems = Dictionary(grouping: items) { item in
let title = item.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression) let title = item.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)
return title return title
} }
for (_, showEpisodes) in groupedItems { for (_, showEpisodes) in groupedItems {
let sortedEpisodes = showEpisodes.sorted { $0.episodeNumber < $1.episodeNumber } let sortedEpisodes = showEpisodes.sorted { $0.episodeNumber < $1.episodeNumber }
for i in 0..<sortedEpisodes.count - 1 { for i in 0..<sortedEpisodes.count - 1 {
let currentEpisode = sortedEpisodes[i] let currentEpisode = sortedEpisodes[i]
let nextEpisode = sortedEpisodes[i + 1] let nextEpisode = sortedEpisodes[i + 1]
if currentEpisode.progress >= 0.8 && nextEpisode.episodeNumber > currentEpisode.episodeNumber { let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
let threshold = (100.0 - remainingTimePercentage) / 100.0
if currentEpisode.progress >= threshold && nextEpisode.episodeNumber > currentEpisode.episodeNumber {
itemsToRemove.insert(currentEpisode.id) itemsToRemove.insert(currentEpisode.id)
} }
} }
} }
if !itemsToRemove.isEmpty { if !itemsToRemove.isEmpty {
items.removeAll { itemsToRemove.contains($0.id) } items.removeAll { itemsToRemove.contains($0.id) }
if let data = try? JSONEncoder().encode(items) { if let data = try? JSONEncoder().encode(items) {
@ -61,59 +63,59 @@ class ContinueWatchingManager {
} }
} }
} }
func save(item: ContinueWatchingItem) { func save(item: ContinueWatchingItem) {
// Use real playback times
let lastKey = "lastPlayedTime_\(item.fullUrl)" let lastKey = "lastPlayedTime_\(item.fullUrl)"
let totalKey = "totalTime_\(item.fullUrl)" let totalKey = "totalTime_\(item.fullUrl)"
let lastPlayed = UserDefaults.standard.double(forKey: lastKey) let lastPlayed = UserDefaults.standard.double(forKey: lastKey)
let totalTime = UserDefaults.standard.double(forKey: totalKey) let totalTime = UserDefaults.standard.double(forKey: totalKey)
// Compute up-to-date progress
let actualProgress: Double let actualProgress: Double
if totalTime > 0 { if totalTime > 0 {
actualProgress = min(max(lastPlayed / totalTime, 0), 1) actualProgress = min(max(lastPlayed / totalTime, 0), 1)
} else { } else {
actualProgress = item.progress actualProgress = item.progress
}
let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
// If watched 90%, remove it let threshold = (100.0 - remainingTimePercentage) / 100.0
if actualProgress >= 0.9 { if actualProgress >= threshold {
remove(item: item) remove(item: item)
return return
} }
// Otherwise update progress and remove old episodes from the same show var updatedItem = item
var updatedItem = item updatedItem.progress = actualProgress
updatedItem.progress = actualProgress
var items = fetchItems()
var items = fetchItems()
let showTitle = item.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
let showTitle = item.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines)
items.removeAll { existingItem in
let existingShowTitle = existingItem.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
.trimmingCharacters(in: .whitespacesAndNewlines) .trimmingCharacters(in: .whitespacesAndNewlines)
return showTitle == existingShowTitle && items.removeAll { existingItem in
existingItem.episodeNumber < item.episodeNumber && let existingShowTitle = existingItem.mediaTitle.replacingOccurrences(of: "Episode \\d+.*$", with: "", options: .regularExpression)
existingItem.progress >= 0.8 .trimmingCharacters(in: .whitespacesAndNewlines)
}
let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
items.removeAll { existing in let threshold = (100.0 - remainingTimePercentage) / 100.0
existing.fullUrl == item.fullUrl && return showTitle == existingShowTitle &&
existing.episodeNumber == item.episodeNumber && existingItem.episodeNumber < item.episodeNumber &&
existing.module.metadata.sourceName == item.module.metadata.sourceName existingItem.progress >= threshold
} }
items.append(updatedItem) items.removeAll { existing in
existing.fullUrl == item.fullUrl &&
if let data = try? JSONEncoder().encode(items) { existing.episodeNumber == item.episodeNumber &&
UserDefaults.standard.set(data, forKey: storageKey) existing.module.metadata.sourceName == item.module.metadata.sourceName
}
items.append(updatedItem)
if let data = try? JSONEncoder().encode(items) {
UserDefaults.standard.set(data, forKey: storageKey)
}
} }
} }
func fetchItems() -> [ContinueWatchingItem] { func fetchItems() -> [ContinueWatchingItem] {
guard guard
let data = UserDefaults.standard.data(forKey: storageKey), let data = UserDefaults.standard.data(forKey: storageKey),
@ -121,7 +123,7 @@ class ContinueWatchingManager {
else { else {
return [] return []
} }
var seen = Set<String>() var seen = Set<String>()
let unique = raw.reversed().filter { item in let unique = raw.reversed().filter { item in
let key = "\(item.fullUrl)|\(item.module.metadata.sourceName)|\(item.episodeNumber)" let key = "\(item.fullUrl)|\(item.module.metadata.sourceName)|\(item.episodeNumber)"
@ -132,10 +134,10 @@ class ContinueWatchingManager {
return true return true
} }
}.reversed() }.reversed()
return Array(unique) return Array(unique)
} }
func remove(item: ContinueWatchingItem) { func remove(item: ContinueWatchingItem) {
var items = fetchItems() var items = fetchItems()
items.removeAll { $0.id == item.id } items.removeAll { $0.id == item.id }

View file

@ -24,7 +24,10 @@ struct CircularProgressBar: View {
.rotationEffect(Angle(degrees: 270.0)) .rotationEffect(Angle(degrees: 270.0))
.animation(.linear, value: progress) .animation(.linear, value: progress)
if progress >= 0.9 { let remainingTimePercentage = UserDefaults.standard.object(forKey: "remainingTimePercentage") != nil ? UserDefaults.standard.double(forKey: "remainingTimePercentage") : 90.0
let threshold = (100.0 - remainingTimePercentage) / 100.0
if progress >= threshold {
Image(systemName: "checkmark") Image(systemName: "checkmark")
.font(.system(size: 12)) .font(.system(size: 12))
} else { } else {