Files
yattee/Yattee/Services/Subscriptions/LocalSubscriptionProvider.swift
Arkadiusz Fal 9065234606 Key subscription providers by account rather than backend type
SubscriptionService held one provider per backend and each resolved its
instance from settingsManager.subscriptionAccount, falling back to "the
first enabled Invidious instance" when that was unset. With several logins
possible on one instance that model breaks twice: two accounts would share
a single in-memory subscription cache, and the fallback could send an
account's request to a server it holds no credential for.

Providers are now built per account and cached by account ID, and each
resolves its instance and credential from the account it was built for -
no fallback. The protocol's accountType becomes a DataContext, which is
what actually identifies a provider now.

Views that branched on "is the subscription account local" read the context
instead, and the settings picker writes both stores. Keeping the legacy
setting in step is deliberate for now: several views still read it, and
letting the two diverge would show one account in the UI while serving
another's data. The Phase 3 switcher removes the last readers.

Signing out discards the account's provider so its cached subscriptions
cannot be served after the account is gone.
2026-08-24 23:56:40 +02:00

71 lines
2.2 KiB
Swift

//
// LocalSubscriptionProvider.swift
// Yattee
//
// Local subscription provider using SwiftData with iCloud sync.
// This is the default provider when using "Yattee (iCloud)" subscription account.
//
import Foundation
/// Local subscription provider that stores subscriptions in SwiftData.
/// Syncs with iCloud via CloudKitSyncEngine.
@MainActor
final class LocalSubscriptionProvider: SubscriptionProvider {
// MARK: - Properties
let context: DataContext = .local
private let dataManager: DataManager
// MARK: - Initialization
init(dataManager: DataManager) {
self.dataManager = dataManager
}
// MARK: - SubscriptionProvider
func fetchSubscriptions() async throws -> [Channel] {
// Convert Subscription models to Channel models
dataManager.subscriptions().map { subscription in
Channel(
id: ChannelID(source: subscription.contentSource, channelID: subscription.channelID),
name: subscription.name,
description: subscription.channelDescription,
subscriberCount: subscription.subscriberCount,
thumbnailURL: subscription.avatarURL,
bannerURL: subscription.bannerURL,
isVerified: subscription.isVerified
)
}
}
func subscribe(to channel: Channel) async throws {
// Check if already subscribed
if dataManager.isSubscribed(to: channel.id.channelID) {
throw SubscriptionProviderError.alreadySubscribed
}
dataManager.subscribe(to: channel)
}
func unsubscribe(from channelID: String) async throws {
// Check if subscribed
guard dataManager.isSubscribed(to: channelID) else {
throw SubscriptionProviderError.notSubscribed
}
dataManager.unsubscribe(from: channelID)
}
func isSubscribed(to channelID: String) async -> Bool {
dataManager.isSubscribed(to: channelID)
}
func refreshCache() async throws {
// Local provider doesn't need cache refresh - data is already local
}
var cachedSubscriptionCount: Int? {
dataManager.subscriptionCount
}
}