patch security setting

This commit is contained in:
cheykrym 2025-10-24 11:43:28 +03:00
parent e3cf374893
commit 107318ef21
11 changed files with 161 additions and 28 deletions

View File

@ -90,7 +90,7 @@
} }
}, },
"Email" : { "Email" : {
"comment" : "Заголовок экрана настроек email\nРаздел настроек безопасности для email" "comment" : "Заголовок экрана настроек email"
}, },
"Email не подтверждён. Подтвердите, чтобы активировать дополнительные проверки." : { "Email не подтверждён. Подтвердите, чтобы активировать дополнительные проверки." : {
"comment" : "Описание необходимости подтверждения email" "comment" : "Описание необходимости подтверждения email"
@ -226,9 +226,6 @@
"Аудио" : { "Аудио" : {
"comment" : "Audio message placeholder" "comment" : "Audio message placeholder"
}, },
"Аутентификация" : {
"comment" : "Раздел настроек безопасности для аутентификации"
},
"Без звука (скоро)" : { "Без звука (скоро)" : {
}, },
@ -266,6 +263,9 @@
"Введите код из приложения" : { "Введите код из приложения" : {
"comment" : "Поле ввода кода 2FA" "comment" : "Поле ввода кода 2FA"
}, },
"Введите пароль" : {
"comment" : "Поле ввода пароля на приложение"
},
"Веб" : { "Веб" : {
"comment" : "Тип сессии — веб" "comment" : "Тип сессии — веб"
}, },
@ -363,6 +363,9 @@
"Всего сессий" : { "Всего сессий" : {
"comment" : "Сводка по количеству сессий" "comment" : "Сводка по количеству сессий"
}, },
"Вход и защита аккаунта (заглушка)" : {
"comment" : "Раздел настроек безопасности для аутентификации"
},
"Вы" : { "Вы" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -611,6 +614,9 @@
"Защита входа" : { "Защита входа" : {
"comment" : "Раздел защиты входа через email" "comment" : "Раздел защиты входа через email"
}, },
"Защита приложением будет добавлена в будущих обновлениях." : {
"comment" : "Сообщение заглушки пароля на приложение"
},
"Здесь не будут чаты" : { "Здесь не будут чаты" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1017,6 +1023,9 @@
} }
} }
}, },
"Настоящая защита приложения появится позже. Пока вы можете ознакомится с макетом." : {
"comment" : "Описание заглушки для пароля на приложение"
},
"Настройка приложения" : { "Настройка приложения" : {
"comment" : "Раздел инструкций подключения" "comment" : "Раздел инструкций подключения"
}, },
@ -1580,7 +1589,7 @@
} }
}, },
"Пароли не совпадают" : { "Пароли не совпадают" : {
"comment" : "Пароли не совпадают", "comment" : "Заголовок ошибки несовпадения паролей\nПароли не совпадают",
"localizations" : { "localizations" : {
"en" : { "en" : {
"stringUnit" : { "stringUnit" : {
@ -1633,6 +1642,9 @@
} }
} }
}, },
"Пароль на приложение" : {
"comment" : "Заголовок экрана пароля на приложение\nПереход к настройкам пароля на приложение"
},
"Пароль не удовлетворяет требованиям" : { "Пароль не удовлетворяет требованиям" : {
"extractionState" : "manual", "extractionState" : "manual",
"localizations" : { "localizations" : {
@ -1654,12 +1666,18 @@
} }
} }
}, },
"Пароль-приложение" : {
"comment" : "Раздел формы установки пароля на приложение"
},
"Первый вход: %@" : { "Первый вход: %@" : {
"comment" : "Дата первого входа в сессию" "comment" : "Дата первого входа в сессию"
}, },
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : { "Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
"comment" : "FAQ answer: reset password" "comment" : "FAQ answer: reset password"
}, },
"Повторите пароль" : {
"comment" : "Поле подтверждения пароля на приложение"
},
"Повторить" : { "Повторить" : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -1869,6 +1887,9 @@
} }
} }
} }
},
"Приватность и контроль" : {
}, },
"Приватные чаты" : { "Приватные чаты" : {
"localizations" : { "localizations" : {
@ -1974,6 +1995,9 @@
"Проверочный код" : { "Проверочный код" : {
"comment" : "Раздел верификации 2FA" "comment" : "Раздел верификации 2FA"
}, },
"Проверьте ввод и попробуйте снова." : {
"comment" : "Сообщение ошибки несовпадения паролей"
},
"Проверьте данные и повторите попытку." : { "Проверьте данные и повторите попытку." : {
"localizations" : { "localizations" : {
"en" : { "en" : {
@ -2325,6 +2349,9 @@
} }
} }
}, },
"Сохранить пароль" : {
"comment" : "Кнопка сохранения пароля на приложение"
},
"Спасибо! Мы получили ваш отзыв" : { "Спасибо! Мы получили ваш отзыв" : {
"comment" : "feedback: success title", "comment" : "feedback: success title",
"localizations" : { "localizations" : {

View File

@ -34,7 +34,7 @@ class LoginViewModel: ObservableObject {
} }
enum OnboardingDestination: Equatable { enum OnboardingDestination: Equatable {
case twoFactor case securitySettings
} }
private enum DefaultsKeys { private enum DefaultsKeys {
@ -132,7 +132,7 @@ class LoginViewModel: ObservableObject {
self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина self?.isLoggedIn = true // 👈 переключаем на главный экран после автологина
self?.loadStoredUser() self?.loadStoredUser()
self?.socketService.connectForCurrentUser() self?.socketService.connectForCurrentUser()
self?.onboardingDestination = .twoFactor self?.onboardingDestination = .securitySettings
} else { } else {
self?.socketService.disconnect() self?.socketService.disconnect()
} }

View File

@ -21,7 +21,7 @@ struct MainView: View {
@State private var isQrPresented = false @State private var isQrPresented = false
@State private var deepLinkChatItem: PrivateChatListItem? @State private var deepLinkChatItem: PrivateChatListItem?
@State private var isDeepLinkChatActive = false @State private var isDeepLinkChatActive = false
@State private var hasTriggeredTwoFactorOnboarding = false @State private var hasTriggeredSecuritySettingsOnboarding = false
private var tabTitle: String { private var tabTitle: String {
switch selectedTab { switch selectedTab {
@ -224,13 +224,13 @@ private extension MainView {
} }
func handleTwoFactorOnboardingIfNeeded() { func handleTwoFactorOnboardingIfNeeded() {
guard viewModel.onboardingDestination == .twoFactor else { guard viewModel.onboardingDestination == .securitySettings else {
hasTriggeredTwoFactorOnboarding = false hasTriggeredSecuritySettingsOnboarding = false
return return
} }
guard !hasTriggeredTwoFactorOnboarding else { return } guard !hasTriggeredSecuritySettingsOnboarding else { return }
hasTriggeredTwoFactorOnboarding = true hasTriggeredSecuritySettingsOnboarding = true
if isMessengerModeEnabled { if isMessengerModeEnabled {
if selectedTab != 5 { if selectedTab != 5 {

View File

@ -0,0 +1,82 @@
import SwiftUI
struct AppLockSettingsView: View {
@State private var desiredPassword: String = ""
@State private var confirmationPassword: String = ""
@State private var activeAlert: AppLockAlert?
@FocusState private var focusedField: Field?
private enum Field: Hashable {
case desired
case confirmation
}
var body: some View {
Form {
Section(header: Text(NSLocalizedString("Пароль-приложение", comment: "Раздел формы установки пароля на приложение"))) {
SecureField(NSLocalizedString("Введите пароль", comment: "Поле ввода пароля на приложение"), text: $desiredPassword)
.focused($focusedField, equals: .desired)
SecureField(NSLocalizedString("Повторите пароль", comment: "Поле подтверждения пароля на приложение"), text: $confirmationPassword)
.focused($focusedField, equals: .confirmation)
Button(NSLocalizedString("Сохранить пароль", comment: "Кнопка сохранения пароля на приложение")) {
handleSaveTapped()
}
.disabled(desiredPassword.isEmpty || confirmationPassword.isEmpty)
}
Section {
Text(NSLocalizedString("Настоящая защита приложения появится позже. Пока вы можете ознакомится с макетом.", comment: "Описание заглушки для пароля на приложение"))
.font(.callout)
.foregroundColor(.secondary)
}
}
.navigationTitle(NSLocalizedString("Пароль на приложение", comment: "Заголовок экрана пароля на приложение"))
.navigationBarTitleDisplayMode(.inline)
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.4) {
focusedField = .desired
}
}
.alert(item: $activeAlert) { alert in
Alert(
title: Text(alert.title),
message: Text(alert.message),
dismissButton: .default(Text(NSLocalizedString("OK", comment: "Общий текст кнопки OK")))
)
}
}
private func handleSaveTapped() {
guard !desiredPassword.isEmpty, desiredPassword == confirmationPassword else {
activeAlert = AppLockAlert(
title: NSLocalizedString("Пароли не совпадают", comment: "Заголовок ошибки несовпадения паролей"),
message: NSLocalizedString("Проверьте ввод и попробуйте снова.", comment: "Сообщение ошибки несовпадения паролей"))
return
}
activeAlert = AppLockAlert(
title: NSLocalizedString("Скоро", comment: "Заголовок заглушки"),
message: NSLocalizedString("Защита приложением будет добавлена в будущих обновлениях.", comment: "Сообщение заглушки пароля на приложение")
)
desiredPassword.removeAll()
confirmationPassword.removeAll()
}
}
private struct AppLockAlert: Identifiable {
let id = UUID()
let title: String
let message: String
}
#if DEBUG
struct AppLockSettingsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
AppLockSettingsView()
}
}
}
#endif

View File

@ -4,37 +4,57 @@ struct SecuritySettingsView: View {
@ObservedObject var viewModel: LoginViewModel @ObservedObject var viewModel: LoginViewModel
@State private var isTwoFactorActive = false @State private var isTwoFactorActive = false
@State private var isEmailSettingsActive = false @State private var isEmailSettingsActive = false
@State private var isAppLockActive = false
var body: some View { var body: some View {
List { List {
Section(header: Text(NSLocalizedString("Аутентификация", comment: "Раздел настроек безопасности для аутентификации"))) { Section(header: Text(NSLocalizedString("Вход и защита аккаунта (заглушка)", comment: "Раздел настроек безопасности для аутентификации"))) {
NavigationLink(isActive: $isTwoFactorActive) { NavigationLink(isActive: $isTwoFactorActive) {
TwoFactorAuthView() TwoFactorAuthView()
} label: { } label: {
Label(NSLocalizedString("Двухфакторная аутентификация", comment: "Переход к настройкам двухфакторной аутентификации"), systemImage: "lock.shield") Label(NSLocalizedString("Двухфакторная аутентификация", comment: "Переход к настройкам двухфакторной аутентификации"), systemImage: "lock.shield")
} }
}
Section(header: Text(NSLocalizedString("Email", comment: "Раздел настроек безопасности для email"))) {
NavigationLink(isActive: $isEmailSettingsActive) { NavigationLink(isActive: $isEmailSettingsActive) {
EmailSecuritySettingsView() EmailSecuritySettingsView()
} label: { } label: {
Label(NSLocalizedString("Настройки email", comment: "Переход к настройкам безопасности email"), systemImage: "envelope") Label(NSLocalizedString("Настройки email", comment: "Переход к настройкам безопасности email"), systemImage: "envelope")
} }
NavigationLink(isActive: $isAppLockActive) {
AppLockSettingsView()
} label: {
Label(NSLocalizedString("Пароль на приложение", comment: "Переход к настройкам пароля на приложение"), systemImage: "lock.square")
}
}
Section(header: Text(NSLocalizedString("Приватность и контроль", comment: ""))) {
NavigationLink(destination: EditPrivacyView()) {
Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill")
}
NavigationLink(destination: ChangePasswordView()) {
Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key")
}
NavigationLink(destination: ActiveSessionsView()) {
Label(NSLocalizedString("Активные сессии", comment: ""), systemImage: "iphone")
}
} }
} }
.listStyle(.insetGrouped) .listStyle(.insetGrouped)
.navigationTitle(NSLocalizedString("Безопасность", comment: "Заголовок экрана настроек безопасности")) .navigationTitle(NSLocalizedString("Безопасность", comment: "Заголовок экрана настроек безопасности"))
.navigationBarTitleDisplayMode(.inline) .navigationBarTitleDisplayMode(.inline)
.onAppear { handleTwoFactorOnboardingIfNeeded() } .onAppear { handleSecuritySettingsOnboardingIfNeeded() }
.onChange(of: viewModel.onboardingDestination) { _ in .onChange(of: viewModel.onboardingDestination) { _ in
handleTwoFactorOnboardingIfNeeded() handleSecuritySettingsOnboardingIfNeeded()
} }
} }
private func handleTwoFactorOnboardingIfNeeded() { private func handleSecuritySettingsOnboardingIfNeeded() {
guard viewModel.onboardingDestination == .twoFactor else { return } guard viewModel.onboardingDestination == .securitySettings else { return }
isTwoFactorActive = true // isSecuritySettingsActive = true
viewModel.onboardingDestination = nil viewModel.onboardingDestination = nil
} }
} }

View File

@ -19,8 +19,8 @@ struct SettingsView: View {
// Label("Мой профиль", systemImage: "person.crop.circle") // Label("Мой профиль", systemImage: "person.crop.circle")
// } // }
NavigationLink(destination: EditPrivacyView()) { NavigationLink(destination: EditProfileView()) {
Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill") Label(NSLocalizedString("Редактировать профиль", comment: ""), systemImage: "person.crop.circle")
} }
NavigationLink(destination: BlockedUsersView()) { NavigationLink(destination: BlockedUsersView()) {
@ -30,17 +30,21 @@ struct SettingsView: View {
// MARK: - Безопасность // MARK: - Безопасность
Section(header: Text(NSLocalizedString("Безопасность", comment: ""))) { Section(header: Text(NSLocalizedString("Безопасность", comment: ""))) {
NavigationLink(destination: EditPrivacyView()) {
Label(NSLocalizedString("Конфиденциальность", comment: ""), systemImage: "lock.fill")
}
NavigationLink(destination: ChangePasswordView()) { NavigationLink(destination: ChangePasswordView()) {
Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key") Label(NSLocalizedString("Сменить пароль", comment: ""), systemImage: "key")
} }
NavigationLink(destination: ActiveSessionsView()) {
Label(NSLocalizedString("Активные сессии", comment: ""), systemImage: "iphone")
}
NavigationLink(isActive: $isSecurityActive) { NavigationLink(isActive: $isSecurityActive) {
SecuritySettingsView(viewModel: viewModel) SecuritySettingsView(viewModel: viewModel)
} label: { } label: {
Label(NSLocalizedString("Безопасность", comment: ""), systemImage: "lock.shield") Label(NSLocalizedString("Безопасность", comment: ""), systemImage: "lock.shield")
} }
NavigationLink(destination: ActiveSessionsView()) {
Label(NSLocalizedString("Активные сессии", comment: ""), systemImage: "iphone")
}
} }
// MARK: - Приложение // MARK: - Приложение
@ -177,7 +181,7 @@ struct SettingsView: View {
private extension SettingsView { private extension SettingsView {
func handleTwoFactorOnboardingIfNeeded() { func handleTwoFactorOnboardingIfNeeded() {
guard viewModel.onboardingDestination == .twoFactor else { return } guard viewModel.onboardingDestination == .securitySettings else { return }
isSecurityActive = true isSecurityActive = true
} }
} }