add edit privacy
This commit is contained in:
parent
c96fe4991d
commit
fb8413e68c
@ -94,3 +94,24 @@ struct ProfilePermissionsPayload: Decodable {
|
|||||||
let maxMessageAutoDeleteSeconds: Int?
|
let maxMessageAutoDeleteSeconds: Int?
|
||||||
let autoDeleteAfterDays: 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 {
|
enum ProfileServiceError: LocalizedError {
|
||||||
case unexpectedStatus(String)
|
case unexpectedStatus(String)
|
||||||
case decoding(debugDescription: String)
|
case decoding(debugDescription: String)
|
||||||
|
case encoding(String)
|
||||||
|
|
||||||
var errorDescription: String? {
|
var errorDescription: String? {
|
||||||
switch self {
|
switch self {
|
||||||
@ -12,6 +13,8 @@ enum ProfileServiceError: LocalizedError {
|
|||||||
return AppConfig.DEBUG
|
return AppConfig.DEBUG
|
||||||
? debugDescription
|
? debugDescription
|
||||||
: NSLocalizedString("Не удалось загрузить профиль.", comment: "Profile service decoding error")
|
: 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 {
|
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)
|
||||||
|
|||||||
@ -74,6 +74,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"OK" : {
|
"OK" : {
|
||||||
|
"comment" : "Profile update alert button",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -219,6 +220,9 @@
|
|||||||
"Глобальный поиск" : {
|
"Глобальный поиск" : {
|
||||||
"comment" : "Global search section"
|
"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"
|
"comment" : "Search error fallback\nSearch service decoding error"
|
||||||
@ -621,6 +628,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Не удалось обработать ответ сервера." : {
|
"Не удалось обработать ответ сервера." : {
|
||||||
|
"comment" : "Profile update decode error",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -630,6 +638,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Не удалось подготовить данные запроса." : {
|
||||||
|
"comment" : "Profile update encoding error"
|
||||||
|
},
|
||||||
"Не удалось сериализовать данные запроса." : {
|
"Не удалось сериализовать данные запроса." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -640,6 +651,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Не удалось сохранить изменения профиля." : {
|
||||||
|
"comment" : "Profile update unexpected status"
|
||||||
|
},
|
||||||
"Неверный запрос (400)." : {
|
"Неверный запрос (400)." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
@ -823,6 +837,7 @@
|
|||||||
|
|
||||||
},
|
},
|
||||||
"Ошибка" : {
|
"Ошибка" : {
|
||||||
|
"comment" : "Profile update error title",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
|
|||||||
@ -4,6 +4,8 @@ struct EditPrivacyView: View {
|
|||||||
@State private var profilePermissions = ProfilePermissionsState()
|
@State private var profilePermissions = ProfilePermissionsState()
|
||||||
@State private var isLoading = false
|
@State private var isLoading = false
|
||||||
@State private var loadError: String?
|
@State private var loadError: String?
|
||||||
|
@State private var isSaving = false
|
||||||
|
@State private var alertData: AlertData?
|
||||||
|
|
||||||
private let profileService = ProfileService()
|
private let profileService = ProfileService()
|
||||||
|
|
||||||
@ -112,11 +114,20 @@ struct EditPrivacyView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Button("Сохранить изменения") {
|
Button {
|
||||||
print("Параметры приватности: \(profilePermissions)")
|
Task {
|
||||||
|
await saveProfile()
|
||||||
}
|
}
|
||||||
|
} label: {
|
||||||
|
if isSaving {
|
||||||
|
ProgressView()
|
||||||
.frame(maxWidth: .infinity, alignment: .center)
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
.disabled(isLoading)
|
} else {
|
||||||
|
Text("Сохранить изменения")
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(isLoading || isSaving)
|
||||||
}
|
}
|
||||||
|
|
||||||
Section {
|
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("Настройки приватности")
|
.navigationTitle("Настройки приватности")
|
||||||
.onChange(of: profilePermissions.forceAutoDeleteMessagesInPrivate) { newValue in
|
.onChange(of: profilePermissions.forceAutoDeleteMessagesInPrivate) { newValue in
|
||||||
if newValue {
|
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 {
|
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 {
|
func loadProfile() async {
|
||||||
await MainActor.run {
|
await MainActor.run {
|
||||||
if !isLoading {
|
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