update
This commit is contained in:
parent
0617d1bd9c
commit
0311e0f5b1
@ -198,6 +198,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Yobble Passport" : {
|
||||
|
||||
},
|
||||
"Автоудаление аккаунта" : {
|
||||
"localizations" : {
|
||||
@ -285,9 +288,6 @@
|
||||
},
|
||||
"Введите логин" : {
|
||||
"comment" : "Логин"
|
||||
},
|
||||
"Введите логин и мы отправим шестизначный код подтверждения." : {
|
||||
|
||||
},
|
||||
"Введите пароль" : {
|
||||
"comment" : "Пароль\nПоле ввода пароля на приложение"
|
||||
@ -392,7 +392,7 @@
|
||||
"Всего сессий" : {
|
||||
"comment" : "Сводка по количеству сессий"
|
||||
},
|
||||
"Вход" : {
|
||||
"Вход в аккаунт" : {
|
||||
|
||||
},
|
||||
"Вход и защита аккаунта (заглушка)" : {
|
||||
@ -778,9 +778,6 @@
|
||||
},
|
||||
"Код дружбы" : {
|
||||
"comment" : "Friend code badge"
|
||||
},
|
||||
"Код может прийти по почте, push или в другое подключенное приложение." : {
|
||||
|
||||
},
|
||||
"Код отправлен. Аккаунт: @%@" : {
|
||||
|
||||
@ -905,6 +902,7 @@
|
||||
},
|
||||
"Логин" : {
|
||||
"comment" : "Логин",
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
@ -1777,9 +1775,6 @@
|
||||
},
|
||||
"Перейдите в раздел \"Настройки > Сменить пароль\" и следуйте инструкциям." : {
|
||||
"comment" : "FAQ answer: reset password"
|
||||
},
|
||||
"Перейти к входу по коду" : {
|
||||
|
||||
},
|
||||
"По умолчанию это полноценная соцсеть с лентой, историями и подписками. Если нужно только общение без лишнего контента, переключитесь на режим “Только чаты”. Переключить режим можно в любой момент." : {
|
||||
|
||||
@ -1923,9 +1918,6 @@
|
||||
},
|
||||
"Получать коды на email при входе" : {
|
||||
"comment" : "Переключатель отправки кодов при входе"
|
||||
},
|
||||
"Получить код" : {
|
||||
|
||||
},
|
||||
"Получить ответ от команды" : {
|
||||
"comment" : "feedback: contact toggle",
|
||||
|
||||
@ -25,7 +25,13 @@ class LoginViewModel: ObservableObject {
|
||||
@Published var termsErrorMessage: String?
|
||||
@Published var onboardingDestination: OnboardingDestination?
|
||||
@Published var loginFlowStep: LoginFlowStep = .passwordlessRequest
|
||||
@Published var passwordlessLogin: String = ""
|
||||
@Published var passwordlessLogin: String = "" {
|
||||
didSet {
|
||||
if passwordlessLogin.count > 32 {
|
||||
passwordlessLogin = String(passwordlessLogin.prefix(32))
|
||||
}
|
||||
}
|
||||
}
|
||||
@Published var verificationCode: String = "" {
|
||||
didSet {
|
||||
let filtered = verificationCode
|
||||
@ -137,8 +143,13 @@ class LoginViewModel: ObservableObject {
|
||||
func login() {
|
||||
isLoading = true
|
||||
showError = false
|
||||
let trimmedLogin = passwordlessLogin.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if trimmedLogin != passwordlessLogin {
|
||||
passwordlessLogin = trimmedLogin
|
||||
}
|
||||
username = trimmedLogin
|
||||
|
||||
authService.login(username: username, password: password) { [weak self] success, error in
|
||||
authService.login(username: trimmedLogin, password: password) { [weak self] success, error in
|
||||
DispatchQueue.main.async {
|
||||
self?.isLoading = false
|
||||
if success {
|
||||
@ -178,6 +189,9 @@ class LoginViewModel: ObservableObject {
|
||||
self.loginFlowStep = .passwordlessVerify
|
||||
self.startResendTimer()
|
||||
} else {
|
||||
if self.handlePasswordlessRedirect(message: message, login: trimmedLogin) {
|
||||
return
|
||||
}
|
||||
self.errorMessage = message ?? NSLocalizedString("Не удалось отправить код.", comment: "")
|
||||
self.showError = true
|
||||
}
|
||||
@ -208,7 +222,7 @@ class LoginViewModel: ObservableObject {
|
||||
} else {
|
||||
self.errorMessage = message ?? NSLocalizedString("Проверьте введённый код и попробуйте снова.", comment: "")
|
||||
self.showError = true
|
||||
self.verificationCode = ""
|
||||
// self.verificationCode = ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -385,6 +399,26 @@ extension LoginViewModel {
|
||||
}
|
||||
|
||||
private extension LoginViewModel {
|
||||
func handlePasswordlessRedirect(message: String?, login: String) -> Bool {
|
||||
guard let message else { return false }
|
||||
|
||||
switch message {
|
||||
case "otp_not_found":
|
||||
username = login
|
||||
passwordlessLogin = login
|
||||
loginFlowStep = .password
|
||||
return true
|
||||
case "account_not_found":
|
||||
username = login
|
||||
passwordlessLogin = login
|
||||
hasAcceptedTerms = false
|
||||
loginFlowStep = .registration
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
enum Constants {
|
||||
static let verificationCodeLength = 6
|
||||
static let defaultResendDelay = 60
|
||||
|
||||
@ -109,7 +109,7 @@ struct PasswordLoginView: View {
|
||||
}
|
||||
|
||||
private var isUsernameValid: Bool {
|
||||
LoginViewModel.isLoginValid(viewModel.username)
|
||||
LoginViewModel.isLoginValid(viewModel.passwordlessLogin)
|
||||
}
|
||||
|
||||
private var isPasswordValid: Bool {
|
||||
@ -151,21 +151,16 @@ struct PasswordLoginView: View {
|
||||
HStack(spacing: 8) {
|
||||
Text("@")
|
||||
.foregroundColor(.secondary)
|
||||
TextField(NSLocalizedString("Введите логин", comment: ""), text: $viewModel.username)
|
||||
TextField(NSLocalizedString("Введите логин", comment: ""), text: $viewModel.passwordlessLogin)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.focused($focusedField, equals: .username)
|
||||
.onChange(of: viewModel.username) { newValue in
|
||||
if newValue.count > 32 {
|
||||
viewModel.username = String(newValue.prefix(32))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
if !isUsernameValid && !viewModel.username.isEmpty {
|
||||
if !isUsernameValid && !viewModel.passwordlessLogin.isEmpty {
|
||||
Text(NSLocalizedString("Неверный логин", comment: "Неверный логин"))
|
||||
.foregroundColor(.red)
|
||||
.font(.caption)
|
||||
@ -400,22 +395,17 @@ private struct PasswordlessRequestView: View {
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
LoginTopBar(openLanguageSettings: openLanguageSettings, onShowModePrompt: hideKeyboardAndShowModePrompt)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(NSLocalizedString("Вход", comment: ""))
|
||||
|
||||
Text(NSLocalizedString("Yobble Passport", comment: ""))
|
||||
.font(.largeTitle).bold()
|
||||
// Text(NSLocalizedString("Введите логин и мы отправим шестизначный код подтверждения.", comment: ""))
|
||||
// .foregroundColor(.secondary)
|
||||
// Text(NSLocalizedString("Введите логин и мы отправим шестизначный код подтверждения.", comment: ""))
|
||||
// .foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
// Text(NSLocalizedString("Логин", comment: ""))
|
||||
// .font(.subheadline)
|
||||
// .foregroundColor(.secondary)
|
||||
HStack(spacing: 8) {
|
||||
Text("@")
|
||||
.foregroundColor(.secondary)
|
||||
@ -434,11 +424,6 @@ private struct PasswordlessRequestView: View {
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
if !isLoginValid && !viewModel.passwordlessLogin.isEmpty {
|
||||
Text(NSLocalizedString("Неверный логин", comment: ""))
|
||||
.foregroundColor(.red)
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
@ -451,10 +436,6 @@ private struct PasswordlessRequestView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
} else {
|
||||
// Text(NSLocalizedString("Получить код", comment: ""))
|
||||
// .bold()
|
||||
// .frame(maxWidth: .infinity)
|
||||
// .padding()
|
||||
Text(NSLocalizedString("Войти", comment: ""))
|
||||
.bold()
|
||||
.frame(maxWidth: .infinity)
|
||||
@ -478,31 +459,6 @@ private struct PasswordlessRequestView: View {
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
|
||||
// Button(action: {
|
||||
// viewModel.hasAcceptedTerms = false
|
||||
// withAnimation {
|
||||
// viewModel.showRegistration()
|
||||
// }
|
||||
// }) {
|
||||
// Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
||||
// .foregroundColor(.blue)
|
||||
// .frame(maxWidth: .infinity)
|
||||
// }
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.showPasswordLogin()
|
||||
}
|
||||
} label: {
|
||||
Text(NSLocalizedString("Войти по паролю", comment: ""))
|
||||
.font(.body)
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
|
||||
// Text(NSLocalizedString("Код может прийти по почте, push или в другое подключенное приложение.", comment: ""))
|
||||
// .font(.footnote)
|
||||
// .foregroundColor(.secondary)
|
||||
}
|
||||
.padding(.vertical, 32)
|
||||
}
|
||||
@ -554,35 +510,55 @@ private struct PasswordlessVerifyView: View {
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
LoginTopBar(openLanguageSettings: openLanguageSettings, onShowModePrompt: hideKeyboardAndShowModePrompt)
|
||||
|
||||
Button {
|
||||
// focusedField = nil
|
||||
withAnimation {
|
||||
viewModel.showPasswordlessRequest()
|
||||
}
|
||||
} label: {
|
||||
HStack(spacing: 6) {
|
||||
Image(systemName: "arrow.left")
|
||||
Text(NSLocalizedString("Назад", comment: ""))
|
||||
}
|
||||
.font(.footnote)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(NSLocalizedString("Введите код", comment: ""))
|
||||
Text(NSLocalizedString("Вход в аккаунт", comment: ""))
|
||||
.font(.largeTitle).bold()
|
||||
Text(String(format: NSLocalizedString("Код отправлен. Аккаунт: @%@", comment: ""), viewModel.passwordlessLogin))
|
||||
Text(String(format: NSLocalizedString("@%@", comment: ""), viewModel.passwordlessLogin))
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
// Text(NSLocalizedString("Введите код", comment: ""))
|
||||
// .font(.largeTitle).bold()
|
||||
//
|
||||
// Text(String(format: NSLocalizedString("Код отправлен. Аккаунт: @%@", comment: ""), viewModel.passwordlessLogin))
|
||||
// .foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
OTPInputView(code: $viewModel.verificationCode, isFocused: $isCodeFieldFocused)
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.verifyPasswordlessCode()
|
||||
}
|
||||
} label: {
|
||||
if viewModel.isVerifyingCode {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
} else {
|
||||
Text(NSLocalizedString("Подтвердить вход", comment: ""))
|
||||
.bold()
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.background(viewModel.canVerifyPasswordlessCode ? Color.blue : Color.gray)
|
||||
.cornerRadius(12)
|
||||
.disabled(!viewModel.canVerifyPasswordlessCode)
|
||||
// Button {
|
||||
// withAnimation {
|
||||
// viewModel.verifyPasswordlessCode()
|
||||
// }
|
||||
// } label: {
|
||||
// if viewModel.isVerifyingCode {
|
||||
// ProgressView()
|
||||
// .frame(maxWidth: .infinity)
|
||||
// .padding()
|
||||
// } else {
|
||||
// Text(NSLocalizedString("Подтвердить вход", comment: ""))
|
||||
// .bold()
|
||||
// .frame(maxWidth: .infinity)
|
||||
// .padding()
|
||||
// }
|
||||
// }
|
||||
// .foregroundColor(.white)
|
||||
// .background(viewModel.canVerifyPasswordlessCode ? Color.blue : Color.gray)
|
||||
// .cornerRadius(12)
|
||||
// .disabled(!viewModel.canVerifyPasswordlessCode)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(NSLocalizedString("Не получили код?", comment: ""))
|
||||
@ -609,14 +585,14 @@ private struct PasswordlessVerifyView: View {
|
||||
|
||||
Divider()
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
viewModel.backToPasswordlessRequest()
|
||||
}
|
||||
} label: {
|
||||
Text(NSLocalizedString("Изменить способ входа", comment: ""))
|
||||
.frame(maxWidth: .infinity)
|
||||
}
|
||||
// Button {
|
||||
// withAnimation {
|
||||
// viewModel.backToPasswordlessRequest()
|
||||
// }
|
||||
// } label: {
|
||||
// Text(NSLocalizedString("Изменить способ входа", comment: ""))
|
||||
// .frame(maxWidth: .infinity)
|
||||
// }
|
||||
|
||||
Button {
|
||||
withAnimation {
|
||||
@ -866,7 +842,7 @@ private struct ForgotPasswordInfoView: View {
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Button(action: onUseCode) {
|
||||
Text(NSLocalizedString("Перейти к входу по коду", comment: ""))
|
||||
Text(NSLocalizedString("Войти", comment: ""))
|
||||
.foregroundColor(.white)
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding()
|
||||
|
||||
@ -11,7 +11,6 @@ struct RegistrationView: View {
|
||||
@ObservedObject var viewModel: LoginViewModel
|
||||
let onShowModePrompt: (() -> Void)?
|
||||
|
||||
@State private var username: String = ""
|
||||
@State private var password: String = ""
|
||||
@State private var confirmPassword: String = ""
|
||||
@State private var inviteCode: String = ""
|
||||
@ -32,7 +31,7 @@ struct RegistrationView: View {
|
||||
|
||||
private var isUsernameValid: Bool {
|
||||
let pattern = "^[A-Za-z0-9_]{3,32}$"
|
||||
return username.range(of: pattern, options: .regularExpression) != nil
|
||||
return viewModel.passwordlessLogin.range(of: pattern, options: .regularExpression) != nil
|
||||
}
|
||||
|
||||
private var isPasswordValid: Bool {
|
||||
@ -78,21 +77,16 @@ struct RegistrationView: View {
|
||||
HStack(spacing: 8) {
|
||||
Text("@")
|
||||
.foregroundColor(.secondary)
|
||||
TextField(NSLocalizedString("Введите логин", comment: "Логин"), text: $username)
|
||||
TextField(NSLocalizedString("Введите логин", comment: "Логин"), text: $viewModel.passwordlessLogin)
|
||||
.autocapitalization(.none)
|
||||
.disableAutocorrection(true)
|
||||
.focused($focusedField, equals: .username)
|
||||
.onChange(of: username) { newValue in
|
||||
if newValue.count > 32 {
|
||||
username = String(newValue.prefix(32))
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(12)
|
||||
|
||||
if !isUsernameValid && !username.isEmpty {
|
||||
if !isUsernameValid && !viewModel.passwordlessLogin.isEmpty {
|
||||
Text(NSLocalizedString("Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)", comment: ""))
|
||||
.foregroundColor(.red)
|
||||
.font(.caption)
|
||||
@ -207,7 +201,9 @@ struct RegistrationView: View {
|
||||
private func registerUser() {
|
||||
isLoading = true
|
||||
errorMessage = ""
|
||||
viewModel.registerUser(username: username, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
|
||||
let trimmedLogin = viewModel.passwordlessLogin.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
viewModel.passwordlessLogin = trimmedLogin
|
||||
viewModel.registerUser(username: trimmedLogin, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
|
||||
isLoading = false
|
||||
if success {
|
||||
viewModel.hasAcceptedTerms = false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user