1
0
Fork 0
pdsman-ios/PDSMan/Views/UserListView.swift

260 lines
No EOL
11 KiB
Swift

import SwiftUI
struct UserListView: View {
@EnvironmentObject var viewModel: PDSViewModel
@State private var editingUser: PDSUser? = nil
@State private var newHandle: String = ""
@State private var showingEditSheet = false
@State private var showingSuspendAlert = false
@State private var showingReactivateAlert = false
@State private var suspensionReason: String = ""
@State private var selectedUser: PDSUser? = nil
@State private var isRefreshing = false
var body: some View {
NavigationView {
Group {
if viewModel.isLoading && viewModel.users.isEmpty {
ProgressView("Loading users...")
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else if viewModel.users.isEmpty {
VStack {
Text("No users found")
.foregroundColor(.secondary)
Button("Refresh") {
Task {
await viewModel.refreshUsers()
}
}
.padding()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
} else {
List {
ForEach(viewModel.users) { user in
UserRow(user: user)
.contentShape(Rectangle())
.onTapGesture {
selectedUser = user
editingUser = user
newHandle = user.handle
showingEditSheet = true
}
.contextMenu {
Button(action: {
selectedUser = user
editingUser = user
newHandle = user.handle
showingEditSheet = true
}) {
Label("Edit Handle", systemImage: "pencil")
}
if user.isActive {
Button(action: {
selectedUser = user
suspensionReason = ""
showingSuspendAlert = true
}) {
Label("Suspend Account", systemImage: "xmark.circle")
}
} else {
Button(action: {
selectedUser = user
showingReactivateAlert = true
}) {
Label("Reactivate Account", systemImage: "checkmark.circle")
}
}
}
}
}
}
}
.navigationTitle("Users")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
if viewModel.demoMode {
Text("DEMO MODE")
.font(.caption)
.padding(5)
.background(Color.green.opacity(0.2))
.foregroundColor(.green)
.cornerRadius(4)
}
}
}
.refreshable {
print("⏳ Pull-to-refresh: Fetching users")
await viewModel.refreshUsers()
print("✅ Pull-to-refresh completed")
}
.sheet(isPresented: $showingEditSheet) {
if let user = editingUser {
NavigationView {
Form {
Section(header: Text("Edit User Handle")) {
TextField("Handle", text: $newHandle)
HStack {
Button("Cancel") {
showingEditSheet = false
}
Spacer()
Button("Save") {
Task {
if await viewModel.editUserHandle(userId: user.id, newHandle: newHandle) {
showingEditSheet = false
}
}
}
.disabled(newHandle.isEmpty || newHandle == user.handle)
}
}
}
.navigationTitle("Edit \(user.displayName)")
}
}
}
.alert("Suspend User", isPresented: $showingSuspendAlert) {
TextField("Reason for suspension", text: $suspensionReason)
Button("Cancel", role: .cancel) { }
Button("Suspend", role: .destructive) {
if let user = selectedUser {
print("Attempting to suspend user: \(user.id)")
Task {
let success = await viewModel.suspendUser(userId: user.id)
print("Suspend user result: \(success)")
// Force UI update
await MainActor.run {
viewModel.refreshUI()
}
}
}
}
} message: {
if let user = selectedUser {
Text("Are you sure you want to suspend \(user.displayName)'s account? This will prevent them from using the service.")
}
}
.alert("Reactivate User", isPresented: $showingReactivateAlert) {
Button("Cancel", role: .cancel) { }
Button("Reactivate") {
if let user = selectedUser {
print("Attempting to reactivate user: \(user.id)")
Task {
let success = await viewModel.reactivateUser(userId: user.id)
print("Reactivate user result: \(success)")
// Force UI update
await MainActor.run {
viewModel.refreshUI()
}
}
}
}
} message: {
if let user = selectedUser {
Text("Are you sure you want to reactivate \(user.displayName)'s account?")
}
}
}
.task {
print("⏳ Task: Fetching users")
// Always fetch on initial load
await viewModel.refreshUsers()
print("✅ Task fetch completed")
}
}
}
struct UserRow: View {
@ObservedObject var user: PDSUser
var formattedDate: String {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .none
return "Joined: \(formatter.string(from: user.joinedAt))"
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack(spacing: 12) {
if let avatarURL = user.avatar {
AsyncImage(url: avatarURL) { image in
image
.resizable()
.aspectRatio(contentMode: .fill)
} placeholder: {
ProgressView()
}
.frame(width: 60, height: 60)
.clipShape(Circle())
.overlay(
Circle()
.stroke(Color.gray.opacity(0.2), lineWidth: 1)
)
} else {
Image(systemName: "person.circle.fill")
.resizable()
.foregroundColor(.gray.opacity(0.7))
.frame(width: 60, height: 60)
}
VStack(alignment: .leading, spacing: 3) {
HStack {
Text(user.displayName)
.font(.title3)
.fontWeight(.semibold)
.lineLimit(1)
if !user.isActive {
Text("SUSPENDED")
.font(.caption)
.padding(.horizontal, 5)
.padding(.vertical, 2)
.background(Color.red)
.foregroundColor(.white)
.cornerRadius(4)
}
}
Text("@\(user.handle)")
.font(.subheadline)
.foregroundColor(.blue)
.lineLimit(1)
}
}
if !user.description.isEmpty {
Text(user.description)
.font(.subheadline)
.foregroundColor(.primary)
.lineLimit(3)
.padding(.top, 2)
}
HStack(spacing: 12) {
Text(formattedDate)
.font(.caption)
.foregroundColor(.secondary)
Spacer()
Text(user.id)
.font(.caption)
.foregroundColor(.secondary)
.lineLimit(1)
.truncationMode(.middle)
.frame(maxWidth: 160)
.opacity(0.7)
}
}
.padding(.vertical, 10)
.opacity(user.isActive ? 1.0 : 0.7)
}
}