209 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
import SwiftUI
 | 
						||
 | 
						||
struct BlockedUsersView: View {
 | 
						||
    @State private var blockedUsers: [BlockedUser] = []
 | 
						||
    @State private var isLoading = false
 | 
						||
    @State private var loadError: String?
 | 
						||
    @State private var showAddBlockedUserAlert = false
 | 
						||
    @State private var pendingUnblock: BlockedUser?
 | 
						||
    @State private var showUnblockConfirmation = false
 | 
						||
 | 
						||
    private let blockedUsersService = BlockedUsersService()
 | 
						||
 | 
						||
    var body: some View {
 | 
						||
        List {
 | 
						||
            if isLoading && blockedUsers.isEmpty {
 | 
						||
                loadingState
 | 
						||
            } else if let loadError, blockedUsers.isEmpty {
 | 
						||
                errorState(loadError)
 | 
						||
            } else if blockedUsers.isEmpty {
 | 
						||
                emptyState
 | 
						||
            } else {
 | 
						||
                Section(header: Text(NSLocalizedString("Заблокированные", comment: ""))) {
 | 
						||
                    ForEach(blockedUsers) { user in
 | 
						||
                        HStack(spacing: 12) {
 | 
						||
                            Circle()
 | 
						||
                                .fill(Color.accentColor.opacity(0.15))
 | 
						||
                                .frame(width: 44, height: 44)
 | 
						||
                                .overlay(
 | 
						||
                                    Text(user.initials)
 | 
						||
                                        .font(.headline)
 | 
						||
                                        .foregroundColor(.accentColor)
 | 
						||
                                )
 | 
						||
                            VStack(alignment: .leading, spacing: 4) {
 | 
						||
                                Text(user.displayName)
 | 
						||
                                    .font(.body)
 | 
						||
                                if let handle = user.handle {
 | 
						||
                                    Text(handle)
 | 
						||
                                        .font(.caption)
 | 
						||
                                        .foregroundColor(.secondary)
 | 
						||
                                }
 | 
						||
                            }
 | 
						||
                            Spacer()
 | 
						||
                        }
 | 
						||
                        .padding(.vertical, 4)
 | 
						||
                        .swipeActions(edge: .trailing) {
 | 
						||
                            Button(role: .destructive) {
 | 
						||
                                pendingUnblock = user
 | 
						||
                                showUnblockConfirmation = true
 | 
						||
                            } label: {
 | 
						||
                                Label(NSLocalizedString("Разблокировать", comment: ""), systemImage: "person.crop.circle.badge.xmark")
 | 
						||
                            }
 | 
						||
                        }
 | 
						||
                    }
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        .navigationTitle(NSLocalizedString("Заблокированные", comment: ""))
 | 
						||
        .navigationBarTitleDisplayMode(.inline)
 | 
						||
        .toolbar {
 | 
						||
            ToolbarItem(placement: .navigationBarTrailing) {
 | 
						||
                Button {
 | 
						||
                    showAddBlockedUserAlert = true
 | 
						||
                } label: {
 | 
						||
                    Image(systemName: "plus")
 | 
						||
                }
 | 
						||
            }
 | 
						||
        }
 | 
						||
        .task {
 | 
						||
            await loadBlockedUsers()
 | 
						||
        }
 | 
						||
        .refreshable {
 | 
						||
            await loadBlockedUsers()
 | 
						||
        }
 | 
						||
        .alert(NSLocalizedString("Скоро", comment: "Add blocked user placeholder title"), isPresented: $showAddBlockedUserAlert) {
 | 
						||
            Button(NSLocalizedString("OK", comment: "Common OK"), role: .cancel) {}
 | 
						||
        } message: {
 | 
						||
            Text(NSLocalizedString("Добавление новых блокировок появится позже.", comment: "Add blocked user placeholder message"))
 | 
						||
        }
 | 
						||
        .confirmationDialog(
 | 
						||
            NSLocalizedString("Удалить из заблокированных?", comment: "Unblock confirmation title"),
 | 
						||
            isPresented: $showUnblockConfirmation,
 | 
						||
            presenting: pendingUnblock
 | 
						||
        ) { user in
 | 
						||
            Button(NSLocalizedString("Разблокировать", comment: "Unblock confirmation action"), role: .destructive) {
 | 
						||
                unblock(user)
 | 
						||
                pendingUnblock = nil
 | 
						||
            }
 | 
						||
            Button(NSLocalizedString("Отмена", comment: "Common cancel"), role: .cancel) {
 | 
						||
                pendingUnblock = nil
 | 
						||
            }
 | 
						||
        } message: { user in
 | 
						||
            Text(String(format: NSLocalizedString("Пользователь \"%1$@\" будет удалён из списка заблокированных.", comment: "Unblock confirmation message"), user.displayName))
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    private var emptyState: some View {
 | 
						||
        VStack(spacing: 12) {
 | 
						||
            Image(systemName: "hand.raised")
 | 
						||
                .font(.system(size: 48))
 | 
						||
                .foregroundColor(.secondary)
 | 
						||
            Text(NSLocalizedString("У вас нет заблокированных пользователей", comment: ""))
 | 
						||
                .font(.headline)
 | 
						||
                .multilineTextAlignment(.center)
 | 
						||
//            Text(NSLocalizedString("Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия", comment: ""))
 | 
						||
//                .font(.subheadline)
 | 
						||
//                .foregroundColor(.secondary)
 | 
						||
//                .multilineTextAlignment(.center)
 | 
						||
        }
 | 
						||
        .frame(maxWidth: .infinity, alignment: .center)
 | 
						||
        .padding(.vertical, 32)
 | 
						||
        .listRowInsets(EdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 16))
 | 
						||
        .listRowSeparator(.hidden)
 | 
						||
    }
 | 
						||
 | 
						||
    private var loadingState: some View {
 | 
						||
        Section {
 | 
						||
            ProgressView()
 | 
						||
                .frame(maxWidth: .infinity, alignment: .center)
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    private func errorState(_ message: String) -> some View {
 | 
						||
        Section {
 | 
						||
            Text(message)
 | 
						||
                .foregroundColor(.red)
 | 
						||
                .frame(maxWidth: .infinity, alignment: .center)
 | 
						||
        }
 | 
						||
    }
 | 
						||
 | 
						||
    @MainActor
 | 
						||
    private func loadBlockedUsers() async {
 | 
						||
        if isLoading {
 | 
						||
            return
 | 
						||
        }
 | 
						||
 | 
						||
        isLoading = true
 | 
						||
        loadError = nil
 | 
						||
 | 
						||
        do {
 | 
						||
            let payloads = try await blockedUsersService.fetchBlockedUsers()
 | 
						||
            blockedUsers = payloads.map(BlockedUser.init)
 | 
						||
        } catch {
 | 
						||
            loadError = error.localizedDescription
 | 
						||
            if AppConfig.DEBUG {
 | 
						||
                print("[BlockedUsersView] load blocked users failed: \(error)")
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        isLoading = false
 | 
						||
    }
 | 
						||
 | 
						||
    private func unblock(_ user: BlockedUser) {
 | 
						||
        // TODO: implement unblock logic when backend is ready
 | 
						||
        blockedUsers.removeAll { $0.id == user.id }
 | 
						||
    }
 | 
						||
}
 | 
						||
 | 
						||
private struct BlockedUser: Identifiable, Equatable {
 | 
						||
    let id: UUID
 | 
						||
    let login: String
 | 
						||
    let fullName: String?
 | 
						||
    let customName: String?
 | 
						||
    let createdAt: Date
 | 
						||
 | 
						||
    private(set) var displayName: String
 | 
						||
    private(set) var handle: String?
 | 
						||
 | 
						||
    var initials: String {
 | 
						||
        let components = displayName.split(separator: " ")
 | 
						||
        let nameInitials = components.prefix(2).compactMap { $0.first }
 | 
						||
        if !nameInitials.isEmpty {
 | 
						||
            return nameInitials
 | 
						||
                .map { String($0).uppercased() }
 | 
						||
                .joined()
 | 
						||
        }
 | 
						||
 | 
						||
        if let handle {
 | 
						||
            let filtered = handle.filter { $0.isLetter }.prefix(2)
 | 
						||
            if !filtered.isEmpty {
 | 
						||
                return filtered.uppercased()
 | 
						||
            }
 | 
						||
        }
 | 
						||
 | 
						||
        return "??"
 | 
						||
    }
 | 
						||
 | 
						||
    init(payload: BlockedUserPayload) {
 | 
						||
        self.id = payload.userId
 | 
						||
        self.login = payload.login
 | 
						||
        self.fullName = payload.fullName
 | 
						||
        self.customName = payload.customName
 | 
						||
        self.createdAt = payload.createdAt
 | 
						||
 | 
						||
        if let customName = payload.customName, !customName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
 | 
						||
            self.displayName = customName
 | 
						||
        } else if let fullName = payload.fullName, !fullName.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
 | 
						||
            self.displayName = fullName
 | 
						||
        } else {
 | 
						||
            self.displayName = payload.login
 | 
						||
        }
 | 
						||
 | 
						||
        if !payload.login.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
 | 
						||
            self.handle = "@\(payload.login)"
 | 
						||
        } else {
 | 
						||
            self.handle = nil
 | 
						||
        }
 | 
						||
    }
 | 
						||
}
 |