mirror of
https://github.com/yattee/yattee.git
synced 2026-08-06 15:21:29 +00:00
When an Invidious/Piped account is active, several views read the local SwiftData subscription store instead of the account, so counts contradicted each other (Home tile said 10 while the Channels list showed the server's 2): - Home Channels tile now uses the provider's count via new SubscriptionService.cachedSubscriptionCount (fetched once in the background when the server cache isn't populated yet) - Export footer and export content use the active account's list; export runs async with a spinner and surfaces fetch errors as a toast - CSV/OPML import routes through SubscriptionService.importSubscriptions, subscribing on the server for server accounts instead of silently writing to the invisible local store - ChannelView subscribe-state is corrected via the provider after the optimistic local-store read; unused *Sync write helpers removed - Server-account subscribe/unsubscribe/import now post subscriptionsDidChange so other views refresh - New "Delete Local Subscription Data" section in Subscriptions settings (visible with a server account) clears the local store and queues CloudKit deletions so iCloud doesn't restore it
84 lines
2.9 KiB
Swift
84 lines
2.9 KiB
Swift
//
|
|
// SubscriptionProvider.swift
|
|
// Yattee
|
|
//
|
|
// Protocol defining subscription management operations.
|
|
// Implementations handle different subscription sources (local, Invidious, Piped).
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Protocol for subscription management providers.
|
|
/// Each provider implementation handles a specific subscription source.
|
|
@MainActor
|
|
protocol SubscriptionProvider: Sendable {
|
|
/// The type of subscription account this provider handles.
|
|
var accountType: SubscriptionAccountType { get }
|
|
|
|
/// Fetches all subscriptions from the provider.
|
|
/// - Returns: Array of channels the user is subscribed to.
|
|
func fetchSubscriptions() async throws -> [Channel]
|
|
|
|
/// Subscribes to a channel.
|
|
/// - Parameter channel: The channel to subscribe to.
|
|
func subscribe(to channel: Channel) async throws
|
|
|
|
/// Unsubscribes from a channel.
|
|
/// - Parameter channelID: The channel ID to unsubscribe from.
|
|
func unsubscribe(from channelID: String) async throws
|
|
|
|
/// Checks if subscribed to a channel.
|
|
/// - Parameter channelID: The channel ID to check.
|
|
/// - Returns: `true` if subscribed, `false` otherwise.
|
|
func isSubscribed(to channelID: String) async -> Bool
|
|
|
|
/// The subscription count known without a network fetch.
|
|
/// Returns nil when the provider hasn't populated its cache yet.
|
|
var cachedSubscriptionCount: Int? { get }
|
|
|
|
/// Refreshes the local cache of subscriptions from the remote source.
|
|
/// For local provider, this is a no-op.
|
|
func refreshCache() async throws
|
|
}
|
|
|
|
// MARK: - Default Implementations
|
|
|
|
extension SubscriptionProvider {
|
|
/// Default implementation for providers that don't need cache refresh.
|
|
func refreshCache() async throws {
|
|
// No-op by default
|
|
}
|
|
}
|
|
|
|
// MARK: - Subscription Provider Error
|
|
|
|
/// Errors that can occur during subscription provider operations.
|
|
enum SubscriptionProviderError: Error, LocalizedError, Equatable, Sendable {
|
|
case notAuthenticated
|
|
case networkError(String)
|
|
case channelNotFound
|
|
case alreadySubscribed
|
|
case notSubscribed
|
|
case instanceNotConfigured
|
|
case operationFailed(String)
|
|
|
|
var errorDescription: String? {
|
|
switch self {
|
|
case .notAuthenticated:
|
|
return String(localized: "subscription.error.notAuthenticated")
|
|
case .networkError(let message):
|
|
return String(localized: "subscription.error.network \(message)")
|
|
case .channelNotFound:
|
|
return String(localized: "subscription.error.channelNotFound")
|
|
case .alreadySubscribed:
|
|
return String(localized: "subscription.error.alreadySubscribed")
|
|
case .notSubscribed:
|
|
return String(localized: "subscription.error.notSubscribed")
|
|
case .instanceNotConfigured:
|
|
return String(localized: "subscription.error.instanceNotConfigured")
|
|
case .operationFailed(let message):
|
|
return String(localized: "subscription.error.operationFailed \(message)")
|
|
}
|
|
}
|
|
}
|