mirror of
https://github.com/yattee/yattee.git
synced 2026-02-20 01:39:46 +00:00
Yattee v2 rewrite
This commit is contained in:
237
Yattee/Views/Settings/InstancePickerSheet.swift
Normal file
237
Yattee/Views/Settings/InstancePickerSheet.swift
Normal file
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// InstancePickerSheet.swift
|
||||
// Yattee
|
||||
//
|
||||
// Quick instance picker sheet for switching between backends.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct InstancePickerSheet: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
|
||||
private var instancesManager: InstancesManager? { appEnvironment?.instancesManager }
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
if let instancesManager {
|
||||
// Enabled instances
|
||||
Section(String(localized: "instance.enabled")) {
|
||||
ForEach(instancesManager.enabledInstances, id: \.url) { instance in
|
||||
let isActive = instance.id == instancesManager.activeInstance?.id
|
||||
PickerInstanceRow(instance: instance, isEnabled: true, isPrimary: isActive) {
|
||||
instancesManager.setActive(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Disabled instances
|
||||
let disabledInstances = instancesManager.instances.filter { !$0.isEnabled }
|
||||
if !disabledInstances.isEmpty {
|
||||
Section(String(localized: "instance.disabled")) {
|
||||
ForEach(disabledInstances, id: \.url) { instance in
|
||||
PickerInstanceRow(instance: instance, isEnabled: false, isPrimary: false) {
|
||||
instancesManager.toggleEnabled(instance)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add instance
|
||||
Section {
|
||||
NavigationLink {
|
||||
QuickAddInstanceView()
|
||||
} label: {
|
||||
Label(String(localized: "instance.add"), systemImage: "plus.circle")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle(String(localized: "instance.picker.title"))
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button(role: .cancel) {
|
||||
dismiss()
|
||||
} label: {
|
||||
Label("Close", systemImage: "xmark")
|
||||
.labelStyle(.iconOnly)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.presentationDetents([.medium, .large])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Picker Instance Row
|
||||
|
||||
private struct PickerInstanceRow: View {
|
||||
let instance: Instance
|
||||
let isEnabled: Bool
|
||||
let isPrimary: Bool
|
||||
let onTap: () -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 12) {
|
||||
// Type icon
|
||||
Image(systemName: instanceIcon)
|
||||
.font(.title3)
|
||||
.foregroundStyle(isEnabled ? .primary : .secondary)
|
||||
.frame(width: 32)
|
||||
|
||||
// Info
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text(instance.displayName)
|
||||
.font(.body)
|
||||
.foregroundStyle(isEnabled ? .primary : .secondary)
|
||||
|
||||
HStack(spacing: 4) {
|
||||
Text(instance.type.displayName)
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
Text("•")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
|
||||
Text(instance.url.host ?? "")
|
||||
.font(.caption)
|
||||
.foregroundStyle(.tertiary)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
// Checkmark for primary, or plus for disabled
|
||||
if isEnabled {
|
||||
if isPrimary {
|
||||
Image(systemName: "checkmark")
|
||||
.foregroundStyle(.tint)
|
||||
}
|
||||
} else {
|
||||
Image(systemName: "plus.circle")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture {
|
||||
onTap()
|
||||
}
|
||||
}
|
||||
|
||||
private var instanceIcon: String {
|
||||
switch instance.type {
|
||||
case .invidious:
|
||||
return "play.rectangle"
|
||||
case .piped:
|
||||
return "waveform"
|
||||
case .peertube:
|
||||
return "film"
|
||||
case .yatteeServer:
|
||||
return "server.rack"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Quick Add Instance View
|
||||
|
||||
private struct QuickAddInstanceView: View {
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
@Environment(\.appEnvironment) private var appEnvironment
|
||||
|
||||
@State private var urlString = ""
|
||||
@State private var isDetecting = false
|
||||
@State private var errorMessage: String?
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section {
|
||||
TextField(String(localized: "instance.url.placeholder"), text: $urlString)
|
||||
.textContentType(.URL)
|
||||
.autocorrectionDisabled()
|
||||
#if os(iOS)
|
||||
.textInputAutocapitalization(.never)
|
||||
.keyboardType(.URL)
|
||||
#endif
|
||||
} header: {
|
||||
Text(String(localized: "instance.url.header"))
|
||||
} footer: {
|
||||
Text(String(localized: "instance.url.footer"))
|
||||
}
|
||||
|
||||
if let error = errorMessage {
|
||||
Section {
|
||||
Text(error)
|
||||
.foregroundStyle(.red)
|
||||
}
|
||||
}
|
||||
}
|
||||
#if os(iOS)
|
||||
.scrollDismissesKeyboard(.interactively)
|
||||
#endif
|
||||
.navigationTitle(String(localized: "instance.add"))
|
||||
#if os(iOS)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
#endif
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .confirmationAction) {
|
||||
Button {
|
||||
addInstance()
|
||||
} label: {
|
||||
if isDetecting {
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
} else {
|
||||
Text(String(localized: "common.add"))
|
||||
}
|
||||
}
|
||||
.disabled(urlString.isEmpty || isDetecting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func addInstance() {
|
||||
guard let appEnvironment else { return }
|
||||
|
||||
// Normalize URL
|
||||
var normalizedURL = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if !normalizedURL.hasPrefix("http://") && !normalizedURL.hasPrefix("https://") {
|
||||
normalizedURL = "https://" + normalizedURL
|
||||
}
|
||||
|
||||
guard let url = URL(string: normalizedURL) else {
|
||||
errorMessage = String(localized: "instance.error.invalidURL")
|
||||
return
|
||||
}
|
||||
|
||||
isDetecting = true
|
||||
errorMessage = nil
|
||||
|
||||
Task {
|
||||
let type = await appEnvironment.instanceDetector.detect(url: url)
|
||||
|
||||
await MainActor.run {
|
||||
if let type {
|
||||
let instance = Instance(type: type, url: url, name: url.host ?? normalizedURL, isEnabled: true)
|
||||
appEnvironment.instancesManager.addInstance(instance)
|
||||
dismiss()
|
||||
} else {
|
||||
errorMessage = String(localized: "instance.error.detectionFailed")
|
||||
isDetecting = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Preview
|
||||
|
||||
#Preview {
|
||||
InstancePickerSheet()
|
||||
.appEnvironment(.preview)
|
||||
}
|
||||
Reference in New Issue
Block a user