mirror of
https://github.com/yattee/yattee.git
synced 2026-10-23 21:22:53 +00:00
Creating a playlist while an Invidious account was the selected context wrote it to the device. No request left the app, the row never appeared in the account's list, and the form had come up without its visibility picker — the two symptoms of the same thing: the sheet took the local branch. The destination was decided correctly and then read wrongly. The button resolved the account and put it in a companion `@State`, and the sheet's content closure — created by an earlier body evaluation — read that property as it stood *then*, which was nil. Measured on device: the create resolved the account and validity fine, and one millisecond later the sheet rendered the local form while every live value around it still named the account. Whether the stale read is corrected depends on whether anything re-renders the view before the form is saved, which is why the same steps against Piped had landed on the server. The destination now travels with the presentation as `PlaylistCreationTarget`, so the sheet is handed the account rather than looking one up. One resolver answers for both the create affordances and the row menus, and it is where the rule lives: a context reading an account's data creates on that account or creates nothing at all — a toolbar item can outlive the condition that put it there, and a write with nowhere to go is not a write to somewhere else. A context that fell back to local reads is the one case that does create locally, and it is honest: those are the rows on screen, with the banner above them saying why. Two more from the same pass: - Subscriptions stopped giving two answers at once. Under a fallback the local feed asks for a Yattee Server nobody has configured, so "Add a subscriptions account in Settings" was stacking under a banner saying this account needs signing in again. The banner names the cause, so the feed error stands down while it is up. - The playlists banner no longer lands on the search field. A navigation bar carrying a search field keeps its full height over content that cannot scroll, and the empty, loading and error states could not — they scroll now, which also gives pull to refresh somewhere to live.
73 lines
2.4 KiB
Swift
73 lines
2.4 KiB
Swift
//
|
|
// PlaylistCreationTarget.swift
|
|
// Yattee
|
|
//
|
|
// Where a newly created playlist goes.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
/// Where the playlists screen would put a playlist it is asked to create.
|
|
///
|
|
/// Kept as a value rather than left to be re-derived at save time: a creation
|
|
/// travels with the sheet that collects it, so the destination cannot move
|
|
/// while the form is open — and cannot be read one update late, which is how a
|
|
/// creation on an account ended up in local data with its visibility picker
|
|
/// missing.
|
|
enum PlaylistCreationTarget: Identifiable, Equatable {
|
|
/// The device's own playlists.
|
|
case local
|
|
/// A server-side playlist on this account.
|
|
case account(Account)
|
|
|
|
var id: String {
|
|
switch self {
|
|
case .local: "local"
|
|
case let .account(account): account.id.uuidString
|
|
}
|
|
}
|
|
|
|
/// The account to create on, or `nil` when the device is the destination.
|
|
var account: Account? {
|
|
switch self {
|
|
case .local: nil
|
|
case let .account(account): account
|
|
}
|
|
}
|
|
|
|
/// Where a creation started right now would go, or `nil` when there is
|
|
/// nowhere to put one and the affordance must not be offered.
|
|
///
|
|
/// Follows the *effective* context, which is what the rows on screen came
|
|
/// from: a selected account that cannot be used falls back to local reads,
|
|
/// and creating locally is then the same answer the list is already giving
|
|
/// — with the banner above it saying why. What never happens is the other
|
|
/// direction: an account context whose server cannot be written to right
|
|
/// now creates nothing at all, rather than quietly retargeting the write at
|
|
/// local data the user did not choose.
|
|
///
|
|
/// - Parameters:
|
|
/// - account: the account `effectiveContext` names, when it resolves.
|
|
/// - allowsServerMutations: the validator's write gate.
|
|
/// - capabilities: what that account's backend can do.
|
|
static func resolve(
|
|
effectiveContext: DataContext,
|
|
account: Account?,
|
|
allowsServerMutations: Bool,
|
|
capabilities: AccountCapabilities?
|
|
) -> PlaylistCreationTarget? {
|
|
switch effectiveContext {
|
|
case .local:
|
|
return .local
|
|
|
|
case .account:
|
|
guard let account,
|
|
allowsServerMutations,
|
|
capabilities?.supportsPlaylistWrites == true
|
|
else { return nil }
|
|
|
|
return .account(account)
|
|
}
|
|
}
|
|
}
|