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

View File

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

View File

@ -21,7 +21,7 @@ struct MainView: View {
@State private var isQrPresented = false
@State private var deepLinkChatItem: PrivateChatListItem?
@State private var isDeepLinkChatActive = false
@State private var hasTriggeredTwoFactorOnboarding = false
@State private var hasTriggeredSecuritySettingsOnboarding = false
private var tabTitle: String {
switch selectedTab {
@ -224,13 +224,13 @@ private extension MainView {
}
func handleTwoFactorOnboardingIfNeeded() {
guard viewModel.onboardingDestination == .twoFactor else {
hasTriggeredTwoFactorOnboarding = false
guard viewModel.onboardingDestination == .securitySettings else {
hasTriggeredSecuritySettingsOnboarding = false
return
}
guard !hasTriggeredTwoFactorOnboarding else { return }
hasTriggeredTwoFactorOnboarding = true
guard !hasTriggeredSecuritySettingsOnboarding else { return }
hasTriggeredSecuritySettingsOnboarding = true
if isMessengerModeEnabled {
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
@State private var isTwoFactorActive = false
@State private var isEmailSettingsActive = false
@State private var isAppLockActive = false
var body: some View {
List {
Section(header: Text(NSLocalizedString("Аутентификация", comment: "Раздел настроек безопасности для аутентификации"))) {
Section(header: Text(NSLocalizedString("Вход и защита аккаунта (заглушка)", comment: "Раздел настроек безопасности для аутентификации"))) {
NavigationLink(isActive: $isTwoFactorActive) {
TwoFactorAuthView()
} label: {
Label(NSLocalizedString("Двухфакторная аутентификация", comment: "Переход к настройкам двухфакторной аутентификации"), systemImage: "lock.shield")
}
}
Section(header: Text(NSLocalizedString("Email", comment: "Раздел настроек безопасности для email"))) {
NavigationLink(isActive: $isEmailSettingsActive) {
EmailSecuritySettingsView()
} label: {
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)
.navigationTitle(NSLocalizedString("Безопасность", comment: "Заголовок экрана настроек безопасности"))
.navigationBarTitleDisplayMode(.inline)
.onAppear { handleTwoFactorOnboardingIfNeeded() }
.onAppear { handleSecuritySettingsOnboardingIfNeeded() }
.onChange(of: viewModel.onboardingDestination) { _ in
handleTwoFactorOnboardingIfNeeded()
handleSecuritySettingsOnboardingIfNeeded()
}
}
private func handleTwoFactorOnboardingIfNeeded() {
guard viewModel.onboardingDestination == .twoFactor else { return }
isTwoFactorActive = true
private func handleSecuritySettingsOnboardingIfNeeded() {
guard viewModel.onboardingDestination == .securitySettings else { return }
// isSecuritySettingsActive = true
viewModel.onboardingDestination = nil
}
}

View File

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