154 lines
5.5 KiB
Swift
154 lines
5.5 KiB
Swift
|
|
import SwiftUI
|
||
|
|
import VoiceCatCore
|
||
|
|
|
||
|
|
struct AccountsView: View {
|
||
|
|
@Bindable var session: SessionState
|
||
|
|
@State private var showCreateAccount = false
|
||
|
|
@State private var newUsername = ""
|
||
|
|
@State private var newPassword = ""
|
||
|
|
@State private var accountToDelete: Account?
|
||
|
|
@State private var showResetPassword = false
|
||
|
|
@State private var resetForAccount: Account?
|
||
|
|
@State private var resetPassword = ""
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
List {
|
||
|
|
ForEach(session.accounts, id: \.username) { account in
|
||
|
|
AccountRowView(account: account)
|
||
|
|
.swipeActions(edge: .trailing) {
|
||
|
|
Button(role: .destructive) {
|
||
|
|
accountToDelete = account
|
||
|
|
} label: {
|
||
|
|
Label("Delete", systemImage: "trash")
|
||
|
|
}
|
||
|
|
Button {
|
||
|
|
resetForAccount = account
|
||
|
|
showResetPassword = true
|
||
|
|
} label: {
|
||
|
|
Label("Reset PW", systemImage: "key")
|
||
|
|
}
|
||
|
|
.tint(.orange)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.navigationTitle("Accounts")
|
||
|
|
.navigationBarTitleDisplayMode(.inline)
|
||
|
|
.toolbar {
|
||
|
|
ToolbarItem(placement: .primaryAction) {
|
||
|
|
Button {
|
||
|
|
showCreateAccount = true
|
||
|
|
} label: {
|
||
|
|
Image(systemName: "plus")
|
||
|
|
}
|
||
|
|
.accessibilityLabel("Create account")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.refreshable {
|
||
|
|
session.fetchAccountList()
|
||
|
|
}
|
||
|
|
.onAppear {
|
||
|
|
session.fetchAccountList()
|
||
|
|
}
|
||
|
|
.confirmationDialog("Delete account?", isPresented: Binding(
|
||
|
|
get: { accountToDelete != nil },
|
||
|
|
set: { if !$0 { accountToDelete = nil } }
|
||
|
|
)) {
|
||
|
|
if let a = accountToDelete {
|
||
|
|
Button("Delete \(a.username)", role: .destructive) {
|
||
|
|
session.deleteAccount(username: a.username)
|
||
|
|
accountToDelete = nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Button("Cancel", role: .cancel) { accountToDelete = nil }
|
||
|
|
}
|
||
|
|
.sheet(isPresented: $showCreateAccount) {
|
||
|
|
CreateAccountSheet(session: session)
|
||
|
|
}
|
||
|
|
.alert("Reset Password", isPresented: $showResetPassword) {
|
||
|
|
SecureField("New password", text: $resetPassword)
|
||
|
|
.accessibilityLabel("New password for account")
|
||
|
|
Button("Reset") {
|
||
|
|
if let a = resetForAccount, !resetPassword.isEmpty {
|
||
|
|
session.resetPassword(username: a.username, newPassword: resetPassword)
|
||
|
|
}
|
||
|
|
resetPassword = ""
|
||
|
|
resetForAccount = nil
|
||
|
|
}
|
||
|
|
Button("Cancel", role: .cancel) {
|
||
|
|
resetPassword = ""
|
||
|
|
resetForAccount = nil
|
||
|
|
}
|
||
|
|
} message: {
|
||
|
|
Text("Enter a new password for \(resetForAccount?.username ?? "").")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private struct AccountRowView: View {
|
||
|
|
let account: Account
|
||
|
|
|
||
|
|
private var joinedDate: String {
|
||
|
|
let date = Date(timeIntervalSince1970: Double(account.createdAtUnixMs) / 1000)
|
||
|
|
return date.formatted(.dateTime.year().month().day())
|
||
|
|
}
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
VStack(alignment: .leading, spacing: 2) {
|
||
|
|
HStack {
|
||
|
|
Text(account.username)
|
||
|
|
.fontWeight(.medium)
|
||
|
|
if account.isAdmin {
|
||
|
|
Text("admin")
|
||
|
|
.font(.caption2)
|
||
|
|
.padding(.horizontal, 6).padding(.vertical, 2)
|
||
|
|
.background(.orange.opacity(0.2), in: Capsule())
|
||
|
|
.foregroundStyle(.orange)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Text("Created \(joinedDate)")
|
||
|
|
.font(.caption)
|
||
|
|
.foregroundStyle(.secondary)
|
||
|
|
}
|
||
|
|
.accessibilityElement(children: .combine)
|
||
|
|
.accessibilityLabel("\(account.username)\(account.isAdmin ? ", administrator" : ""), created \(joinedDate)")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private struct CreateAccountSheet: View {
|
||
|
|
@Bindable var session: SessionState
|
||
|
|
@Environment(\.dismiss) private var dismiss
|
||
|
|
@State private var username = ""
|
||
|
|
@State private var password = ""
|
||
|
|
|
||
|
|
var body: some View {
|
||
|
|
NavigationStack {
|
||
|
|
Form {
|
||
|
|
Section {
|
||
|
|
TextField("Username", text: $username)
|
||
|
|
.textContentType(.username)
|
||
|
|
.autocorrectionDisabled()
|
||
|
|
.textInputAutocapitalization(.never)
|
||
|
|
.accessibilityLabel("Username")
|
||
|
|
SecureField("Password", text: $password)
|
||
|
|
.textContentType(.newPassword)
|
||
|
|
.accessibilityLabel("Password")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
.navigationTitle("Create Account")
|
||
|
|
.navigationBarTitleDisplayMode(.inline)
|
||
|
|
.toolbar {
|
||
|
|
ToolbarItem(placement: .cancellationAction) {
|
||
|
|
Button("Cancel") { dismiss() }
|
||
|
|
}
|
||
|
|
ToolbarItem(placement: .confirmationAction) {
|
||
|
|
Button("Create") {
|
||
|
|
session.createAccount(username: username, password: password)
|
||
|
|
dismiss()
|
||
|
|
}
|
||
|
|
.disabled(username.isEmpty || password.isEmpty)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|