add new model to blacklist
This commit is contained in:
		
							parent
							
								
									052ff5fe4f
								
							
						
					
					
						commit
						3f0543aa3a
					
				@ -29,3 +29,16 @@ struct ErrorResponse: Decodable {
 | 
			
		||||
struct MessagePayload: Decodable {
 | 
			
		||||
    let message: String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct BlockedUserInfo: Decodable {
 | 
			
		||||
    let userId: UUID
 | 
			
		||||
    let login: String
 | 
			
		||||
    let fullName: String?
 | 
			
		||||
    let customName: String?
 | 
			
		||||
    let createdAt: Date
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct BlockedUsersPayload: Decodable {
 | 
			
		||||
    let hasMore: Bool
 | 
			
		||||
    let items: [BlockedUserInfo]
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -19,14 +19,6 @@ enum BlockedUsersServiceError: LocalizedError {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct BlockedUserPayload: Decodable {
 | 
			
		||||
    let userId: UUID
 | 
			
		||||
    let login: String
 | 
			
		||||
    let fullName: String?
 | 
			
		||||
    let customName: String?
 | 
			
		||||
    let createdAt: Date
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
final class BlockedUsersService {
 | 
			
		||||
    private let client: NetworkClient
 | 
			
		||||
    private let decoder: JSONDecoder
 | 
			
		||||
@ -38,16 +30,22 @@ final class BlockedUsersService {
 | 
			
		||||
        self.decoder.dateDecodingStrategy = .custom(Self.decodeDate)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func fetchBlockedUsers(completion: @escaping (Result<[BlockedUserPayload], Error>) -> Void) {
 | 
			
		||||
    func fetchBlockedUsers(limit: Int, offset: Int, completion: @escaping (Result<BlockedUsersPayload, Error>) -> Void) {
 | 
			
		||||
        let query = [
 | 
			
		||||
            "limit": String(limit),
 | 
			
		||||
            "offset": String(offset)
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        client.request(
 | 
			
		||||
            path: "/v1/user/blacklist/list",
 | 
			
		||||
            method: .get,
 | 
			
		||||
            query: query,
 | 
			
		||||
            requiresAuth: true
 | 
			
		||||
        ) { [decoder] result in
 | 
			
		||||
            switch result {
 | 
			
		||||
            case .success(let response):
 | 
			
		||||
                do {
 | 
			
		||||
                    let apiResponse = try decoder.decode(APIResponse<[BlockedUserPayload]>.self, from: response.data)
 | 
			
		||||
                    let apiResponse = try decoder.decode(APIResponse<BlockedUsersPayload>.self, from: response.data)
 | 
			
		||||
                    guard apiResponse.status == "fine" else {
 | 
			
		||||
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось загрузить список.", comment: "Blocked users service unexpected status")
 | 
			
		||||
                        completion(.failure(BlockedUsersServiceError.unexpectedStatus(message)))
 | 
			
		||||
@ -73,9 +71,9 @@ final class BlockedUsersService {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func fetchBlockedUsers() async throws -> [BlockedUserPayload] {
 | 
			
		||||
    func fetchBlockedUsers(limit: Int, offset: Int) async throws -> BlockedUsersPayload {
 | 
			
		||||
        try await withCheckedThrowingContinuation { continuation in
 | 
			
		||||
            fetchBlockedUsers { result in
 | 
			
		||||
            fetchBlockedUsers(limit: limit, offset: offset) { result in
 | 
			
		||||
                continuation.resume(with: result)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -118,6 +118,9 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Home" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Login must not end with 'bot' for non-bot accounts" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "OK" : {
 | 
			
		||||
      "comment" : "Common OK\nProfile update alert button\nОбщий текст кнопки OK",
 | 
			
		||||
@ -509,9 +512,6 @@
 | 
			
		||||
    },
 | 
			
		||||
    "Заблокировать контакт" : {
 | 
			
		||||
      "comment" : "Contacts context action block"
 | 
			
		||||
    },
 | 
			
		||||
    "Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия" : {
 | 
			
		||||
 | 
			
		||||
    },
 | 
			
		||||
    "Завершить" : {
 | 
			
		||||
      "comment" : "Кнопка завершения конкретной сессии\nПодтверждение завершения других сессий\nПодтверждение завершения конкретной сессии"
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,8 @@ import SwiftUI
 | 
			
		||||
struct BlockedUsersView: View {
 | 
			
		||||
    @State private var blockedUsers: [BlockedUser] = []
 | 
			
		||||
    @State private var isLoading = false
 | 
			
		||||
    @State private var hasMore = true
 | 
			
		||||
    @State private var offset = 0
 | 
			
		||||
    @State private var loadError: String?
 | 
			
		||||
    @State private var pendingUnblock: BlockedUser?
 | 
			
		||||
    @State private var showUnblockConfirmation = false
 | 
			
		||||
@ -10,49 +12,20 @@ struct BlockedUsersView: View {
 | 
			
		||||
    @State private var activeAlert: ActiveAlert?
 | 
			
		||||
 | 
			
		||||
    private let blockedUsersService = BlockedUsersService()
 | 
			
		||||
    private let limit = 20
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        List {
 | 
			
		||||
            if isLoading && blockedUsers.isEmpty {
 | 
			
		||||
                loadingState
 | 
			
		||||
                initialLoadingState
 | 
			
		||||
            } 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")
 | 
			
		||||
                            }
 | 
			
		||||
                            .disabled(removingUserIds.contains(user.id))
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                usersSection
 | 
			
		||||
                if hasMore {
 | 
			
		||||
                    loadMoreState
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -70,9 +43,6 @@ struct BlockedUsersView: View {
 | 
			
		||||
        .task {
 | 
			
		||||
            await loadBlockedUsers()
 | 
			
		||||
        }
 | 
			
		||||
//        .refreshable {
 | 
			
		||||
//            await loadBlockedUsers()
 | 
			
		||||
//        }
 | 
			
		||||
        .alert(item: $activeAlert) { alert in
 | 
			
		||||
            switch alert {
 | 
			
		||||
            case .addPlaceholder:
 | 
			
		||||
@ -110,6 +80,43 @@ struct BlockedUsersView: View {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var usersSection: some View {
 | 
			
		||||
        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, 0)
 | 
			
		||||
                .swipeActions(edge: .trailing) {
 | 
			
		||||
                    Button(role: .destructive) {
 | 
			
		||||
                        pendingUnblock = user
 | 
			
		||||
                        showUnblockConfirmation = true
 | 
			
		||||
                    } label: {
 | 
			
		||||
                        Label(NSLocalizedString("Разблокировать", comment: ""), systemImage: "person.crop.circle.badge.xmark")
 | 
			
		||||
                    }
 | 
			
		||||
                    .disabled(removingUserIds.contains(user.id))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var emptyState: some View {
 | 
			
		||||
        VStack(spacing: 12) {
 | 
			
		||||
            Image(systemName: "hand.raised")
 | 
			
		||||
@ -118,10 +125,6 @@ struct BlockedUsersView: View {
 | 
			
		||||
            Text(NSLocalizedString("У вас нет заблокированных пользователей", comment: ""))
 | 
			
		||||
                .font(.headline)
 | 
			
		||||
                .multilineTextAlignment(.center)
 | 
			
		||||
//            Text(NSLocalizedString("Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия", comment: ""))
 | 
			
		||||
//                .font(.subheadline)
 | 
			
		||||
//                .foregroundColor(.secondary)
 | 
			
		||||
//                .multilineTextAlignment(.center)
 | 
			
		||||
        }
 | 
			
		||||
        .frame(maxWidth: .infinity, alignment: .center)
 | 
			
		||||
        .padding(.vertical, 32)
 | 
			
		||||
@ -129,13 +132,25 @@ struct BlockedUsersView: View {
 | 
			
		||||
        .listRowSeparator(.hidden)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var loadingState: some View {
 | 
			
		||||
    private var initialLoadingState: some View {
 | 
			
		||||
        Section {
 | 
			
		||||
            ProgressView()
 | 
			
		||||
                .frame(maxWidth: .infinity, alignment: .center)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private var loadMoreState: some View {
 | 
			
		||||
        Section {
 | 
			
		||||
            ProgressView()
 | 
			
		||||
                .frame(maxWidth: .infinity, alignment: .center)
 | 
			
		||||
                .onAppear {
 | 
			
		||||
                    Task {
 | 
			
		||||
                        await loadBlockedUsers()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func errorState(_ message: String) -> some View {
 | 
			
		||||
        Section {
 | 
			
		||||
            Text(message)
 | 
			
		||||
@ -146,19 +161,26 @@ struct BlockedUsersView: View {
 | 
			
		||||
 | 
			
		||||
    @MainActor
 | 
			
		||||
    private func loadBlockedUsers() async {
 | 
			
		||||
        if isLoading {
 | 
			
		||||
        guard !isLoading, hasMore else {
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        isLoading = true
 | 
			
		||||
        if offset == 0 {
 | 
			
		||||
            loadError = nil
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        do {
 | 
			
		||||
            let payloads = try await blockedUsersService.fetchBlockedUsers()
 | 
			
		||||
            blockedUsers = payloads.map(BlockedUser.init)
 | 
			
		||||
            let payload = try await blockedUsersService.fetchBlockedUsers(limit: limit, offset: offset)
 | 
			
		||||
            blockedUsers.append(contentsOf: payload.items.map(BlockedUser.init))
 | 
			
		||||
            offset += payload.items.count
 | 
			
		||||
            hasMore = payload.hasMore
 | 
			
		||||
        } catch {
 | 
			
		||||
            loadError = error.localizedDescription
 | 
			
		||||
            activeAlert = .error(message: error.localizedDescription)
 | 
			
		||||
            let message = error.localizedDescription
 | 
			
		||||
            if offset == 0 {
 | 
			
		||||
                loadError = message
 | 
			
		||||
            }
 | 
			
		||||
            activeAlert = .error(message: message)
 | 
			
		||||
            if AppConfig.DEBUG { print("[BlockedUsersView] load blocked users failed: \(error)") }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -211,7 +233,7 @@ private struct BlockedUser: Identifiable, Equatable {
 | 
			
		||||
        return "??"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init(payload: BlockedUserPayload) {
 | 
			
		||||
    init(payload: BlockedUserInfo) {
 | 
			
		||||
        self.id = payload.userId
 | 
			
		||||
        self.login = payload.login
 | 
			
		||||
        self.fullName = payload.fullName
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user