Files
QueueCube/ios/QueueCube/Backend/Settings.swift
T
buzzert 8723955705 ios: add share extension to queue and play video links
Embed an iOS and Catalyst share extension using existing discovery and player APIs. Share saved players through an App Group snapshot, preserve the queue, and verify advancing playback before reporting success.

Cover URL provider transport, settings migration, discovery addresses, duplicate handling, cancellation, and partial enqueue/play outcomes with focused tests. Document authentication and signing limits.
2026-10-20 22:08:12 -07:00

140 lines
4.2 KiB
Swift

//
// Settings.swift
// QueueCube
//
// Created by James Magahern on 6/10/25.
//
import Foundation
struct Settings: Codable
{
var selectedServer: Server?
var configuredServers: [Server] {
willSet {
// Set selected server to whatever the first server is, if we're adding the first one.
if configuredServers.isEmpty && !newValue.isEmpty && selectedServer == nil {
selectedServer = newValue.first
}
// If the selected server is being removed, set it to something else
if !newValue.contains(where: { $0 == selectedServer }) {
selectedServer = newValue.first // nil if empty
}
}
}
var isConfigured: Bool { !configuredServers.isEmpty }
static func fromDefaults(defaults: UserDefaults = .standard,
sharedDefaults: UserDefaults? = UserDefaults(suiteName: appGroupIdentifier)) -> Settings {
let settings = Settings(
selectedServer: defaults[SelectedServerKey.self],
configuredServers: defaults[ConfiguredServersKey.self]
)
settings.publishForSharing(defaults: sharedDefaults)
return settings
}
func save(defaults: UserDefaults = .standard,
sharedDefaults: UserDefaults? = UserDefaults(suiteName: Self.appGroupIdentifier)) {
defaults[ConfiguredServersKey.self] = configuredServers
defaults[SelectedServerKey.self] = selectedServer
publishForSharing(defaults: sharedDefaults)
postSettingsChanged()
}
// The containing app remains the owner of settings. Publish one snapshot so
// the extension never observes a selected player from a different revision.
static let appGroupIdentifier = "group.net.buzzert.QueueCube"
private static let sharedSettingsKey = "shareSettings.v1"
static func fromSharedDefaults(defaults: UserDefaults? = UserDefaults(suiteName: appGroupIdentifier)) -> Settings {
guard let defaults,
let data = defaults.data(forKey: sharedSettingsKey),
let settings = try? PropertyListDecoder().decode(Settings.self, from: data)
else { return Settings(selectedServer: nil, configuredServers: []) }
return settings
}
private func publishForSharing(defaults: UserDefaults?) {
guard let defaults,
let data = try? PropertyListEncoder().encode(self) else { return }
defaults.set(data, forKey: Self.sharedSettingsKey)
}
func postSettingsChanged() {
NotificationCenter.default.post(name: .settingsChanged, object: nil)
}
// MARK: - Modifiers
func selectedServer(_ server: Server?) -> Self {
var copy = self
copy.selectedServer = server
return copy
}
func configuredServers(_ servers: [Server]) -> Self {
var copy = self
copy.configuredServers = servers
return copy
}
// MARK: - Types
enum Keys: String
{
case selectedServer
case configuredServers
}
fileprivate protocol Key
{
associatedtype Value: Codable
static var defaultValue: Value { get }
static var key: String { get }
}
private struct ConfiguredServersKey: Key {
static var defaultValue: [Server] { [] }
}
private struct SelectedServerKey: Key {
static var defaultValue: Server? { nil }
}
}
extension UserDefaults
{
fileprivate subscript<T: Settings.Key>(_ type: T.Type) -> T.Value {
get {
guard let data = data(forKey: type.key)
else { return type.defaultValue }
guard let value = try? PropertyListDecoder().decode(type.Value, from: data)
else { return type.defaultValue }
return value
}
set {
let data = try? PropertyListEncoder().encode(newValue)
set(data, forKey: type.key)
}
}
}
extension Settings.Key
{
static var key: String { Mirror(reflecting: Self.self).description }
}
extension Notification.Name
{
static let settingsChanged = Notification.Name("settingsChanged")
}