add block user from settings
This commit is contained in:
parent
7a7f2eec5e
commit
7dc4882f27
@ -135,6 +135,15 @@ final class BlockedUsersService {
|
|||||||
|
|
||||||
func add(userId: UUID, completion: @escaping (Result<BlockedUserInfo, Error>) -> Void) {
|
func add(userId: UUID, completion: @escaping (Result<BlockedUserInfo, Error>) -> Void) {
|
||||||
let request = BlockedUserCreateRequest(userId: userId, login: nil)
|
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()
|
let encoder = JSONEncoder()
|
||||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
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 {
|
private static func decodeDate(from decoder: Decoder) throws -> Date {
|
||||||
let container = try decoder.singleValueContainer()
|
let container = try decoder.singleValueContainer()
|
||||||
let string = try container.decode(String.self)
|
let string = try container.decode(String.self)
|
||||||
|
|||||||
@ -335,6 +335,9 @@
|
|||||||
"Введите пароль" : {
|
"Введите пароль" : {
|
||||||
"comment" : "Пароль\nПоле ввода пароля на приложение"
|
"comment" : "Пароль\nПоле ввода пароля на приложение"
|
||||||
},
|
},
|
||||||
|
"Введите юзернейм человека, которого нужно заблокировать. Символ @ указывать не нужно." : {
|
||||||
|
"comment" : "Blocked users add login footer"
|
||||||
|
},
|
||||||
"Веб" : {
|
"Веб" : {
|
||||||
"comment" : "Тип сессии — веб"
|
"comment" : "Тип сессии — веб"
|
||||||
},
|
},
|
||||||
@ -590,9 +593,6 @@
|
|||||||
"Добавить контакт" : {
|
"Добавить контакт" : {
|
||||||
"comment" : "Message profile add contact alert title"
|
"comment" : "Message profile add contact alert title"
|
||||||
},
|
},
|
||||||
"Добавление новых блокировок появится позже." : {
|
|
||||||
"comment" : "Add blocked user placeholder message"
|
|
||||||
},
|
|
||||||
"Добавьте контакты, чтобы быстрее выходить на связь." : {
|
"Добавьте контакты, чтобы быстрее выходить на связь." : {
|
||||||
"comment" : "Contacts empty state subtitle"
|
"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"
|
"comment" : "Contacts context action block"
|
||||||
@ -1106,6 +1106,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Логин пользователя" : {
|
||||||
|
"comment" : "Blocked users add login header"
|
||||||
|
},
|
||||||
"Логин уже занят." : {
|
"Логин уже занят." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -1256,6 +1259,9 @@
|
|||||||
"Напишите нам через форму обратной связи в разделе \"Поддержка\"." : {
|
"Напишите нам через форму обратной связи в разделе \"Поддержка\"." : {
|
||||||
"comment" : "FAQ answer: support"
|
"comment" : "FAQ answer: support"
|
||||||
},
|
},
|
||||||
|
"Например, username" : {
|
||||||
|
"comment" : "Blocked users add login placeholder"
|
||||||
|
},
|
||||||
"Например: заметил неточную информацию в статье..." : {
|
"Например: заметил неточную информацию в статье..." : {
|
||||||
"comment" : "feedback placeholder: content",
|
"comment" : "feedback placeholder: content",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
@ -2788,7 +2794,7 @@
|
|||||||
"comment" : "Кнопка копирования кода восстановления"
|
"comment" : "Кнопка копирования кода восстановления"
|
||||||
},
|
},
|
||||||
"Скоро" : {
|
"Скоро" : {
|
||||||
"comment" : "Add blocked user placeholder title\nContacts placeholder title\nЗаголовок заглушки"
|
"comment" : "Contacts placeholder title\nЗаголовок заглушки"
|
||||||
},
|
},
|
||||||
"Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : {
|
"Скоро можно будет искать сообщения, ссылки и файлы в этом чате." : {
|
||||||
"comment" : "Message profile search action description"
|
"comment" : "Message profile search action description"
|
||||||
|
|||||||
@ -11,6 +11,11 @@ struct BlockedUsersView: View {
|
|||||||
@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?
|
@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 blockedUsersService = BlockedUsersService()
|
||||||
private let limit = 20
|
private let limit = 20
|
||||||
@ -32,7 +37,7 @@ struct BlockedUsersView: View {
|
|||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
ToolbarItem(placement: .navigationBarTrailing) {
|
||||||
Button {
|
Button {
|
||||||
activeAlert = .addPlaceholder
|
isAddUserSheetPresented = true
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus")
|
Image(systemName: "plus")
|
||||||
}
|
}
|
||||||
@ -41,14 +46,11 @@ struct BlockedUsersView: View {
|
|||||||
.task {
|
.task {
|
||||||
await loadBlockedUsers()
|
await loadBlockedUsers()
|
||||||
}
|
}
|
||||||
|
.sheet(isPresented: $isAddUserSheetPresented, onDismiss: resetAddBlockedUserForm) {
|
||||||
|
addBlockedUserSheet
|
||||||
|
}
|
||||||
.alert(item: $activeAlert) { alert in
|
.alert(item: $activeAlert) { alert in
|
||||||
switch alert {
|
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):
|
case .error(_, let message):
|
||||||
return Alert(
|
return Alert(
|
||||||
title: Text(NSLocalizedString("Ошибка", comment: "Common error title")),
|
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 {
|
private var usersSection: some View {
|
||||||
Section(header: Text(NSLocalizedString("Заблокированные", comment: ""))) {
|
Section(header: Text(NSLocalizedString("Заблокированные", comment: ""))) {
|
||||||
ForEach(Array(blockedUsers.enumerated()), id: \.element.id) { index, user in
|
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 {
|
private func userRow(_ user: BlockedUser, index: Int) -> some View {
|
||||||
HStack(spacing: 12) {
|
HStack(spacing: 12) {
|
||||||
Circle()
|
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
|
@MainActor
|
||||||
private func loadBlockedUsers() async {
|
private func loadBlockedUsers() async {
|
||||||
errorMessageDown = nil
|
errorMessageDown = nil
|
||||||
@ -296,13 +405,10 @@ private struct BlockedUser: Identifiable, Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private enum ActiveAlert: Identifiable {
|
private enum ActiveAlert: Identifiable {
|
||||||
case addPlaceholder
|
|
||||||
case error(id: UUID = UUID(), message: String)
|
case error(id: UUID = UUID(), message: String)
|
||||||
|
|
||||||
var id: String {
|
var id: String {
|
||||||
switch self {
|
switch self {
|
||||||
case .addPlaceholder:
|
|
||||||
return "addPlaceholder"
|
|
||||||
case .error(let id, _):
|
case .error(let id, _):
|
||||||
return id.uuidString
|
return id.uuidString
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user