tv-anarchy/Sources/TVAnarchyiOS/SettingsView.swift
Natalie 17cf518418 feat(ios): downloads (DownloadManager/DownloadsView), remote control view + bridge/player refinements
(cherry picked from commit a1f7e44e17bb41f48976b69f4dbe5278cbad06b2)
2026-06-09 06:38:45 -07:00

80 lines
3.4 KiB
Swift

// Settings tab: bridge connection, playback buffer, prefetch-ahead policy, and
// offline storage management.
import SwiftUI
import LilithDesignTokens
struct SettingsScreen: View {
@EnvironmentObject private var settings: BridgeSettings
@EnvironmentObject private var downloads: DownloadManager
@State private var portText = ""
var body: some View {
NavigationStack {
Form {
Section("Bridge") {
LabeledContent("Host") {
TextField("127.0.0.1", text: $settings.host)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.multilineTextAlignment(.trailing)
}
LabeledContent("Port") {
TextField("8787", text: $portText)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.onChange(of: portText) { _, new in
if let p = Int(new), p > 0, p < 65536 { settings.port = p }
}
}
LabeledContent("Token") {
SecureField("optional", text: $settings.token)
.multilineTextAlignment(.trailing)
}
}
Section {
VStack(alignment: .leading) {
Text("Buffer: \(settings.networkCachingMs) ms")
Slider(
value: Binding(
get: { Double(settings.networkCachingMs) },
set: { settings.networkCachingMs = Int($0) }
),
in: 300...8000, step: 100
)
}
} header: {
Text("Playback")
} footer: {
Text("Higher buffer absorbs more network jitter but makes seeking slower. 1500 ms is a good default over the mesh.")
}
Section {
Toggle("Prefetch ahead", isOn: $settings.prefetchEnabled)
if settings.prefetchEnabled {
Stepper("Keep next \(settings.prefetchCount) episode\(settings.prefetchCount == 1 ? "" : "s")",
value: $settings.prefetchCount, in: 1...10)
}
} header: {
Text("Offline")
} footer: {
Text("While you watch a series, the next episodes download automatically so they're ready offline.")
}
Section("Storage") {
LabeledContent("Downloaded", value: "\(downloads.entries.count) episodes")
LabeledContent("On disk", value: ByteCountFormatter.string(fromByteCount: downloads.totalBytes, countStyle: .file))
if !downloads.entries.isEmpty {
Button("Delete all offline", role: .destructive) {
for e in downloads.entries { downloads.delete(episodeId: e.episodeId) }
}
}
}
}
.navigationTitle("Settings")
.onAppear { portText = String(settings.port) }
}
}
}