yattee/Shared/Settings/Import/ImportSettingsAccountRow.swift

199 lines
7.5 KiB
Swift

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
)
}
}
}