add edit privacy
This commit is contained in:
		
							parent
							
								
									c96fe4991d
								
							
						
					
					
						commit
						fb8413e68c
					
				@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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" : {
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user