patch security setting
This commit is contained in:
parent
e3cf374893
commit
107318ef21
@ -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" : {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
82
yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift
Normal file
82
yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift
Normal 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
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,9 +18,9 @@ struct SettingsView: View {
|
||||
// NavigationLink(destination: EditProfileView()) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user