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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user