Compare commits
No commits in common. "52cf7e3b1c2406d6cb4c9a2a775be274c39e2270" and "140e82e122bbb9c7dffd71a44515fcc3ead21a29" have entirely different histories.
52cf7e3b1c
...
140e82e122
@ -18,7 +18,11 @@ class LoginViewModel: ObservableObject {
|
||||
@Published var isLoggedIn: Bool = false
|
||||
@Published var socketState: SocketService.ConnectionState
|
||||
@Published var chatLoadingState: ChatLoadingState = .idle
|
||||
@Published var hasAcceptedTerms: Bool = false
|
||||
@Published var hasAcceptedTerms: Bool {
|
||||
didSet {
|
||||
UserDefaults.standard.set(hasAcceptedTerms, forKey: DefaultsKeys.termsAccepted)
|
||||
}
|
||||
}
|
||||
@Published var isLoadingTerms: Bool = false
|
||||
@Published var termsContent: String = ""
|
||||
@Published var termsErrorMessage: String?
|
||||
@ -35,10 +39,12 @@ class LoginViewModel: ObservableObject {
|
||||
private enum DefaultsKeys {
|
||||
static let currentUser = "currentUser"
|
||||
static let userId = "userId"
|
||||
static let termsAccepted = "termsAccepted"
|
||||
}
|
||||
|
||||
init() {
|
||||
socketState = socketService.currentConnectionState
|
||||
hasAcceptedTerms = UserDefaults.standard.bool(forKey: DefaultsKeys.termsAccepted)
|
||||
observeSocketState()
|
||||
observeChatsReload()
|
||||
// loadStoredUser()
|
||||
|
||||
@ -16,7 +16,6 @@ struct LoginView: View {
|
||||
@State private var isShowingRegistration = false
|
||||
@State private var showLegacySupportNotice = false
|
||||
@State private var isShowingTerms = false
|
||||
@State private var hasResetTermsOnAppear = false
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
private enum Field: Hashable {
|
||||
@ -113,7 +112,7 @@ struct LoginView: View {
|
||||
.font(.caption)
|
||||
}
|
||||
|
||||
TermsAgreementCard(
|
||||
TermsAgreementView(
|
||||
isAccepted: $viewModel.hasAcceptedTerms,
|
||||
openTerms: {
|
||||
viewModel.loadTermsIfNeeded()
|
||||
@ -148,7 +147,6 @@ struct LoginView: View {
|
||||
// Кнопка регистрации
|
||||
Button(action: {
|
||||
isShowingRegistration = true
|
||||
viewModel.hasAcceptedTerms = false
|
||||
}) {
|
||||
Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
||||
.foregroundColor(.blue)
|
||||
@ -170,10 +168,6 @@ struct LoginView: View {
|
||||
)
|
||||
}
|
||||
.onAppear {
|
||||
if !hasResetTermsOnAppear {
|
||||
viewModel.hasAcceptedTerms = false
|
||||
hasResetTermsOnAppear = true
|
||||
}
|
||||
if shouldShowLegacySupportNotice {
|
||||
showLegacySupportNotice = true
|
||||
}
|
||||
@ -278,6 +272,106 @@ struct LoginView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private struct TermsAgreementView: View {
|
||||
@Binding var isAccepted: Bool
|
||||
var openTerms: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Button {
|
||||
isAccepted.toggle()
|
||||
} label: {
|
||||
Image(systemName: isAccepted ? "checkmark.square.fill" : "square")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundColor(isAccepted ? .blue : .secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(NSLocalizedString("Согласиться с правилами", comment: ""))
|
||||
.accessibilityValue(isAccepted ? NSLocalizedString("Включено", comment: "") : NSLocalizedString("Выключено", comment: ""))
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(NSLocalizedString("Я ознакомился и принимаю правила сервиса", comment: ""))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Button(action: openTerms) {
|
||||
HStack(spacing: 4) {
|
||||
Text(NSLocalizedString("Открыть правила", comment: ""))
|
||||
Image(systemName: "arrow.up.right")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.font(.footnote)
|
||||
.foregroundColor(.blue)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(16)
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(14)
|
||||
}
|
||||
}
|
||||
|
||||
private struct TermsFullScreenView: View {
|
||||
@Binding var isPresented: Bool
|
||||
var title: String
|
||||
var content: String
|
||||
var isLoading: Bool
|
||||
var errorMessage: String?
|
||||
var onRetry: () -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
} else if let errorMessage {
|
||||
VStack(spacing: 16) {
|
||||
Text(errorMessage)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
Button(action: onRetry) {
|
||||
Text(NSLocalizedString("Повторить", comment: ""))
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if let attributed = try? AttributedString(markdown: content) {
|
||||
Text(attributed)
|
||||
} else {
|
||||
Text(content)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(.systemBackground))
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(action: { isPresented = false }) {
|
||||
Text(NSLocalizedString("Закрыть", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
private var selectedThemeOption: ThemeOption {
|
||||
ThemeOption.option(for: themeManager.theme)
|
||||
}
|
||||
@ -310,6 +404,7 @@ struct LoginView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
let viewModel = LoginViewModel()
|
||||
viewModel.isLoading = false // чтобы убрать спиннер
|
||||
viewModel.hasAcceptedTerms = true
|
||||
return LoginView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@ struct RegistrationView: View {
|
||||
@State private var isLoading: Bool = false
|
||||
@State private var showError: Bool = false
|
||||
@State private var errorMessage: String = ""
|
||||
@State private var isShowingTerms: Bool = false
|
||||
|
||||
@FocusState private var focusedField: Field?
|
||||
|
||||
@ -45,7 +44,7 @@ struct RegistrationView: View {
|
||||
}
|
||||
|
||||
private var isFormValid: Bool {
|
||||
isUsernameValid && isPasswordValid && isConfirmPasswordValid && viewModel.hasAcceptedTerms
|
||||
isUsernameValid && isPasswordValid && isConfirmPasswordValid
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
@ -147,14 +146,6 @@ struct RegistrationView: View {
|
||||
.focused($focusedField, equals: .invite)
|
||||
}
|
||||
|
||||
TermsAgreementCard(
|
||||
isAccepted: $viewModel.hasAcceptedTerms,
|
||||
openTerms: {
|
||||
viewModel.loadTermsIfNeeded()
|
||||
isShowingTerms = true
|
||||
}
|
||||
)
|
||||
|
||||
Button(action: registerUser) {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
@ -193,23 +184,6 @@ struct RegistrationView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
.fullScreenCover(isPresented: $isShowingTerms) {
|
||||
TermsFullScreenView(
|
||||
isPresented: $isShowingTerms,
|
||||
title: NSLocalizedString("Правила сервиса", comment: ""),
|
||||
content: viewModel.termsContent,
|
||||
isLoading: viewModel.isLoadingTerms,
|
||||
errorMessage: viewModel.termsErrorMessage,
|
||||
onRetry: {
|
||||
viewModel.reloadTerms()
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
if viewModel.termsContent.isEmpty {
|
||||
viewModel.loadTermsIfNeeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func registerUser() {
|
||||
@ -228,7 +202,6 @@ struct RegistrationView: View {
|
||||
|
||||
private func dismissSheet() {
|
||||
focusedField = nil
|
||||
viewModel.hasAcceptedTerms = false
|
||||
isPresented = false
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
}
|
||||
|
||||
@ -1,102 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct TermsAgreementCard: View {
|
||||
@Binding var isAccepted: Bool
|
||||
var openTerms: () -> Void
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
Button {
|
||||
isAccepted.toggle()
|
||||
} label: {
|
||||
Image(systemName: isAccepted ? "checkmark.square.fill" : "square")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundColor(isAccepted ? .blue : .secondary)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel(NSLocalizedString("Согласиться с правилами", comment: ""))
|
||||
.accessibilityValue(isAccepted ? NSLocalizedString("Включено", comment: "") : NSLocalizedString("Выключено", comment: ""))
|
||||
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text(NSLocalizedString("Я ознакомился и принимаю правила сервиса", comment: ""))
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Button(action: openTerms) {
|
||||
HStack(spacing: 4) {
|
||||
Text(NSLocalizedString("Открыть правила", comment: ""))
|
||||
Image(systemName: "arrow.up.right")
|
||||
.font(.caption)
|
||||
}
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.font(.footnote)
|
||||
.foregroundColor(.blue)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding(16)
|
||||
.background(Color(.secondarySystemBackground))
|
||||
.cornerRadius(14)
|
||||
}
|
||||
}
|
||||
|
||||
struct TermsFullScreenView: View {
|
||||
@Binding var isPresented: Bool
|
||||
var title: String
|
||||
var content: String
|
||||
var isLoading: Bool
|
||||
var errorMessage: String?
|
||||
var onRetry: () -> Void
|
||||
|
||||
var body: some View {
|
||||
NavigationView {
|
||||
Group {
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
} else if let errorMessage {
|
||||
VStack(spacing: 16) {
|
||||
Text(errorMessage)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
Button(action: onRetry) {
|
||||
Text(NSLocalizedString("Повторить", comment: ""))
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 12)
|
||||
.background(Color.blue)
|
||||
.foregroundColor(.white)
|
||||
.cornerRadius(12)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if let attributed = try? AttributedString(markdown: content) {
|
||||
Text(attributed)
|
||||
} else {
|
||||
Text(content)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color(.systemBackground))
|
||||
.navigationTitle(title)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(action: { isPresented = false }) {
|
||||
Text(NSLocalizedString("Закрыть", comment: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationViewStyle(StackNavigationViewStyle())
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user