From 107318ef21622f317a57fa1a69c3eaa516855040 Mon Sep 17 00:00:00 2001 From: cheykrym Date: Fri, 24 Oct 2025 11:43:28 +0300 Subject: [PATCH] patch security setting --- yobble/Resources/Localizable.xcstrings | 37 +++++++-- yobble/ViewModels/LoginViewModel.swift | 4 +- yobble/Views/Tab/MainView.swift | 10 +-- .../{ => Security}/ActiveSessionsView.swift | 0 .../Security/AppLockSettingsView.swift | 82 +++++++++++++++++++ .../{ => Security}/ChangePasswordView.swift | 0 .../{ => Security}/EditPrivacyView.swift | 0 .../EmailSecuritySettingsView.swift | 0 .../{ => Security}/TwoFactorAuthView.swift | 0 .../Tab/Settings/SecuritySettingsView.swift | 38 +++++++-- yobble/Views/Tab/Settings/SettingsView.swift | 18 ++-- 11 files changed, 161 insertions(+), 28 deletions(-) rename yobble/Views/Tab/Settings/{ => Security}/ActiveSessionsView.swift (100%) create mode 100644 yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift rename yobble/Views/Tab/Settings/{ => Security}/ChangePasswordView.swift (100%) rename yobble/Views/Tab/Settings/{ => Security}/EditPrivacyView.swift (100%) rename yobble/Views/Tab/Settings/{ => Security}/EmailSecuritySettingsView.swift (100%) rename yobble/Views/Tab/Settings/{ => Security}/TwoFactorAuthView.swift (100%) diff --git a/yobble/Resources/Localizable.xcstrings b/yobble/Resources/Localizable.xcstrings index 1d2a14c..8fdc778 100644 --- a/yobble/Resources/Localizable.xcstrings +++ b/yobble/Resources/Localizable.xcstrings @@ -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" : { diff --git a/yobble/ViewModels/LoginViewModel.swift b/yobble/ViewModels/LoginViewModel.swift index f17e45a..4515d74 100644 --- a/yobble/ViewModels/LoginViewModel.swift +++ b/yobble/ViewModels/LoginViewModel.swift @@ -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() } diff --git a/yobble/Views/Tab/MainView.swift b/yobble/Views/Tab/MainView.swift index c9a414f..4586910 100644 --- a/yobble/Views/Tab/MainView.swift +++ b/yobble/Views/Tab/MainView.swift @@ -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 { diff --git a/yobble/Views/Tab/Settings/ActiveSessionsView.swift b/yobble/Views/Tab/Settings/Security/ActiveSessionsView.swift similarity index 100% rename from yobble/Views/Tab/Settings/ActiveSessionsView.swift rename to yobble/Views/Tab/Settings/Security/ActiveSessionsView.swift diff --git a/yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift b/yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift new file mode 100644 index 0000000..a4276cb --- /dev/null +++ b/yobble/Views/Tab/Settings/Security/AppLockSettingsView.swift @@ -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 diff --git a/yobble/Views/Tab/Settings/ChangePasswordView.swift b/yobble/Views/Tab/Settings/Security/ChangePasswordView.swift similarity index 100% rename from yobble/Views/Tab/Settings/ChangePasswordView.swift rename to yobble/Views/Tab/Settings/Security/ChangePasswordView.swift diff --git a/yobble/Views/Tab/Settings/EditPrivacyView.swift b/yobble/Views/Tab/Settings/Security/EditPrivacyView.swift similarity index 100% rename from yobble/Views/Tab/Settings/EditPrivacyView.swift rename to yobble/Views/Tab/Settings/Security/EditPrivacyView.swift diff --git a/yobble/Views/Tab/Settings/EmailSecuritySettingsView.swift b/yobble/Views/Tab/Settings/Security/EmailSecuritySettingsView.swift similarity index 100% rename from yobble/Views/Tab/Settings/EmailSecuritySettingsView.swift rename to yobble/Views/Tab/Settings/Security/EmailSecuritySettingsView.swift diff --git a/yobble/Views/Tab/Settings/TwoFactorAuthView.swift b/yobble/Views/Tab/Settings/Security/TwoFactorAuthView.swift similarity index 100% rename from yobble/Views/Tab/Settings/TwoFactorAuthView.swift rename to yobble/Views/Tab/Settings/Security/TwoFactorAuthView.swift diff --git a/yobble/Views/Tab/Settings/SecuritySettingsView.swift b/yobble/Views/Tab/Settings/SecuritySettingsView.swift index d3e5a4d..2cce035 100644 --- a/yobble/Views/Tab/Settings/SecuritySettingsView.swift +++ b/yobble/Views/Tab/Settings/SecuritySettingsView.swift @@ -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 } } diff --git a/yobble/Views/Tab/Settings/SettingsView.swift b/yobble/Views/Tab/Settings/SettingsView.swift index 2b9a83f..476fecc 100644 --- a/yobble/Views/Tab/Settings/SettingsView.swift +++ b/yobble/Views/Tab/Settings/SettingsView.swift @@ -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 } }