add block user from settings

This commit is contained in:
cheykrym 2025-12-13 02:38:46 +03:00
parent 7a7f2eec5e
commit 7dc4882f27
3 changed files with 144 additions and 15 deletions

View File

@ -135,6 +135,15 @@ final class BlockedUsersService {
func add(userId: UUID, completion: @escaping (Result<BlockedUserInfo, Error>) -> Void) {
let request = BlockedUserCreateRequest(userId: userId, login: nil)
add(request: request, completion: completion)
}
func add(login: String, completion: @escaping (Result<BlockedUserInfo, Error>) -> Void) {
let request = BlockedUserCreateRequest(userId: nil, login: login)
add(request: request, completion: completion)
}
private func add(request: BlockedUserCreateRequest, completion: @escaping (Result<BlockedUserInfo, Error>) -> Void) {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
@ -187,6 +196,14 @@ final class BlockedUsersService {
}
}
func add(login: String) async throws -> BlockedUserInfo {
try await withCheckedThrowingContinuation { continuation in
add(login: login) { result in
continuation.resume(with: result)
}
}
}
private static func decodeDate(from decoder: Decoder) throws -> Date {
let container = try decoder.singleValueContainer()
let string = try container.decode(String.self)

View File

@ -335,6 +335,9 @@
"Введите пароль" : {
"comment" : "Пароль\nПоле ввода пароля на приложение"
},
"Введите юзернейм человека, которого нужно заблокировать. Символ @ указывать не нужно." : {
"comment" : "Blocked users add login footer"
},
"Веб" : {
"comment" : "Тип сессии — веб"
},
@ -590,9 +593,6 @@
"Добавить контакт" : {
"comment" : "Message profile add contact alert title"
},
"Добавление новых блокировок появится позже." : {
"comment" : "Add blocked user placeholder message"
},
"Добавьте контакты, чтобы быстрее выходить на связь." : {
"comment" : "Contacts empty state subtitle"
},
@ -648,7 +648,7 @@
},
"Заблокировать" : {
"comment" : "Message profile block title"
"comment" : "Blocked users add confirm\nBlocked users add title\nMessage profile block title"
},
"Заблокировать контакт" : {
"comment" : "Contacts context action block"
@ -1106,6 +1106,9 @@
}
}
},
"Логин пользователя" : {
"comment" : "Blocked users add login header"
},
"Логин уже занят." : {
"localizations" : {
"en" : {
@ -1256,6 +1259,9 @@
"Напишите нам через форму обратной связи в разделе \"Поддержка\"." : {
"comment" : "FAQ answer: support"
},
"Например, username" : {
"comment" : "Blocked users add login placeholder"
},
"Например: заметил неточную информацию в статье..." : {
"comment" : "feedback placeholder: content",
"localizations" : {
@ -2788,7 +2794,7 @@
"comment" : "Кнопка копирования кода восстановления"
},
"Скоро" : {
"comment" : "Add blocked user placeholder title\nContacts placeholder title\nЗаголовок заглушки"
"comment" : "Contacts placeholder title\nЗаголовок заглушки"
},
"Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : {
"comment" : "Message profile search action description"

View File

@ -11,6 +11,11 @@ struct BlockedUsersView: View {
@State private var removingUserIds: Set<UUID> = []
@State private var activeAlert: ActiveAlert?
@State private var errorMessageDown: String?
@State private var isAddUserSheetPresented = false
@State private var newBlockedUserLogin = ""
@State private var addBlockedUserError: String?
@State private var isProcessingAddBlockedUser = false
@FocusState private var isAddBlockedUserFieldFocused: Bool
private let blockedUsersService = BlockedUsersService()
private let limit = 20
@ -32,7 +37,7 @@ struct BlockedUsersView: View {
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
activeAlert = .addPlaceholder
isAddUserSheetPresented = true
} label: {
Image(systemName: "plus")
}
@ -41,14 +46,11 @@ struct BlockedUsersView: View {
.task {
await loadBlockedUsers()
}
.sheet(isPresented: $isAddUserSheetPresented, onDismiss: resetAddBlockedUserForm) {
addBlockedUserSheet
}
.alert(item: $activeAlert) { alert in
switch alert {
case .addPlaceholder:
return Alert(
title: Text(NSLocalizedString("Скоро", comment: "Add blocked user placeholder title")),
message: Text(NSLocalizedString("Добавление новых блокировок появится позже.", comment: "Add blocked user placeholder message")),
dismissButton: .default(Text(NSLocalizedString("OK", comment: "Common OK")))
)
case .error(_, let message):
return Alert(
title: Text(NSLocalizedString("Ошибка", comment: "Common error title")),
@ -78,6 +80,58 @@ struct BlockedUsersView: View {
}
}
private var addBlockedUserSheet: some View {
NavigationView {
Form {
Section(
header: Text(NSLocalizedString("Логин пользователя", comment: "Blocked users add login header")),
footer: Text(NSLocalizedString("Введите юзернейм человека, которого нужно заблокировать. Символ @ указывать не нужно.", comment: "Blocked users add login footer"))
.font(.footnote)
.foregroundColor(.secondary)
) {
TextField(NSLocalizedString("Например, username", comment: "Blocked users add login placeholder"), text: $newBlockedUserLogin)
.textInputAutocapitalization(.never)
.autocorrectionDisabled()
.keyboardType(.asciiCapable)
.focused($isAddBlockedUserFieldFocused)
}
if let addBlockedUserError {
Section {
Text(addBlockedUserError)
.font(.footnote)
.foregroundColor(.red)
.multilineTextAlignment(.leading)
}
}
}
.navigationTitle(NSLocalizedString("Заблокировать", comment: "Blocked users add title"))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button(NSLocalizedString("Отмена", comment: "Common cancel")) {
isAddUserSheetPresented = false
}
}
ToolbarItem(placement: .confirmationAction) {
if isProcessingAddBlockedUser {
ProgressView()
} else {
Button(NSLocalizedString("Заблокировать", comment: "Blocked users add confirm")) {
submitAddBlockedUser()
}
.disabled(!canSubmitNewBlockedUser)
}
}
}
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
isAddBlockedUserFieldFocused = true
}
}
}
}
private var usersSection: some View {
Section(header: Text(NSLocalizedString("Заблокированные", comment: ""))) {
ForEach(Array(blockedUsers.enumerated()), id: \.element.id) { index, user in
@ -99,6 +153,14 @@ struct BlockedUsersView: View {
}
}
private var trimmedNewBlockedUserLogin: String {
newBlockedUserLogin.trimmingCharacters(in: .whitespacesAndNewlines)
}
private var canSubmitNewBlockedUser: Bool {
!trimmedNewBlockedUserLogin.isEmpty && !isProcessingAddBlockedUser
}
private func userRow(_ user: BlockedUser, index: Int) -> some View {
HStack(spacing: 12) {
Circle()
@ -185,6 +247,53 @@ struct BlockedUsersView: View {
}
}
private func submitAddBlockedUser() {
guard canSubmitNewBlockedUser else { return }
let login = trimmedNewBlockedUserLogin
isProcessingAddBlockedUser = true
addBlockedUserError = nil
Task {
await performAddBlockedUser(login: login)
}
}
private func resetAddBlockedUserForm() {
newBlockedUserLogin = ""
addBlockedUserError = nil
isProcessingAddBlockedUser = false
isAddBlockedUserFieldFocused = false
}
private func performAddBlockedUser(login: String) async {
do {
let payload = try await blockedUsersService.add(login: login)
let newUser = BlockedUser(payload: payload)
await MainActor.run {
let existed = blockedUsers.contains(where: { $0.id == newUser.id })
blockedUsers.removeAll { $0.id == newUser.id }
blockedUsers.insert(newUser, at: 0)
if !existed {
offset += 1
}
isAddUserSheetPresented = false
}
} catch {
if AppConfig.DEBUG {
print("[BlockedUsersView] add blocked user failed: \(error)")
}
await MainActor.run {
addBlockedUserError = error.localizedDescription
}
}
await MainActor.run {
isProcessingAddBlockedUser = false
}
}
@MainActor
private func loadBlockedUsers() async {
errorMessageDown = nil
@ -296,13 +405,10 @@ private struct BlockedUser: Identifiable, Equatable {
}
private enum ActiveAlert: Identifiable {
case addPlaceholder
case error(id: UUID = UUID(), message: String)
var id: String {
switch self {
case .addPlaceholder:
return "addPlaceholder"
case .error(let id, _):
return id.uuidString
}