Files
yattee/Yattee/Services/Subscriptions/SubscriptionProviderFactory.swift
Arkadiusz Fal 0c5745e4fb Let a subscribe name the account it lands on
Subscribing went wherever the selected account happened to be, and the
button offering it read the local store first — so under a server account
it claimed a channel was subscribed when only the device knew about it.
The button now asks the active provider and nothing else, except under a
local context, where the local store is that provider and can answer
without a hop.

The channel menu gains a destination list: local, plus every account
whose backend does subscriptions, whose instance is switched on, and
which has a credential on this device. A row names its own target, so it
deliberately does not care whether the selected context can be written
to — that is precisely the case where naming somewhere else is the only
way to subscribe at all. The main button is disabled instead, with the
reason beside it. tvOS reaches the same list through its own button and a
full-screen picker, because a menu cannot be driven with a remote.

Membership is asked for only once the rows are on screen: an unwarmed
provider answers by fetching that account's entire subscription list, and
asking every destination up front is a burst nobody requested. Answers
carry the round they were asked under, and "not asked yet" is kept apart
from "asked, not subscribed" — a checkmark the user acts on must not be a
guess.

SubscriptionService grows the destination-parameterized subscribe,
unsubscribe and membership calls the menu writes through, with the
context-implicit ones left as wrappers over the current context. A
membership answer that outlived its login now says nothing rather than
"no". Building a provider moves behind SubscriptionProviderFactory, which
is what lets a test see which destination a write was routed to without a
server behind it.

The unsubscribe-only screens are untouched: they act on the account being
browsed, which is the account whose list they are showing.
2026-09-04 23:18:04 +02:00

62 lines
2.0 KiB
Swift

//
// SubscriptionProviderFactory.swift
// Yattee
//
// Builds the provider that serves one account's subscriptions.
//
import Foundation
/// Makes the provider for an account.
///
/// A seam rather than a switch inside `SubscriptionService`: the provider is
/// what a destination write actually lands on, so which one a context resolves
/// to has to be observable without a server behind it.
@MainActor
protocol SubscriptionProviderFactory {
/// The provider for an account on a resolved, subscription-capable
/// instance, or nil when the backend has no subscriptions API.
func provider(
for account: Account,
instance: Instance,
accountsManager: AccountsManager
) -> (any SubscriptionProvider)?
}
/// The app's factory: one provider per backend that has an accounts API.
@MainActor
struct DefaultSubscriptionProviderFactory: SubscriptionProviderFactory {
let invidiousAPI: InvidiousAPI
let pipedAPI: PipedAPI
let invidiousCredentialsManager: InvidiousCredentialsManager
let pipedCredentialsManager: PipedCredentialsManager
let settingsManager: SettingsManager
func provider(
for account: Account,
instance: Instance,
accountsManager: AccountsManager
) -> (any SubscriptionProvider)? {
switch instance.type {
case .invidious:
return InvidiousSubscriptionProvider(
account: account,
invidiousAPI: invidiousAPI,
credentialsManager: invidiousCredentialsManager,
accountsManager: accountsManager,
settingsManager: settingsManager
)
case .piped:
return PipedSubscriptionProvider(
account: account,
pipedAPI: pipedAPI,
credentialsManager: pipedCredentialsManager,
accountsManager: accountsManager,
settingsManager: settingsManager
)
default:
return nil
}
}
}