import SwiftUI struct ImportSettingsAccountRow: View { var account: Account var fileModel: ImportSettingsFileModel @State private var password = "" @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() @ObservedObject private var model = ImportSettingsSheetViewModel.shared func afterValidation() { if isValid { model.importableAccounts.insert(account.id) model.selectedAccounts.insert(account.id) model.importableAccountsPasswords[account.id] = password } else { model.selectedAccounts.remove(account.id) model.importableAccounts.remove(account.id) model.importableAccountsPasswords.removeValue(forKey: account.id) } } var body: some View { #if os(tvOS) row #else Button(action: { model.toggleAccount(account, accounts: accounts) }) { row } .buttonStyle(.plain) #endif } var row: some View { let accountExists = AccountsModel.shared.find(account.id) != nil return VStack(alignment: .leading) { HStack { Text(account.username) Spacer() Image(systemName: "checkmark") .foregroundColor(.accentColor) .opacity(isChecked ? 1 : 0) } Text(account.instance?.description ?? "") .font(.caption) .foregroundColor(.secondary) Group { if let instanceID = account.instanceID { if accountExists { HStack { Image(systemName: "xmark.circle.fill") .foregroundColor(Color("AppRedColor")) Text("Account already exists") } } else { Group { if InstancesModel.shared.find(instanceID) != nil || InstancesModel.shared.findByURLString(account.urlString) != nil { HStack { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text("Custom Location already exists") } } else if model.selectedInstances.contains(instanceID) { HStack { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text("Custom Location selected for import") } } else { HStack { Image(systemName: "xmark.circle.fill") .foregroundColor(.red) Text("Custom Location not selected for import") } .foregroundColor(Color("AppRedColor")) } } .frame(minHeight: 20) if account.password.isNil || account.password!.isEmpty { Group { if password.isEmpty { HStack { Image(systemName: "key") Text("Password required to import") } .foregroundColor(Color("AppRedColor")) } else { AccountValidationStatus( app: .constant(instance.app), isValid: $isValid, isValidated: $isValidated, isValidating: $isValidating, error: $validationError ) } } .frame(minHeight: 20) } else { HStack { Image(systemName: "checkmark.circle.fill") .foregroundColor(.green) Text("Password saved in import file") } } } } } .foregroundColor(.primary) .font(.caption) .padding(.vertical, 2) if !accountExists && (account.password.isNil || account.password!.isEmpty) { SecureField("Password", text: $password) .onChange(of: password) { _ in validate() } #if !os(tvOS) .textFieldStyle(RoundedBorderTextFieldStyle()) #endif } } .frame(maxWidth: .infinity, alignment: .leading) .contentShape(Rectangle()) .onChange(of: isValid) { _ in afterValidation() } .animation(nil, value: isChecked) } var isChecked: Bool { model.isSelectedForImport(account) } var locationsSettingsGroupImporter: LocationsSettingsGroupImporter? { fileModel.locationsSettingsGroupImporter } var accounts: [Account] { fileModel.locationsSettingsGroupImporter?.accounts ?? [] } private var instance: Instance! { (fileModel.locationsSettingsGroupImporter?.instances ?? []).first { $0.id == account.instanceID } } private var validator: AccountValidator { AccountValidator( app: .constant(instance.app), url: instance.apiURLString, account: Account(instanceID: instance.id, urlString: instance.apiURLString, username: account.username, password: password), id: .constant(account.username), isValid: $isValid, isValidated: $isValidated, isValidating: $isValidating, error: $validationError ) } private func validate() { isValid = false validationDebounce.invalidate() guard !account.username.isEmpty, !password.isEmpty else { validator.reset() return } isValidating = true validationDebounce.debouncing(1) { validator.validateAccount() } } } struct ImportSettingsAccountRow_Previews: PreviewProvider { static var previews: some View { let fileModel = ImportSettingsFileModel() fileModel.loadData(URL(string: "https://gist.githubusercontent.com/arekf/578668969c9fdef1b3828bea864c3956/raw/f794a95a20261bcb1145e656c8dda00bea339e2a/yattee-recents.yatteesettings")!) return List { ImportSettingsAccountRow( account: .init(name: "arekf", urlString: "https://instance.com", username: "arekf"), fileModel: fileModel ) ImportSettingsAccountRow( account: .init(name: "arekf", urlString: "https://instance.com", username: "arekf", password: "a"), fileModel: fileModel ) } } }