Compare commits
	
		
			3 Commits
		
	
	
		
			052ff5fe4f
			...
			9f6beecb49
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9f6beecb49 | |||
| 3e9d6696b0 | |||
| 3f0543aa3a | 
@ -29,3 +29,16 @@ struct ErrorResponse: Decodable {
 | 
				
			|||||||
struct MessagePayload: Decodable {
 | 
					struct MessagePayload: Decodable {
 | 
				
			||||||
    let message: String
 | 
					    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 {
 | 
					final class BlockedUsersService {
 | 
				
			||||||
    private let client: NetworkClient
 | 
					    private let client: NetworkClient
 | 
				
			||||||
    private let decoder: JSONDecoder
 | 
					    private let decoder: JSONDecoder
 | 
				
			||||||
@ -38,16 +30,22 @@ final class BlockedUsersService {
 | 
				
			|||||||
        self.decoder.dateDecodingStrategy = .custom(Self.decodeDate)
 | 
					        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(
 | 
					        client.request(
 | 
				
			||||||
            path: "/v1/user/blacklist/list",
 | 
					            path: "/v1/user/blacklist/list",
 | 
				
			||||||
            method: .get,
 | 
					            method: .get,
 | 
				
			||||||
 | 
					            query: query,
 | 
				
			||||||
            requiresAuth: true
 | 
					            requiresAuth: true
 | 
				
			||||||
        ) { [decoder] result in
 | 
					        ) { [decoder] result in
 | 
				
			||||||
            switch result {
 | 
					            switch result {
 | 
				
			||||||
            case .success(let response):
 | 
					            case .success(let response):
 | 
				
			||||||
                do {
 | 
					                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 {
 | 
					                    guard apiResponse.status == "fine" else {
 | 
				
			||||||
                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось загрузить список.", comment: "Blocked users service unexpected status")
 | 
					                        let message = apiResponse.detail ?? NSLocalizedString("Не удалось загрузить список.", comment: "Blocked users service unexpected status")
 | 
				
			||||||
                        completion(.failure(BlockedUsersServiceError.unexpectedStatus(message)))
 | 
					                        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
 | 
					        try await withCheckedThrowingContinuation { continuation in
 | 
				
			||||||
            fetchBlockedUsers { result in
 | 
					            fetchBlockedUsers(limit: limit, offset: offset) { result in
 | 
				
			||||||
                continuation.resume(with: result)
 | 
					                continuation.resume(with: result)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -94,6 +94,9 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Email не подтверждён. Подтвердите, чтобы активировать дополнительные проверки." : {
 | 
					    "Email не подтверждён. Подтвердите, чтобы активировать дополнительные проверки." : {
 | 
				
			||||||
      "comment" : "Описание необходимости подтверждения email"
 | 
					      "comment" : "Описание необходимости подтверждения email"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "error" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Fun Fest" : {
 | 
					    "Fun Fest" : {
 | 
				
			||||||
      "comment" : "Fun Fest",
 | 
					      "comment" : "Fun Fest",
 | 
				
			||||||
@ -118,6 +121,9 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Home" : {
 | 
					    "Home" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "Login must not end with 'bot' for non-bot accounts" : {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "OK" : {
 | 
					    "OK" : {
 | 
				
			||||||
      "comment" : "Common OK\nProfile update alert button\nОбщий текст кнопки OK",
 | 
					      "comment" : "Common OK\nProfile update alert button\nОбщий текст кнопки OK",
 | 
				
			||||||
@ -509,9 +515,6 @@
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
    "Заблокировать контакт" : {
 | 
					    "Заблокировать контакт" : {
 | 
				
			||||||
      "comment" : "Contacts context action block"
 | 
					      "comment" : "Contacts context action block"
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия" : {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "Завершить" : {
 | 
					    "Завершить" : {
 | 
				
			||||||
      "comment" : "Кнопка завершения конкретной сессии\nПодтверждение завершения других сессий\nПодтверждение завершения конкретной сессии"
 | 
					      "comment" : "Кнопка завершения конкретной сессии\nПодтверждение завершения других сессий\nПодтверждение завершения конкретной сессии"
 | 
				
			||||||
 | 
				
			|||||||
@ -3,56 +3,35 @@ import SwiftUI
 | 
				
			|||||||
struct BlockedUsersView: View {
 | 
					struct BlockedUsersView: View {
 | 
				
			||||||
    @State private var blockedUsers: [BlockedUser] = []
 | 
					    @State private var blockedUsers: [BlockedUser] = []
 | 
				
			||||||
    @State private var isLoading = false
 | 
					    @State private var isLoading = false
 | 
				
			||||||
 | 
					    @State private var hasMore = true
 | 
				
			||||||
 | 
					    @State private var offset = 0
 | 
				
			||||||
    @State private var loadError: String?
 | 
					    @State private var loadError: String?
 | 
				
			||||||
    @State private var pendingUnblock: BlockedUser?
 | 
					    @State private var pendingUnblock: BlockedUser?
 | 
				
			||||||
    @State private var showUnblockConfirmation = false
 | 
					    @State private var showUnblockConfirmation = false
 | 
				
			||||||
    @State private var removingUserIds: Set<UUID> = []
 | 
					    @State private var removingUserIds: Set<UUID> = []
 | 
				
			||||||
    @State private var activeAlert: ActiveAlert?
 | 
					    @State private var activeAlert: ActiveAlert?
 | 
				
			||||||
 | 
					    @State private var errorMessageDown: String?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private let blockedUsersService = BlockedUsersService()
 | 
					    private let blockedUsersService = BlockedUsersService()
 | 
				
			||||||
 | 
					    private let limit = 20
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var body: some View {
 | 
					    var body: some View {
 | 
				
			||||||
        List {
 | 
					        List {
 | 
				
			||||||
            if isLoading && blockedUsers.isEmpty {
 | 
					            if isLoading && blockedUsers.isEmpty {
 | 
				
			||||||
                loadingState
 | 
					                initialLoadingState
 | 
				
			||||||
            } else if let loadError, blockedUsers.isEmpty {
 | 
					            } else if let loadError, blockedUsers.isEmpty {
 | 
				
			||||||
                errorState(loadError)
 | 
					                errorState(loadError)
 | 
				
			||||||
            } else if blockedUsers.isEmpty {
 | 
					            } else if blockedUsers.isEmpty {
 | 
				
			||||||
                emptyState
 | 
					                emptyState
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                Section(header: Text(NSLocalizedString("Заблокированные", comment: ""))) {
 | 
					                usersSection
 | 
				
			||||||
                    ForEach(blockedUsers) { user in
 | 
					                if isLoading {
 | 
				
			||||||
                        HStack(spacing: 12) {
 | 
					                    Section {
 | 
				
			||||||
                            Circle()
 | 
					                        ProgressView()
 | 
				
			||||||
                                .fill(Color.accentColor.opacity(0.15))
 | 
					                            .frame(maxWidth: .infinity, alignment: .center)
 | 
				
			||||||
                                .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))
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					                } else if errorMessageDown != nil{
 | 
				
			||||||
 | 
					                    Text("error")
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@ -70,9 +49,6 @@ struct BlockedUsersView: View {
 | 
				
			|||||||
        .task {
 | 
					        .task {
 | 
				
			||||||
            await loadBlockedUsers()
 | 
					            await loadBlockedUsers()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
//        .refreshable {
 | 
					 | 
				
			||||||
//            await loadBlockedUsers()
 | 
					 | 
				
			||||||
//        }
 | 
					 | 
				
			||||||
        .alert(item: $activeAlert) { alert in
 | 
					        .alert(item: $activeAlert) { alert in
 | 
				
			||||||
            switch alert {
 | 
					            switch alert {
 | 
				
			||||||
            case .addPlaceholder:
 | 
					            case .addPlaceholder:
 | 
				
			||||||
@ -110,6 +86,51 @@ 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))
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                .onAppear {
 | 
				
			||||||
 | 
					                    if user.id == blockedUsers.last?.id {
 | 
				
			||||||
 | 
					                        Task {
 | 
				
			||||||
 | 
					                            await loadBlockedUsers()
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var emptyState: some View {
 | 
					    private var emptyState: some View {
 | 
				
			||||||
        VStack(spacing: 12) {
 | 
					        VStack(spacing: 12) {
 | 
				
			||||||
            Image(systemName: "hand.raised")
 | 
					            Image(systemName: "hand.raised")
 | 
				
			||||||
@ -118,10 +139,6 @@ struct BlockedUsersView: View {
 | 
				
			|||||||
            Text(NSLocalizedString("У вас нет заблокированных пользователей", comment: ""))
 | 
					            Text(NSLocalizedString("У вас нет заблокированных пользователей", comment: ""))
 | 
				
			||||||
                .font(.headline)
 | 
					                .font(.headline)
 | 
				
			||||||
                .multilineTextAlignment(.center)
 | 
					                .multilineTextAlignment(.center)
 | 
				
			||||||
//            Text(NSLocalizedString("Заблокируйте аккаунт, чтобы скрыть его сообщения и взаимодействия", comment: ""))
 | 
					 | 
				
			||||||
//                .font(.subheadline)
 | 
					 | 
				
			||||||
//                .foregroundColor(.secondary)
 | 
					 | 
				
			||||||
//                .multilineTextAlignment(.center)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        .frame(maxWidth: .infinity, alignment: .center)
 | 
					        .frame(maxWidth: .infinity, alignment: .center)
 | 
				
			||||||
        .padding(.vertical, 32)
 | 
					        .padding(.vertical, 32)
 | 
				
			||||||
@ -129,7 +146,7 @@ struct BlockedUsersView: View {
 | 
				
			|||||||
        .listRowSeparator(.hidden)
 | 
					        .listRowSeparator(.hidden)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var loadingState: some View {
 | 
					    private var initialLoadingState: some View {
 | 
				
			||||||
        Section {
 | 
					        Section {
 | 
				
			||||||
            ProgressView()
 | 
					            ProgressView()
 | 
				
			||||||
                .frame(maxWidth: .infinity, alignment: .center)
 | 
					                .frame(maxWidth: .infinity, alignment: .center)
 | 
				
			||||||
@ -146,23 +163,32 @@ struct BlockedUsersView: View {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @MainActor
 | 
					    @MainActor
 | 
				
			||||||
    private func loadBlockedUsers() async {
 | 
					    private func loadBlockedUsers() async {
 | 
				
			||||||
        if isLoading {
 | 
					        errorMessageDown = nil
 | 
				
			||||||
 | 
					        guard !isLoading, hasMore else {
 | 
				
			||||||
            return
 | 
					            return
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        isLoading = true
 | 
					        isLoading = true
 | 
				
			||||||
        loadError = nil
 | 
					        defer { isLoading = false }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        do {
 | 
					        if offset == 0 {
 | 
				
			||||||
            let payloads = try await blockedUsersService.fetchBlockedUsers()
 | 
					            loadError = nil
 | 
				
			||||||
            blockedUsers = payloads.map(BlockedUser.init)
 | 
					 | 
				
			||||||
        } catch {
 | 
					 | 
				
			||||||
            loadError = error.localizedDescription
 | 
					 | 
				
			||||||
            activeAlert = .error(message: error.localizedDescription)
 | 
					 | 
				
			||||||
            if AppConfig.DEBUG { print("[BlockedUsersView] load blocked users failed: \(error)") }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        isLoading = false
 | 
					        do {
 | 
				
			||||||
 | 
					            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 {
 | 
				
			||||||
 | 
					            let message = error.localizedDescription
 | 
				
			||||||
 | 
					            if offset == 0 {
 | 
				
			||||||
 | 
					                loadError = message
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            activeAlert = .error(message: message)
 | 
				
			||||||
 | 
					            errorMessageDown = message
 | 
				
			||||||
 | 
					            if AppConfig.DEBUG { print("[BlockedUsersView] load blocked users failed: \(error)") }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @MainActor
 | 
					    @MainActor
 | 
				
			||||||
@ -211,7 +237,7 @@ private struct BlockedUser: Identifiable, Equatable {
 | 
				
			|||||||
        return "??"
 | 
					        return "??"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init(payload: BlockedUserPayload) {
 | 
					    init(payload: BlockedUserInfo) {
 | 
				
			||||||
        self.id = payload.userId
 | 
					        self.id = payload.userId
 | 
				
			||||||
        self.login = payload.login
 | 
					        self.login = payload.login
 | 
				
			||||||
        self.fullName = payload.fullName
 | 
					        self.fullName = payload.fullName
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user