ios_app_v2/yobble/Views/Tab/Settings/BlockedUsersView.swift

209 lines
7.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}
}
}