add edit privacy

This commit is contained in:
cheykrym 2025-10-08 02:09:44 +03:00
parent c96fe4991d
commit fb8413e68c
4 changed files with 193 additions and 4 deletions

View File

@ -94,3 +94,24 @@ struct ProfilePermissionsPayload: Decodable {
let maxMessageAutoDeleteSeconds: Int?
let autoDeleteAfterDays: Int?
}
struct ProfilePermissionsRequestPayload: Encodable {
let isSearchable: Bool
let allowMessageForwarding: Bool
let allowMessagesFromNonContacts: Bool
let showProfilePhotoToNonContacts: Bool
let lastSeenVisibility: Int
let showBioToNonContacts: Bool
let showStoriesToNonContacts: Bool
let allowServerChats: Bool
let publicInvitePermission: Int
let groupInvitePermission: Int
let callPermission: Int
let forceAutoDeleteMessagesInPrivate: Bool
let maxMessageAutoDeleteSeconds: Int?
let autoDeleteAfterDays: Int?
}
struct ProfileUpdateRequestPayload: Encodable {
let profilePermissions: ProfilePermissionsRequestPayload
}

View File

@ -3,6 +3,7 @@ import Foundation
enum ProfileServiceError: LocalizedError {
case unexpectedStatus(String)
case decoding(debugDescription: String)
case encoding(String)
var errorDescription: String? {
switch self {
@ -12,6 +13,8 @@ enum ProfileServiceError: LocalizedError {
return AppConfig.DEBUG
? debugDescription
: NSLocalizedString("Не удалось загрузить профиль.", comment: "Profile service decoding error")
case .encoding(let message):
return message
}
}
}
@ -70,6 +73,66 @@ final class ProfileService {
}
}
func updateProfile(_ payload: ProfileUpdateRequestPayload, completion: @escaping (Result<String, Error>) -> Void) {
let encoder = JSONEncoder()
encoder.keyEncodingStrategy = .convertToSnakeCase
guard let body = try? encoder.encode(payload) else {
let message = NSLocalizedString("Не удалось подготовить данные запроса.", comment: "Profile update encoding error")
completion(.failure(ProfileServiceError.encoding(message)))
return
}
client.request(
path: "/v1/profile/edit",
method: .put,
body: body,
requiresAuth: true
) { result in
switch result {
case .success(let response):
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let apiResponse = try decoder.decode(APIResponse<MessagePayload>.self, from: response.data)
guard apiResponse.status == "fine" else {
let message = apiResponse.detail ?? NSLocalizedString("Не удалось сохранить изменения профиля.", comment: "Profile update unexpected status")
completion(.failure(ProfileServiceError.unexpectedStatus(message)))
return
}
completion(.success(apiResponse.data.message))
} catch {
let debugMessage = Self.describeDecodingError(error: error, data: response.data)
if AppConfig.DEBUG {
print("[ProfileService] decode update response failed: \(debugMessage)")
}
if AppConfig.DEBUG {
completion(.failure(ProfileServiceError.decoding(debugDescription: debugMessage)))
} else {
let message = NSLocalizedString("Не удалось обработать ответ сервера.", comment: "Profile update decode error")
completion(.failure(ProfileServiceError.unexpectedStatus(message)))
}
}
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 updateProfile(_ payload: ProfileUpdateRequestPayload) async throws -> String {
try await withCheckedThrowingContinuation { continuation in
updateProfile(payload) { 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

@ -74,6 +74,7 @@
}
},
"OK" : {
"comment" : "Profile update alert button",
"localizations" : {
"en" : {
"stringUnit" : {
@ -219,6 +220,9 @@
"Глобальный поиск" : {
"comment" : "Global search section"
},
"Готово" : {
"comment" : "Profile update success title"
},
"Данные" : {
},
@ -570,6 +574,9 @@
},
"Настройки приватности" : {
},
"Настройки приватности обновлены." : {
"comment" : "Profile update success fallback"
},
"Не удалось выполнить поиск." : {
"comment" : "Search error fallback\nSearch service decoding error"
@ -621,6 +628,7 @@
}
},
"Не удалось обработать ответ сервера." : {
"comment" : "Profile update decode error",
"localizations" : {
"en" : {
"stringUnit" : {
@ -630,6 +638,9 @@
}
}
},
"Не удалось подготовить данные запроса." : {
"comment" : "Profile update encoding error"
},
"Не удалось сериализовать данные запроса." : {
"localizations" : {
"en" : {
@ -640,6 +651,9 @@
}
}
},
"Не удалось сохранить изменения профиля." : {
"comment" : "Profile update unexpected status"
},
"Неверный запрос (400)." : {
"localizations" : {
"en" : {
@ -823,6 +837,7 @@
},
"Ошибка" : {
"comment" : "Profile update error title",
"localizations" : {
"en" : {
"stringUnit" : {

View File

@ -4,6 +4,8 @@ struct EditPrivacyView: View {
@State private var profilePermissions = ProfilePermissionsState()
@State private var isLoading = false
@State private var loadError: String?
@State private var isSaving = false
@State private var alertData: AlertData?
private let profileService = ProfileService()
@ -112,11 +114,20 @@ struct EditPrivacyView: View {
}
Section {
Button("Сохранить изменения") {
print("Параметры приватности: \(profilePermissions)")
Button {
Task {
await saveProfile()
}
} label: {
if isSaving {
ProgressView()
.frame(maxWidth: .infinity, alignment: .center)
} else {
Text("Сохранить изменения")
.frame(maxWidth: .infinity, alignment: .center)
}
}
.frame(maxWidth: .infinity, alignment: .center)
.disabled(isLoading)
.disabled(isLoading || isSaving)
}
Section {
@ -130,6 +141,17 @@ struct EditPrivacyView: View {
}
}
}
.alert(item: $alertData) { data in
Alert(
title: Text(data.kind == .success
? NSLocalizedString("Готово", comment: "Profile update success title")
: NSLocalizedString("Ошибка", comment: "Profile update error title")),
message: Text(data.message),
dismissButton: .default(Text(NSLocalizedString("OK", comment: "Profile update alert button"))) {
alertData = nil
}
)
}
.navigationTitle("Настройки приватности")
.onChange(of: profilePermissions.forceAutoDeleteMessagesInPrivate) { newValue in
if newValue {
@ -212,7 +234,64 @@ extension ProfilePermissionsState {
}
}
private extension ProfilePermissionsState {
var requestPayload: ProfilePermissionsRequestPayload {
ProfilePermissionsRequestPayload(
isSearchable: isSearchable,
allowMessageForwarding: allowMessageForwarding,
allowMessagesFromNonContacts: allowMessagesFromNonContacts,
showProfilePhotoToNonContacts: showProfilePhotoToNonContacts,
lastSeenVisibility: lastSeenVisibility,
showBioToNonContacts: showBioToNonContacts,
showStoriesToNonContacts: showStoriesToNonContacts,
allowServerChats: allowServerChats,
publicInvitePermission: publicInvitePermission,
groupInvitePermission: groupInvitePermission,
callPermission: callPermission,
forceAutoDeleteMessagesInPrivate: forceAutoDeleteMessagesInPrivate,
maxMessageAutoDeleteSeconds: maxMessageAutoDeleteSeconds,
autoDeleteAfterDays: autoDeleteAfterDays
)
}
}
private extension EditPrivacyView {
func saveProfile() async {
let shouldProceed = await MainActor.run { () -> Bool in
if isSaving {
return false
}
isSaving = true
return true
}
guard shouldProceed else { return }
do {
let requestPayload = ProfileUpdateRequestPayload(profilePermissions: profilePermissions.requestPayload)
let responseMessage = try await profileService.updateProfile(requestPayload)
let fallback = NSLocalizedString("Настройки приватности обновлены.", comment: "Profile update success fallback")
let message = responseMessage.isEmpty ? fallback : responseMessage
await MainActor.run {
alertData = AlertData(kind: .success, message: message)
isSaving = false
}
} catch {
let message: String
if let error = error as? LocalizedError, let description = error.errorDescription {
message = description
} else {
message = error.localizedDescription
}
await MainActor.run {
alertData = AlertData(kind: .error, message: message)
isSaving = false
}
}
}
func loadProfile() async {
await MainActor.run {
if !isLoading {
@ -242,3 +321,14 @@ private extension EditPrivacyView {
}
}
}
private struct AlertData: Identifiable {
enum Kind {
case success
case error
}
let id = UUID()
let kind: Kind
let message: String
}