edit register
This commit is contained in:
parent
b8ffca967b
commit
50916b732a
@ -1036,6 +1036,9 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Назад к входу" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Напишите нам через форму обратной связи в разделе \"Поддержка\"." : {
|
"Напишите нам через форму обратной связи в разделе \"Поддержка\"." : {
|
||||||
"comment" : "FAQ answer: support"
|
"comment" : "FAQ answer: support"
|
||||||
@ -1676,7 +1679,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Пароли не совпадают" : {
|
"Пароли не совпадают" : {
|
||||||
"comment" : "Заголовок ошибки несовпадения паролей\nПароли не совпадают",
|
"comment" : "Заголовок ошибки несовпадения паролей",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"en" : {
|
"en" : {
|
||||||
"stringUnit" : {
|
"stringUnit" : {
|
||||||
@ -1815,6 +1818,9 @@
|
|||||||
},
|
},
|
||||||
"Подключение" : {
|
"Подключение" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Подтвердите пароль" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Подтвердить" : {
|
"Подтвердить" : {
|
||||||
"comment" : "Кнопка подтверждения кода 2FA"
|
"comment" : "Кнопка подтверждения кода 2FA"
|
||||||
@ -2441,6 +2447,9 @@
|
|||||||
},
|
},
|
||||||
"Согласиться с правилами" : {
|
"Согласиться с правилами" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Создайте логин и пароль. При желании добавьте инвайт." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Создать новые коды" : {
|
"Создать новые коды" : {
|
||||||
"comment" : "Кнопка генерации резервных кодов"
|
"comment" : "Кнопка генерации резервных кодов"
|
||||||
|
|||||||
@ -48,6 +48,7 @@ class LoginViewModel: ObservableObject {
|
|||||||
case passwordlessRequest
|
case passwordlessRequest
|
||||||
case passwordlessVerify
|
case passwordlessVerify
|
||||||
case password
|
case password
|
||||||
|
case registration
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ChatLoadingState: Equatable {
|
enum ChatLoadingState: Equatable {
|
||||||
@ -230,6 +231,10 @@ class LoginViewModel: ObservableObject {
|
|||||||
loginFlowStep = .passwordlessRequest
|
loginFlowStep = .passwordlessRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func showRegistration() {
|
||||||
|
loginFlowStep = .registration
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func registerUser(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
|
func registerUser(username: String, password: String, invite: String?, completion: @escaping (Bool, String?) -> Void) {
|
||||||
authService.register(username: username, password: password, invite: invite) { [weak self] success, message in
|
authService.register(username: username, password: password, invite: invite) { [weak self] success, message in
|
||||||
|
|||||||
67
yobble/Views/Login/LoginTopBar.swift
Normal file
67
yobble/Views/Login/LoginTopBar.swift
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct LoginTopBar: View {
|
||||||
|
let openLanguageSettings: () -> Void
|
||||||
|
@EnvironmentObject private var themeManager: ThemeManager
|
||||||
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
|
private let themeOptions = ThemeOption.ordered
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
Button(action: openLanguageSettings) {
|
||||||
|
Text("🌍")
|
||||||
|
.padding(8)
|
||||||
|
}
|
||||||
|
Spacer()
|
||||||
|
Menu {
|
||||||
|
ForEach(themeOptions) { option in
|
||||||
|
Button(action: { selectTheme(option) }) {
|
||||||
|
themeMenuContent(for: option)
|
||||||
|
.opacity(option.isEnabled ? 1.0 : 0.5)
|
||||||
|
}
|
||||||
|
.disabled(!option.isEnabled)
|
||||||
|
}
|
||||||
|
} label: {
|
||||||
|
Image(systemName: themeIconName)
|
||||||
|
.padding(8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var selectedThemeOption: ThemeOption {
|
||||||
|
ThemeOption.option(for: themeManager.theme)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var themeIconName: String {
|
||||||
|
switch themeManager.theme {
|
||||||
|
case .system:
|
||||||
|
return colorScheme == .dark ? "moon.fill" : "sun.max.fill"
|
||||||
|
case .light:
|
||||||
|
return "sun.max.fill"
|
||||||
|
case .oledDark:
|
||||||
|
return "moon.fill"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func themeMenuContent(for option: ThemeOption) -> some View {
|
||||||
|
let isSelected = option == selectedThemeOption
|
||||||
|
|
||||||
|
return HStack(spacing: 8) {
|
||||||
|
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
||||||
|
.foregroundColor(isSelected ? .accentColor : .secondary)
|
||||||
|
VStack(alignment: .leading, spacing: 2) {
|
||||||
|
Text(option.title)
|
||||||
|
if let note = option.note {
|
||||||
|
Text(note)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundColor(.secondary)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func selectTheme(_ option: ThemeOption) {
|
||||||
|
guard let mappedTheme = option.mappedTheme else { return }
|
||||||
|
themeManager.setTheme(mappedTheme)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -54,6 +54,9 @@ struct LoginView: View {
|
|||||||
case .password:
|
case .password:
|
||||||
PasswordLoginView(viewModel: viewModel)
|
PasswordLoginView(viewModel: viewModel)
|
||||||
.transition(.opacity)
|
.transition(.opacity)
|
||||||
|
case .registration:
|
||||||
|
RegistrationView(viewModel: viewModel)
|
||||||
|
.transition(.move(edge: .bottom).combined(with: .opacity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +80,6 @@ struct PasswordLoginView: View {
|
|||||||
private let themeOptions = ThemeOption.ordered
|
private let themeOptions = ThemeOption.ordered
|
||||||
@AppStorage("messengerModeEnabled") private var isMessengerModeEnabled: Bool = false
|
@AppStorage("messengerModeEnabled") private var isMessengerModeEnabled: Bool = false
|
||||||
|
|
||||||
@State private var isShowingRegistration = false
|
|
||||||
@State private var showLegacySupportNotice = false
|
@State private var showLegacySupportNotice = false
|
||||||
@State private var isShowingTerms = false
|
@State private var isShowingTerms = false
|
||||||
@State private var hasResetTermsOnAppear = false
|
@State private var hasResetTermsOnAppear = false
|
||||||
@ -197,17 +199,16 @@ struct PasswordLoginView: View {
|
|||||||
.disabled(!isLoginButtonEnabled)
|
.disabled(!isLoginButtonEnabled)
|
||||||
|
|
||||||
Button(action: {
|
Button(action: {
|
||||||
isShowingRegistration = true
|
|
||||||
viewModel.hasAcceptedTerms = false
|
viewModel.hasAcceptedTerms = false
|
||||||
|
withAnimation {
|
||||||
|
viewModel.showRegistration()
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
}
|
}
|
||||||
.padding(.top, 4)
|
.padding(.top, 4)
|
||||||
.sheet(isPresented: $isShowingRegistration) {
|
|
||||||
RegistrationView(viewModel: viewModel, isPresented: $isShowingRegistration)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(minLength: 0)
|
Spacer(minLength: 0)
|
||||||
}
|
}
|
||||||
@ -637,72 +638,6 @@ private struct OTPInputView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private struct LoginTopBar: View {
|
|
||||||
let openLanguageSettings: () -> Void
|
|
||||||
@EnvironmentObject private var themeManager: ThemeManager
|
|
||||||
@Environment(\.colorScheme) private var colorScheme
|
|
||||||
private let themeOptions = ThemeOption.ordered
|
|
||||||
|
|
||||||
var body: some View {
|
|
||||||
HStack {
|
|
||||||
Button(action: openLanguageSettings) {
|
|
||||||
Text("🌍")
|
|
||||||
.padding(8)
|
|
||||||
}
|
|
||||||
Spacer()
|
|
||||||
Menu {
|
|
||||||
ForEach(themeOptions) { option in
|
|
||||||
Button(action: { selectTheme(option) }) {
|
|
||||||
themeMenuContent(for: option)
|
|
||||||
.opacity(option.isEnabled ? 1.0 : 0.5)
|
|
||||||
}
|
|
||||||
.disabled(!option.isEnabled)
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: themeIconName)
|
|
||||||
.padding(8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var selectedThemeOption: ThemeOption {
|
|
||||||
ThemeOption.option(for: themeManager.theme)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var themeIconName: String {
|
|
||||||
switch themeManager.theme {
|
|
||||||
case .system:
|
|
||||||
return colorScheme == .dark ? "moon.fill" : "sun.max.fill"
|
|
||||||
case .light:
|
|
||||||
return "sun.max.fill"
|
|
||||||
case .oledDark:
|
|
||||||
return "moon.fill"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func themeMenuContent(for option: ThemeOption) -> some View {
|
|
||||||
let isSelected = option == selectedThemeOption
|
|
||||||
|
|
||||||
return HStack(spacing: 8) {
|
|
||||||
Image(systemName: isSelected ? "checkmark.circle.fill" : "circle")
|
|
||||||
.foregroundColor(isSelected ? .accentColor : .secondary)
|
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
|
||||||
Text(option.title)
|
|
||||||
if let note = option.note {
|
|
||||||
Text(note)
|
|
||||||
.font(.caption)
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func selectTheme(_ option: ThemeOption) {
|
|
||||||
guard let mappedTheme = option.mappedTheme else { return }
|
|
||||||
themeManager.setTheme(mappedTheme)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct MessengerModePrompt: View {
|
private struct MessengerModePrompt: View {
|
||||||
@Binding var selection: Bool
|
@Binding var selection: Bool
|
||||||
let onAccept: () -> Void
|
let onAccept: () -> Void
|
||||||
@ -816,6 +751,7 @@ struct LoginView_Previews: PreviewProvider {
|
|||||||
preview(step: .passwordlessRequest)
|
preview(step: .passwordlessRequest)
|
||||||
preview(step: .passwordlessVerify)
|
preview(step: .passwordlessVerify)
|
||||||
preview(step: .password)
|
preview(step: .password)
|
||||||
|
preview(step: .registration)
|
||||||
}
|
}
|
||||||
.environmentObject(ThemeManager())
|
.environmentObject(ThemeManager())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,8 +9,6 @@ import SwiftUI
|
|||||||
|
|
||||||
struct RegistrationView: View {
|
struct RegistrationView: View {
|
||||||
@ObservedObject var viewModel: LoginViewModel
|
@ObservedObject var viewModel: LoginViewModel
|
||||||
@Binding var isPresented: Bool
|
|
||||||
@Environment(\.presentationMode) private var presentationMode
|
|
||||||
|
|
||||||
@State private var username: String = ""
|
@State private var username: String = ""
|
||||||
@State private var password: String = ""
|
@State private var password: String = ""
|
||||||
@ -49,149 +47,133 @@ struct RegistrationView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
ScrollView(showsIndicators: false) {
|
||||||
ScrollView {
|
VStack(alignment: .leading, spacing: 24) {
|
||||||
ZStack(alignment: .top) {
|
LoginTopBar(openLanguageSettings: openLanguageSettings)
|
||||||
Color.clear
|
|
||||||
.contentShape(Rectangle())
|
|
||||||
.onTapGesture { focusedField = nil }
|
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
Button(action: goBack) {
|
||||||
Group {
|
HStack(spacing: 6) {
|
||||||
HStack {
|
Image(systemName: "arrow.left")
|
||||||
TextField(NSLocalizedString("Логин", comment: "Логин"), text: $username)
|
Text(NSLocalizedString("Назад к входу", comment: ""))
|
||||||
.autocapitalization(.none)
|
}
|
||||||
.disableAutocorrection(true)
|
.font(.footnote)
|
||||||
.focused($focusedField, equals: .username)
|
.foregroundColor(.blue)
|
||||||
Spacer()
|
}
|
||||||
if !username.isEmpty {
|
|
||||||
Image(systemName: isUsernameValid ? "checkmark.circle" : "xmark.circle")
|
VStack(alignment: .leading, spacing: 8) {
|
||||||
.foregroundColor(isUsernameValid ? .green : .red)
|
Text(NSLocalizedString("Регистрация", comment: "Регистрация"))
|
||||||
}
|
.font(.largeTitle).bold()
|
||||||
}
|
Text(NSLocalizedString("Создайте логин и пароль. При желании добавьте инвайт.", comment: ""))
|
||||||
.padding()
|
.foregroundColor(.secondary)
|
||||||
.background(Color(.secondarySystemBackground))
|
}
|
||||||
.cornerRadius(8)
|
|
||||||
|
Group {
|
||||||
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
|
TextField(NSLocalizedString("Логин", comment: "Логин"), text: $username)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.disableAutocorrection(true)
|
.disableAutocorrection(true)
|
||||||
|
.focused($focusedField, equals: .username)
|
||||||
|
.padding()
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
.cornerRadius(12)
|
||||||
.onChange(of: username) { newValue in
|
.onChange(of: username) { newValue in
|
||||||
if newValue.count > 32 {
|
if newValue.count > 32 {
|
||||||
username = String(newValue.prefix(32))
|
username = String(newValue.prefix(32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isUsernameValid && !username.isEmpty {
|
if !isUsernameValid && !username.isEmpty {
|
||||||
Text(NSLocalizedString("Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)", comment: "Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)"))
|
Text(NSLocalizedString("Логин должен быть от 3 до 32 символов (английские буквы, цифры, _)", comment: ""))
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
SecureField(NSLocalizedString("Пароль", comment: "Пароль"), text: $password)
|
SecureField(NSLocalizedString("Пароль", comment: "Пароль"), text: $password)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.focused($focusedField, equals: .password)
|
.focused($focusedField, equals: .password)
|
||||||
Spacer()
|
|
||||||
if !password.isEmpty {
|
|
||||||
Image(systemName: isPasswordValid ? "checkmark.circle" : "xmark.circle")
|
|
||||||
.foregroundColor(isPasswordValid ? .green : .red)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color(.secondarySystemBackground))
|
.background(Color(.secondarySystemBackground))
|
||||||
.cornerRadius(8)
|
.cornerRadius(12)
|
||||||
.autocapitalization(.none)
|
|
||||||
.onChange(of: password) { newValue in
|
.onChange(of: password) { newValue in
|
||||||
if newValue.count > 128 {
|
if newValue.count > 128 {
|
||||||
password = String(newValue.prefix(128))
|
password = String(newValue.prefix(128))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isPasswordValid && !password.isEmpty {
|
if !isPasswordValid && !password.isEmpty {
|
||||||
Text(NSLocalizedString("Пароль должен быть от 8 до 128 символов", comment: "Пароль должен быть от 6 до 32 символов"))
|
Text(NSLocalizedString("Пароль должен быть от 8 до 128 символов", comment: ""))
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HStack {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
SecureField(NSLocalizedString("Подтверждение пароля", comment: "Подтверждение пароля"), text: $confirmPassword)
|
SecureField(NSLocalizedString("Подтвердите пароль", comment: ""), text: $confirmPassword)
|
||||||
.autocapitalization(.none)
|
.autocapitalization(.none)
|
||||||
.focused($focusedField, equals: .confirmPassword)
|
.focused($focusedField, equals: .confirmPassword)
|
||||||
Spacer()
|
|
||||||
if !confirmPassword.isEmpty {
|
|
||||||
Image(systemName: isConfirmPasswordValid ? "checkmark.circle" : "xmark.circle")
|
|
||||||
.foregroundColor(isConfirmPasswordValid ? .green : .red)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding()
|
.padding()
|
||||||
.background(Color(.secondarySystemBackground))
|
.background(Color(.secondarySystemBackground))
|
||||||
.cornerRadius(8)
|
.cornerRadius(12)
|
||||||
.autocapitalization(.none)
|
|
||||||
.onChange(of: confirmPassword) { newValue in
|
.onChange(of: confirmPassword) { newValue in
|
||||||
if newValue.count > 32 {
|
if newValue.count > 32 {
|
||||||
confirmPassword = String(newValue.prefix(32))
|
confirmPassword = String(newValue.prefix(32))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !isConfirmPasswordValid && !confirmPassword.isEmpty {
|
if !isConfirmPasswordValid && !confirmPassword.isEmpty {
|
||||||
Text(NSLocalizedString("Пароли не совпадают", comment: "Пароли не совпадают"))
|
Text(NSLocalizedString("Пароли не совпадают", comment: ""))
|
||||||
.foregroundColor(.red)
|
.foregroundColor(.red)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
|
||||||
|
|
||||||
TextField(NSLocalizedString("Инвайт-код (необязательно)", comment: "Инвайт-код"), text: $inviteCode)
|
|
||||||
.padding()
|
|
||||||
.background(Color(.secondarySystemBackground))
|
|
||||||
.cornerRadius(8)
|
|
||||||
.autocapitalization(.none)
|
|
||||||
.disableAutocorrection(true)
|
|
||||||
.focused($focusedField, equals: .invite)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TermsAgreementCard(
|
|
||||||
isAccepted: $viewModel.hasAcceptedTerms,
|
|
||||||
openTerms: {
|
|
||||||
viewModel.loadTermsIfNeeded()
|
|
||||||
isShowingTerms = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Button(action: registerUser) {
|
|
||||||
if isLoading {
|
|
||||||
ProgressView()
|
|
||||||
.padding()
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.background(Color.gray.opacity(0.6))
|
|
||||||
.cornerRadius(8)
|
|
||||||
} else {
|
|
||||||
Text(NSLocalizedString("Зарегистрироваться", comment: "Зарегистрироваться"))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.padding()
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.background(isFormValid ? Color.blue : Color.gray.opacity(0.6))
|
|
||||||
.cornerRadius(8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.disabled(!isFormValid)
|
|
||||||
.padding(.bottom)
|
|
||||||
}
|
}
|
||||||
.padding()
|
|
||||||
|
TextField(NSLocalizedString("Инвайт-код (необязательно)", comment: ""), text: $inviteCode)
|
||||||
|
.autocapitalization(.none)
|
||||||
|
.disableAutocorrection(true)
|
||||||
|
.focused($focusedField, equals: .invite)
|
||||||
|
.padding()
|
||||||
|
.background(Color(.secondarySystemBackground))
|
||||||
|
.cornerRadius(12)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.navigationTitle(Text(NSLocalizedString("Регистрация", comment: "Регистрация")))
|
TermsAgreementCard(
|
||||||
.toolbar {
|
isAccepted: $viewModel.hasAcceptedTerms,
|
||||||
ToolbarItem(placement: .navigationBarTrailing) {
|
openTerms: {
|
||||||
Button(action: dismissSheet) {
|
viewModel.loadTermsIfNeeded()
|
||||||
Text(NSLocalizedString("Закрыть", comment: "Закрыть"))
|
isShowingTerms = true
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
.alert(isPresented: $showError) {
|
|
||||||
Alert(
|
|
||||||
title: Text(NSLocalizedString("Ошибка регистрация", comment: "Ошибка")),
|
|
||||||
message: Text(errorMessage),
|
|
||||||
dismissButton: .default(Text(NSLocalizedString("OK", comment: "")))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Button(action: registerUser) {
|
||||||
|
if isLoading {
|
||||||
|
ProgressView()
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
} else {
|
||||||
|
Text(NSLocalizedString("Зарегистрироваться", comment: "Зарегистрироваться"))
|
||||||
|
.foregroundColor(.white)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.background(isFormValid ? Color.blue : Color.gray.opacity(0.6))
|
||||||
|
.cornerRadius(12)
|
||||||
|
.disabled(!isFormValid)
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 32)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 24)
|
||||||
|
.background(Color(.systemBackground).ignoresSafeArea())
|
||||||
|
.contentShape(Rectangle())
|
||||||
|
.onTapGesture { focusedField = nil }
|
||||||
|
.alert(isPresented: $showError) {
|
||||||
|
Alert(
|
||||||
|
title: Text(NSLocalizedString("Ошибка регистрация", comment: "Ошибка")),
|
||||||
|
message: Text(errorMessage),
|
||||||
|
dismissButton: .default(Text(NSLocalizedString("OK", comment: "")))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
.fullScreenCover(isPresented: $isShowingTerms) {
|
.fullScreenCover(isPresented: $isShowingTerms) {
|
||||||
TermsFullScreenView(
|
TermsFullScreenView(
|
||||||
@ -218,7 +200,7 @@ struct RegistrationView: View {
|
|||||||
viewModel.registerUser(username: username, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
|
viewModel.registerUser(username: username, password: password, invite: inviteCode.isEmpty ? nil : inviteCode) { success, message in
|
||||||
isLoading = false
|
isLoading = false
|
||||||
if success {
|
if success {
|
||||||
dismissSheet()
|
viewModel.hasAcceptedTerms = false
|
||||||
} else {
|
} else {
|
||||||
errorMessage = message ?? NSLocalizedString("Неизвестная ошибка.", comment: "")
|
errorMessage = message ?? NSLocalizedString("Неизвестная ошибка.", comment: "")
|
||||||
showError = true
|
showError = true
|
||||||
@ -226,11 +208,17 @@ struct RegistrationView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func dismissSheet() {
|
private func goBack() {
|
||||||
focusedField = nil
|
focusedField = nil
|
||||||
viewModel.hasAcceptedTerms = false
|
viewModel.hasAcceptedTerms = false
|
||||||
isPresented = false
|
withAnimation {
|
||||||
presentationMode.wrappedValue.dismiss()
|
viewModel.showPasswordLogin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func openLanguageSettings() {
|
||||||
|
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||||
|
UIApplication.shared.open(url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,6 +227,6 @@ struct RegistrationView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let viewModel = LoginViewModel()
|
let viewModel = LoginViewModel()
|
||||||
viewModel.isLoading = false // чтобы убрать спиннер
|
viewModel.isLoading = false // чтобы убрать спиннер
|
||||||
return RegistrationView(viewModel: viewModel, isPresented: .constant(true))
|
return RegistrationView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user