add block user
This commit is contained in:
parent
3c32ef6c70
commit
af89cea3fb
@ -74,6 +74,57 @@ final class ProfileService {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchProfile(userId: UUID, completion: @escaping (Result<ChatProfile, Error>) -> Void) {
|
||||
fetchProfile(userId: userId.uuidString, completion: completion)
|
||||
}
|
||||
|
||||
func fetchProfile(userId: String, completion: @escaping (Result<ChatProfile, Error>) -> Void) {
|
||||
let sanitizedId = userId.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
client.request(
|
||||
path: "/v1/profile/\(sanitizedId)",
|
||||
method: .get,
|
||||
requiresAuth: true
|
||||
) { [decoder] result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
do {
|
||||
let profile = try Self.decodeProfileResponse(
|
||||
data: response.data,
|
||||
decoder: decoder,
|
||||
requestedId: sanitizedId
|
||||
)
|
||||
completion(.success(profile))
|
||||
} catch {
|
||||
if AppConfig.DEBUG {
|
||||
print("[ProfileService] decode profile by id failed: \(error)")
|
||||
}
|
||||
completion(.failure(error))
|
||||
}
|
||||
case .failure(let error):
|
||||
if case let NetworkError.server(_, data) = error,
|
||||
let data,
|
||||
let message = Self.errorMessage(from: data) {
|
||||
completion(.failure(ProfileServiceError.unexpectedStatus(message)))
|
||||
return
|
||||
}
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchProfile(userId: UUID) async throws -> ChatProfile {
|
||||
try await fetchProfile(userId: userId.uuidString)
|
||||
}
|
||||
|
||||
func fetchProfile(userId: String) async throws -> ChatProfile {
|
||||
try await withCheckedThrowingContinuation { continuation in
|
||||
fetchProfile(userId: userId, completion: { result in
|
||||
continuation.resume(with: result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func updateProfile(_ payload: ProfileUpdateRequestPayload, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.keyEncodingStrategy = .convertToSnakeCase
|
||||
@ -201,6 +252,59 @@ final class ProfileService {
|
||||
}
|
||||
}
|
||||
|
||||
private static func decodeProfileResponse(
|
||||
data: Data,
|
||||
decoder: JSONDecoder,
|
||||
requestedId: String
|
||||
) throws -> ChatProfile {
|
||||
let defaultErrorMessage = NSLocalizedString("Не удалось загрузить профиль.", comment: "Profile unexpected status")
|
||||
var dictionaryDecodeError: String?
|
||||
|
||||
do {
|
||||
let apiResponse = try decoder.decode(APIResponse<[String: ChatProfile]>.self, from: data)
|
||||
guard apiResponse.status == "fine" else {
|
||||
let message = apiResponse.detail ?? defaultErrorMessage
|
||||
throw ProfileServiceError.unexpectedStatus(message)
|
||||
}
|
||||
|
||||
let normalizedKey = requestedId.lowercased()
|
||||
if let profile = apiResponse.data[requestedId]
|
||||
?? apiResponse.data[normalizedKey]
|
||||
?? apiResponse.data[requestedId.uppercased()]
|
||||
?? apiResponse.data.first?.value {
|
||||
return profile
|
||||
}
|
||||
|
||||
throw ProfileServiceError.unexpectedStatus(
|
||||
NSLocalizedString("Профиль не найден.", comment: "Profile by id missing")
|
||||
)
|
||||
} catch let error as ProfileServiceError {
|
||||
throw error
|
||||
} catch {
|
||||
dictionaryDecodeError = Self.describeDecodingError(error: error, data: data)
|
||||
}
|
||||
|
||||
do {
|
||||
let apiResponse = try decoder.decode(APIResponse<ChatProfile>.self, from: data)
|
||||
guard apiResponse.status == "fine" else {
|
||||
let message = apiResponse.detail ?? defaultErrorMessage
|
||||
throw ProfileServiceError.unexpectedStatus(message)
|
||||
}
|
||||
return apiResponse.data
|
||||
} catch let error as ProfileServiceError {
|
||||
throw error
|
||||
} catch {
|
||||
let singleError = Self.describeDecodingError(error: error, data: data)
|
||||
let combined: String
|
||||
if let dictionaryDecodeError {
|
||||
combined = dictionaryDecodeError + "\nOR\n" + singleError
|
||||
} else {
|
||||
combined = singleError
|
||||
}
|
||||
throw ProfileServiceError.decoding(debugDescription: combined)
|
||||
}
|
||||
}
|
||||
|
||||
private static func decodeDate(from decoder: Decoder) throws -> Date {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let string = try container.decode(String.self)
|
||||
|
||||
@ -1348,6 +1348,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Не удалось обновить профиль" : {
|
||||
"comment" : "Message profile refresh error title"
|
||||
},
|
||||
"Не удалось обработать данные чатов." : {
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
@ -2383,6 +2386,9 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Профиль не найден." : {
|
||||
"comment" : "Profile by id missing"
|
||||
},
|
||||
"Профиль пока не загружен. Попробуйте позже." : {
|
||||
"comment" : "Profile not ready error"
|
||||
},
|
||||
|
||||
@ -11,16 +11,19 @@ struct MessageProfileView: View {
|
||||
let currentUserId: String?
|
||||
private let avatarSize: CGFloat = 96
|
||||
private let blockedUsersService = BlockedUsersService()
|
||||
private let profileService = ProfileService()
|
||||
|
||||
@State private var areNotificationsEnabled: Bool = true
|
||||
@State private var placeholderAlert: PlaceholderAlert?
|
||||
@State private var isBioExpanded: Bool = false
|
||||
@State private var chatProfile: ChatProfile?
|
||||
@State private var isBlockedByCurrentUserState: Bool
|
||||
@State private var isProcessingBlockAction = false
|
||||
|
||||
init(chat: PrivateChatListItem, currentUserId: String?) {
|
||||
self.chat = chat
|
||||
self.currentUserId = currentUserId
|
||||
_chatProfile = State(initialValue: chat.chatData)
|
||||
_isBlockedByCurrentUserState = State(initialValue: chat.chatData?.relationship?.isTargetUserBlockedByCurrentUser ?? false)
|
||||
}
|
||||
|
||||
@ -526,7 +529,7 @@ struct MessageProfileView: View {
|
||||
private func handleBlockToggleTap() {
|
||||
guard !isProcessingBlockAction else { return }
|
||||
|
||||
guard let userIdString = chat.chatData?.userId,
|
||||
guard let userIdString = currentChatProfile?.userId,
|
||||
let userId = UUID(uuidString: userIdString) else {
|
||||
placeholderAlert = PlaceholderAlert(
|
||||
title: NSLocalizedString("Ошибка", comment: "Common error title"),
|
||||
@ -552,20 +555,44 @@ struct MessageProfileView: View {
|
||||
|
||||
await MainActor.run {
|
||||
isBlockedByCurrentUserState = shouldBlock
|
||||
isProcessingBlockAction = false
|
||||
}
|
||||
|
||||
await refreshChatProfile(userId: userId)
|
||||
} catch {
|
||||
if AppConfig.DEBUG {
|
||||
print("[MessageProfileView] block action failed: \(error)")
|
||||
}
|
||||
await MainActor.run {
|
||||
isProcessingBlockAction = false
|
||||
placeholderAlert = PlaceholderAlert(
|
||||
title: NSLocalizedString("Ошибка", comment: "Common error title"),
|
||||
message: error.localizedDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
await MainActor.run {
|
||||
isProcessingBlockAction = false
|
||||
}
|
||||
}
|
||||
|
||||
private func refreshChatProfile(userId: UUID) async {
|
||||
do {
|
||||
let profile = try await profileService.fetchProfile(userId: userId)
|
||||
await MainActor.run {
|
||||
chatProfile = profile
|
||||
isBlockedByCurrentUserState = profile.relationship?.isTargetUserBlockedByCurrentUser ?? isBlockedByCurrentUserState
|
||||
}
|
||||
} catch {
|
||||
if AppConfig.DEBUG {
|
||||
print("[MessageProfileView] refresh profile failed: \(error)")
|
||||
}
|
||||
await MainActor.run {
|
||||
placeholderAlert = PlaceholderAlert(
|
||||
title: NSLocalizedString("Не удалось обновить профиль", comment: "Message profile refresh error title"),
|
||||
message: error.localizedDescription
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var mediaCategories: [MediaCategory] {
|
||||
@ -585,11 +612,11 @@ struct MessageProfileView: View {
|
||||
// MARK: - Derived Data
|
||||
|
||||
private var profileBio: String? {
|
||||
trimmed(chat.chatData?.bio)
|
||||
trimmed(currentChatProfile?.bio)
|
||||
}
|
||||
|
||||
private var membershipDescription: String? {
|
||||
guard let createdAt = chat.chatData?.createdAt else { return nil }
|
||||
guard let createdAt = currentChatProfile?.createdAt else { return nil }
|
||||
let formatted = MessageProfileView.joinedFormatter.string(from: createdAt)
|
||||
return String(
|
||||
// format: NSLocalizedString("На платформе с %@", comment: "Message profile joined format"),
|
||||
@ -599,12 +626,12 @@ struct MessageProfileView: View {
|
||||
}
|
||||
|
||||
private var shouldShowRelationshipQuickActions: Bool {
|
||||
guard let relationship = chat.chatData?.relationship else { return false }
|
||||
guard let relationship = currentChatProfile?.relationship else { return false }
|
||||
return !relationship.isTargetInContactsOfCurrentUser
|
||||
}
|
||||
|
||||
private var ratingDisplayValue: String {
|
||||
guard let rating = chat.chatData?.rating else {
|
||||
guard let rating = currentChatProfile?.rating else {
|
||||
return NSLocalizedString("Недоступно", comment: "Message profile rating unavailable")
|
||||
}
|
||||
|
||||
@ -638,7 +665,7 @@ struct MessageProfileView: View {
|
||||
)
|
||||
}
|
||||
|
||||
guard let lastSeen = chat.chatData?.lastSeen else { return nil }
|
||||
guard let lastSeen = currentChatProfile?.lastSeen else { return nil }
|
||||
let lastSeenDate = Date(timeIntervalSince1970: TimeInterval(lastSeen))
|
||||
let interval = Date().timeIntervalSince(lastSeenDate)
|
||||
|
||||
@ -673,7 +700,7 @@ struct MessageProfileView: View {
|
||||
)
|
||||
}
|
||||
|
||||
if let relationship = chat.chatData?.relationship {
|
||||
if let relationship = currentChatProfile?.relationship {
|
||||
if relationship.isTargetInContactsOfCurrentUser {
|
||||
tags.append(
|
||||
StatusTag(
|
||||
@ -795,10 +822,14 @@ struct MessageProfileView: View {
|
||||
]
|
||||
}
|
||||
|
||||
private var currentChatProfile: ChatProfile? {
|
||||
chatProfile ?? chat.chatData
|
||||
}
|
||||
|
||||
private var isBlockedByCurrentUser: Bool { isBlockedByCurrentUserState }
|
||||
|
||||
private var avatarUrl: URL? {
|
||||
guard let chatData = chat.chatData,
|
||||
guard let chatData = currentChatProfile,
|
||||
let fileId = chatData.avatars?.current?.fileId else {
|
||||
return nil
|
||||
}
|
||||
@ -809,7 +840,7 @@ struct MessageProfileView: View {
|
||||
@ViewBuilder
|
||||
private var profileAvatar: some View {
|
||||
if let url = avatarUrl,
|
||||
let fileId = chat.chatData?.avatars?.current?.fileId,
|
||||
let fileId = currentChatProfile?.avatars?.current?.fileId,
|
||||
let userId = currentUserId {
|
||||
CachedAvatarView(url: url, fileId: fileId, userId: userId) {
|
||||
placeholderAvatar
|
||||
@ -857,7 +888,7 @@ struct MessageProfileView: View {
|
||||
}
|
||||
|
||||
private var avatarInitial: String {
|
||||
if let name = trimmed(chat.chatData?.customName) ?? trimmed(chat.chatData?.fullName) {
|
||||
if let name = trimmed(currentChatProfile?.customName) ?? trimmed(currentChatProfile?.fullName) {
|
||||
let components = name.split(separator: " ")
|
||||
let initials = components.prefix(2).compactMap { $0.first }
|
||||
if !initials.isEmpty {
|
||||
@ -865,7 +896,7 @@ struct MessageProfileView: View {
|
||||
}
|
||||
}
|
||||
|
||||
if let login = trimmed(chat.chatData?.login) {
|
||||
if let login = trimmed(currentChatProfile?.login) {
|
||||
return String(login.prefix(1)).uppercased()
|
||||
}
|
||||
|
||||
@ -880,13 +911,13 @@ struct MessageProfileView: View {
|
||||
}
|
||||
|
||||
private var displayName: String {
|
||||
if let custom = trimmed(chat.chatData?.customName) {
|
||||
if let custom = trimmed(currentChatProfile?.customName) {
|
||||
return custom
|
||||
}
|
||||
if let full = trimmed(chat.chatData?.fullName) {
|
||||
if let full = trimmed(currentChatProfile?.fullName) {
|
||||
return full
|
||||
}
|
||||
if let login = trimmed(chat.chatData?.login) {
|
||||
if let login = trimmed(currentChatProfile?.login) {
|
||||
return "@\(login)"
|
||||
}
|
||||
return NSLocalizedString("Неизвестный пользователь", comment: "Message profile fallback title")
|
||||
@ -894,9 +925,9 @@ struct MessageProfileView: View {
|
||||
|
||||
private var publicFullName: String? {
|
||||
guard
|
||||
let custom = trimmed(chat.chatData?.customName),
|
||||
let custom = trimmed(currentChatProfile?.customName),
|
||||
!custom.isEmpty, // custom должен быть
|
||||
let full = trimmed(chat.chatData?.fullName),
|
||||
let full = trimmed(currentChatProfile?.fullName),
|
||||
!full.isEmpty // full должен быть
|
||||
else {
|
||||
return nil
|
||||
@ -906,16 +937,16 @@ struct MessageProfileView: View {
|
||||
}
|
||||
|
||||
private var loginDisplay: String? {
|
||||
guard let login = trimmed(chat.chatData?.login) else { return nil }
|
||||
guard let login = trimmed(currentChatProfile?.login) else { return nil }
|
||||
return "@\(login)"
|
||||
}
|
||||
|
||||
private var isDeletedUser: Bool {
|
||||
trimmed(chat.chatData?.login) == nil
|
||||
trimmed(currentChatProfile?.login) == nil
|
||||
}
|
||||
|
||||
private var isOfficial: Bool {
|
||||
chat.chatData?.isOfficial ?? false
|
||||
currentChatProfile?.isOfficial ?? false
|
||||
}
|
||||
|
||||
// MARK: - Formatters & Models
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user