mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 13:33:42 +00:00
Extract instance/account validation status view
This commit is contained in:
parent
7446c945b5
commit
78a0291e5d
@ -7,23 +7,26 @@ final class AccountValidator: Service {
|
||||
let account: Instance.Account?
|
||||
|
||||
var formObjectID: Binding<String>
|
||||
var valid: Binding<Bool>
|
||||
var validated: Binding<Bool>
|
||||
var isValid: Binding<Bool>
|
||||
var isValidated: Binding<Bool>
|
||||
var isValidating: Binding<Bool>
|
||||
var error: Binding<String?>?
|
||||
|
||||
init(
|
||||
url: String,
|
||||
account: Instance.Account? = nil,
|
||||
id: Binding<String>,
|
||||
valid: Binding<Bool>,
|
||||
validated: Binding<Bool>,
|
||||
isValid: Binding<Bool>,
|
||||
isValidated: Binding<Bool>,
|
||||
isValidating: Binding<Bool>,
|
||||
error: Binding<String?>? = nil
|
||||
) {
|
||||
self.url = url
|
||||
self.account = account
|
||||
formObjectID = id
|
||||
self.valid = valid
|
||||
self.validated = validated
|
||||
self.isValid = isValid
|
||||
self.isValidated = isValidated
|
||||
self.isValidating = isValidating
|
||||
self.error = error
|
||||
|
||||
super.init(baseURL: url)
|
||||
@ -50,18 +53,20 @@ final class AccountValidator: Service {
|
||||
return
|
||||
}
|
||||
|
||||
self.valid.wrappedValue = true
|
||||
self.isValid.wrappedValue = true
|
||||
self.error?.wrappedValue = nil
|
||||
self.validated.wrappedValue = true
|
||||
}
|
||||
.onFailure { error in
|
||||
guard self.url == self.formObjectID.wrappedValue else {
|
||||
return
|
||||
}
|
||||
|
||||
self.valid.wrappedValue = false
|
||||
self.isValid.wrappedValue = false
|
||||
self.error?.wrappedValue = error.userMessage
|
||||
self.validated.wrappedValue = true
|
||||
}
|
||||
.onCompletion { _ in
|
||||
self.isValidated.wrappedValue = true
|
||||
self.isValidating.wrappedValue = false
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,22 +80,25 @@ final class AccountValidator: Service {
|
||||
return
|
||||
}
|
||||
|
||||
self.valid.wrappedValue = true
|
||||
self.validated.wrappedValue = true
|
||||
self.isValid.wrappedValue = true
|
||||
}
|
||||
.onFailure { _ in
|
||||
guard self.account!.sid == self.formObjectID.wrappedValue else {
|
||||
return
|
||||
}
|
||||
|
||||
self.valid.wrappedValue = false
|
||||
self.validated.wrappedValue = true
|
||||
self.isValid.wrappedValue = false
|
||||
}
|
||||
.onCompletion { _ in
|
||||
self.isValidated.wrappedValue = true
|
||||
self.isValidating.wrappedValue = false
|
||||
}
|
||||
}
|
||||
|
||||
func reset() {
|
||||
valid.wrappedValue = false
|
||||
validated.wrappedValue = false
|
||||
isValid.wrappedValue = false
|
||||
isValidated.wrappedValue = false
|
||||
isValidating.wrappedValue = false
|
||||
error?.wrappedValue = nil
|
||||
}
|
||||
|
||||
|
@ -11,15 +11,6 @@ final class InvidiousAPI: Service, ObservableObject {
|
||||
@Published var validInstance = true
|
||||
@Published var signedIn = true
|
||||
|
||||
init() {
|
||||
super.init()
|
||||
|
||||
#if os(tvOS)
|
||||
// TODO: remove
|
||||
setAccount(.init(id: UUID(), name: "", url: "https://invidious.home.arekf.net", sid: "RpoS7YPPK2-QS81jJF9z4KSQAjmzsOnMpn84c73-GQ8="))
|
||||
#endif
|
||||
}
|
||||
|
||||
func setAccount(_ account: Instance.Account) {
|
||||
self.account = account
|
||||
|
||||
|
@ -101,6 +101,9 @@
|
||||
376CD21626FBE18D001E1AC1 /* Instance+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376CD21526FBE18D001E1AC1 /* Instance+Fixtures.swift */; };
|
||||
376CD21726FBE18D001E1AC1 /* Instance+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376CD21526FBE18D001E1AC1 /* Instance+Fixtures.swift */; };
|
||||
376CD21826FBE18D001E1AC1 /* Instance+Fixtures.swift in Sources */ = {isa = PBXBuildFile; fileRef = 376CD21526FBE18D001E1AC1 /* Instance+Fixtures.swift */; };
|
||||
37732FF02703A26300F04329 /* ValidationStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FEF2703A26300F04329 /* ValidationStatusView.swift */; };
|
||||
37732FF12703A26300F04329 /* ValidationStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FEF2703A26300F04329 /* ValidationStatusView.swift */; };
|
||||
37732FF22703A26300F04329 /* ValidationStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37732FEF2703A26300F04329 /* ValidationStatusView.swift */; };
|
||||
377A20A92693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
||||
377A20AA2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
||||
377A20AB2693C9A2002842B8 /* TypedContentAccessors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */; };
|
||||
@ -327,6 +330,7 @@
|
||||
37666BA927023AF000F869E5 /* AccountSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSelectionView.swift; sourceTree = "<group>"; };
|
||||
376B2E0626F920D600B1D64D /* SignInRequiredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignInRequiredView.swift; sourceTree = "<group>"; };
|
||||
376CD21526FBE18D001E1AC1 /* Instance+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Instance+Fixtures.swift"; sourceTree = "<group>"; };
|
||||
37732FEF2703A26300F04329 /* ValidationStatusView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidationStatusView.swift; sourceTree = "<group>"; };
|
||||
377A20A82693C9A2002842B8 /* TypedContentAccessors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypedContentAccessors.swift; sourceTree = "<group>"; };
|
||||
3788AC2626F6840700F6BAA9 /* WatchNowSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSection.swift; sourceTree = "<group>"; };
|
||||
3788AC2A26F6842D00F6BAA9 /* WatchNowSectionBody.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchNowSectionBody.swift; sourceTree = "<group>"; };
|
||||
@ -558,6 +562,7 @@
|
||||
37484C1C26FC83A400287258 /* InstancesSettingsView.swift */,
|
||||
37484C1826FC837400287258 /* PlaybackSettingsView.swift */,
|
||||
37B044B626F7AB9000E1419D /* SettingsView.swift */,
|
||||
37732FEF2703A26300F04329 /* ValidationStatusView.swift */,
|
||||
);
|
||||
path = Settings;
|
||||
sourceTree = "<group>";
|
||||
@ -1119,6 +1124,7 @@
|
||||
37BD07BB2698AB60003EBB87 /* AppSidebarNavigation.swift in Sources */,
|
||||
37D4B0E42671614900C925CA /* PearvidiousApp.swift in Sources */,
|
||||
3797758B2689345500DD52A8 /* Store.swift in Sources */,
|
||||
37732FF02703A26300F04329 /* ValidationStatusView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@ -1185,6 +1191,7 @@
|
||||
3797758C2689345500DD52A8 /* Store.swift in Sources */,
|
||||
37141674267A8E10006CA35D /* Country.swift in Sources */,
|
||||
37AAF2A126741C97007FC770 /* SubscriptionsView.swift in Sources */,
|
||||
37732FF12703A26300F04329 /* ValidationStatusView.swift in Sources */,
|
||||
37BA794C26DC30EC002A0235 /* AppSidebarPlaylists.swift in Sources */,
|
||||
37D4B19826717E1500C925CA /* Video.swift in Sources */,
|
||||
375168D72700FDB8008F96A6 /* Debounce.swift in Sources */,
|
||||
@ -1250,6 +1257,7 @@
|
||||
37AAF29226740715007FC770 /* Channel.swift in Sources */,
|
||||
37EAD86D267B9C5600D9E01B /* SponsorBlockAPI.swift in Sources */,
|
||||
37B81B0726D2D6CF00675966 /* PlaybackModel.swift in Sources */,
|
||||
37732FF22703A26300F04329 /* ValidationStatusView.swift in Sources */,
|
||||
37666BAA27023AF000F869E5 /* AccountSelectionView.swift in Sources */,
|
||||
3765788B2685471400D4EA09 /* Playlist.swift in Sources */,
|
||||
373CFADD269663F1003CB2C6 /* Thumbnail.swift in Sources */,
|
||||
|
@ -5,7 +5,7 @@ import SwiftUI
|
||||
struct AddToPlaylistView: View {
|
||||
@EnvironmentObject<PlaylistsModel> private var model
|
||||
|
||||
@State var video: Video
|
||||
let video: Video
|
||||
|
||||
@Environment(\.dismiss) private var dismiss
|
||||
|
||||
|
@ -8,8 +8,9 @@ struct AccountFormView: View {
|
||||
@State private var name = ""
|
||||
@State private var sid = ""
|
||||
|
||||
@State private var valid = false
|
||||
@State private var validated = false
|
||||
@State private var isValid = false
|
||||
@State private var isValidated = false
|
||||
@State private var isValidating = false
|
||||
@State private var validationDebounce = Debounce()
|
||||
|
||||
@FocusState private var focused: Bool
|
||||
@ -82,12 +83,12 @@ struct AccountFormView: View {
|
||||
|
||||
var footer: some View {
|
||||
HStack {
|
||||
validationStatus
|
||||
ValidationStatusView(isValid: $isValid, isValidated: $isValidated, isValidating: $isValidating, error: .constant(nil))
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Save", action: submitForm)
|
||||
.disabled(!valid)
|
||||
.disabled(!isValid)
|
||||
#if !os(tvOS)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
#endif
|
||||
@ -99,17 +100,6 @@ struct AccountFormView: View {
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
var validationStatus: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: valid ? "checkmark.circle.fill" : "xmark.circle.fill")
|
||||
.foregroundColor(valid ? .green : .red)
|
||||
VStack(alignment: .leading) {
|
||||
Text(valid ? "Account found" : "Invalid account details")
|
||||
}
|
||||
}
|
||||
.opacity(validated ? 1 : 0)
|
||||
}
|
||||
|
||||
private func initializeForm() {
|
||||
focused = true
|
||||
}
|
||||
@ -122,13 +112,15 @@ struct AccountFormView: View {
|
||||
return
|
||||
}
|
||||
|
||||
validationDebounce.debouncing(2) {
|
||||
isValidating = true
|
||||
|
||||
validationDebounce.debouncing(1) {
|
||||
validator.validateAccount()
|
||||
}
|
||||
}
|
||||
|
||||
private func submitForm() {
|
||||
guard valid else {
|
||||
guard isValid else {
|
||||
return
|
||||
}
|
||||
|
||||
@ -143,8 +135,9 @@ struct AccountFormView: View {
|
||||
url: instance.url,
|
||||
account: Instance.Account(instanceID: instance.id, url: instance.url, sid: sid),
|
||||
id: $sid,
|
||||
valid: $valid,
|
||||
validated: $validated
|
||||
isValid: $isValid,
|
||||
isValidated: $isValidated,
|
||||
isValidating: $isValidating
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,6 @@ struct InstanceDetailsSettingsView: View {
|
||||
List {
|
||||
Section(header: Text("Accounts")) {
|
||||
ForEach(instances.accounts(instanceID), id: \.self) { account in
|
||||
|
||||
#if !os(tvOS)
|
||||
HStack(spacing: 2) {
|
||||
Text(account.description)
|
||||
@ -27,13 +26,13 @@ struct InstanceDetailsSettingsView: View {
|
||||
}
|
||||
.swipeActions(edge: .leading, allowsFullSwipe: true) {
|
||||
if instances.defaultAccount != account {
|
||||
Button("Make Default", action: { makeDefault(account) })
|
||||
Button("Make Default") { makeDefault(account) }
|
||||
} else {
|
||||
Button("Reset Default", action: resetDefaultAccount)
|
||||
}
|
||||
}
|
||||
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
|
||||
Button("Remove", role: .destructive, action: { removeAccount(account) })
|
||||
Button("Remove", role: .destructive) { removeAccount(account) }
|
||||
}
|
||||
|
||||
#else
|
||||
@ -47,8 +46,8 @@ struct InstanceDetailsSettingsView: View {
|
||||
}
|
||||
}
|
||||
.contextMenu {
|
||||
Button("Toggle Default", action: { toggleDefault(account) })
|
||||
Button("Remove", role: .destructive, action: { removeAccount(account) })
|
||||
Button("Toggle Default") { toggleDefault(account) }
|
||||
Button("Remove", role: .destructive) { removeAccount(account) }
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
@ -6,8 +6,9 @@ struct InstanceFormView: View {
|
||||
@State private var name = ""
|
||||
@State private var url = ""
|
||||
|
||||
@State private var valid = false
|
||||
@State private var validated = false
|
||||
@State private var isValid = false
|
||||
@State private var isValidated = false
|
||||
@State private var isValidating = false
|
||||
@State private var validationError: String?
|
||||
@State private var validationDebounce = Debounce()
|
||||
|
||||
@ -73,21 +74,24 @@ struct InstanceFormView: View {
|
||||
private var formFields: some View {
|
||||
Group {
|
||||
TextField("Name", text: $name, prompt: Text("Instance Name (optional)"))
|
||||
|
||||
.focused($nameFieldFocused)
|
||||
|
||||
TextField("URL", text: $url, prompt: Text("https://invidious.home.net"))
|
||||
#if !os(macOS)
|
||||
.autocapitalization(.none)
|
||||
.keyboardType(.URL)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
private var footer: some View {
|
||||
HStack(alignment: .center) {
|
||||
validationStatus
|
||||
ValidationStatusView(isValid: $isValid, isValidated: $isValidated, isValidating: $isValidating, error: $validationError)
|
||||
|
||||
Spacer()
|
||||
|
||||
Button("Save", action: submitForm)
|
||||
.disabled(!valid)
|
||||
.disabled(!isValid)
|
||||
#if !os(tvOS)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
#endif
|
||||
@ -98,31 +102,13 @@ struct InstanceFormView: View {
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
private var validationStatus: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: valid ? "checkmark.circle.fill" : "xmark.circle.fill")
|
||||
.foregroundColor(valid ? .green : .red)
|
||||
VStack(alignment: .leading) {
|
||||
Text(valid ? "Connected successfully" : "Connection failed")
|
||||
if !valid {
|
||||
Text(validationError ?? "Unknown Error")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 35)
|
||||
}
|
||||
.opacity(validated ? 1 : 0)
|
||||
}
|
||||
|
||||
var validator: AccountValidator {
|
||||
AccountValidator(
|
||||
url: url,
|
||||
id: $url,
|
||||
valid: $valid,
|
||||
validated: $validated,
|
||||
isValid: $isValid,
|
||||
isValidated: $isValidated,
|
||||
isValidating: $isValidating,
|
||||
error: $validationError
|
||||
)
|
||||
}
|
||||
@ -135,6 +121,8 @@ struct InstanceFormView: View {
|
||||
return
|
||||
}
|
||||
|
||||
isValidating = true
|
||||
|
||||
validationDebounce.debouncing(2) {
|
||||
validator.validateInstance()
|
||||
}
|
||||
@ -145,7 +133,7 @@ struct InstanceFormView: View {
|
||||
}
|
||||
|
||||
func submitForm() {
|
||||
guard valid else {
|
||||
guard isValid else {
|
||||
return
|
||||
}
|
||||
|
||||
|
48
Shared/Settings/ValidationStatusView.swift
Normal file
48
Shared/Settings/ValidationStatusView.swift
Normal file
@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ValidationStatusView: View {
|
||||
@Binding var isValid: Bool
|
||||
@Binding var isValidated: Bool
|
||||
@Binding var isValidating: Bool
|
||||
@Binding var error: String?
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 4) {
|
||||
Image(systemName: validationStatusSystemImage)
|
||||
.foregroundColor(validationStatusColor)
|
||||
|
||||
.frame(minWidth: 35, minHeight: 35)
|
||||
.opacity(isValidating ? 1 : (isValidated ? 1 : 0))
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
Text(isValid ? "Connected successfully" : "Connection failed")
|
||||
if !isValid && !error.isNil {
|
||||
Text(error!)
|
||||
.font(.caption2)
|
||||
.foregroundColor(.secondary)
|
||||
.truncationMode(.tail)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 35)
|
||||
.opacity(isValidating ? 0 : (isValidated ? 1 : 0))
|
||||
}
|
||||
}
|
||||
|
||||
var validationStatusSystemImage: String {
|
||||
if isValidating {
|
||||
return "bolt.horizontal.fill"
|
||||
} else {
|
||||
return isValid ? "checkmark.circle.fill" : "xmark.circle.fill"
|
||||
}
|
||||
}
|
||||
|
||||
var validationStatusColor: Color {
|
||||
if isValidating {
|
||||
return .accentColor
|
||||
} else {
|
||||
return isValid ? .green : .red
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user