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 isLoggedIn: Bool = false
|
||||||
@Published var socketState: SocketService.ConnectionState
|
@Published var socketState: SocketService.ConnectionState
|
||||||
@Published var chatLoadingState: ChatLoadingState = .idle
|
@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 isLoadingTerms: Bool = false
|
||||||
@Published var termsContent: String = ""
|
@Published var termsContent: String = ""
|
||||||
@Published var termsErrorMessage: String?
|
@Published var termsErrorMessage: String?
|
||||||
@ -35,10 +39,12 @@ class LoginViewModel: ObservableObject {
|
|||||||
private enum DefaultsKeys {
|
private enum DefaultsKeys {
|
||||||
static let currentUser = "currentUser"
|
static let currentUser = "currentUser"
|
||||||
static let userId = "userId"
|
static let userId = "userId"
|
||||||
|
static let termsAccepted = "termsAccepted"
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
socketState = socketService.currentConnectionState
|
socketState = socketService.currentConnectionState
|
||||||
|
hasAcceptedTerms = UserDefaults.standard.bool(forKey: DefaultsKeys.termsAccepted)
|
||||||
observeSocketState()
|
observeSocketState()
|
||||||
observeChatsReload()
|
observeChatsReload()
|
||||||
// loadStoredUser()
|
// loadStoredUser()
|
||||||
|
|||||||
@ -16,7 +16,6 @@ struct LoginView: View {
|
|||||||
@State private var isShowingRegistration = 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
|
|
||||||
@FocusState private var focusedField: Field?
|
@FocusState private var focusedField: Field?
|
||||||
|
|
||||||
private enum Field: Hashable {
|
private enum Field: Hashable {
|
||||||
@ -113,7 +112,7 @@ struct LoginView: View {
|
|||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
|
|
||||||
TermsAgreementCard(
|
TermsAgreementView(
|
||||||
isAccepted: $viewModel.hasAcceptedTerms,
|
isAccepted: $viewModel.hasAcceptedTerms,
|
||||||
openTerms: {
|
openTerms: {
|
||||||
viewModel.loadTermsIfNeeded()
|
viewModel.loadTermsIfNeeded()
|
||||||
@ -148,7 +147,6 @@ struct LoginView: View {
|
|||||||
// Кнопка регистрации
|
// Кнопка регистрации
|
||||||
Button(action: {
|
Button(action: {
|
||||||
isShowingRegistration = true
|
isShowingRegistration = true
|
||||||
viewModel.hasAcceptedTerms = false
|
|
||||||
}) {
|
}) {
|
||||||
Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
|
||||||
.foregroundColor(.blue)
|
.foregroundColor(.blue)
|
||||||
@ -170,10 +168,6 @@ struct LoginView: View {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
if !hasResetTermsOnAppear {
|
|
||||||
viewModel.hasAcceptedTerms = false
|
|
||||||
hasResetTermsOnAppear = true
|
|
||||||
}
|
|
||||||
if shouldShowLegacySupportNotice {
|
if shouldShowLegacySupportNotice {
|
||||||
showLegacySupportNotice = true
|
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 {
|
private var selectedThemeOption: ThemeOption {
|
||||||
ThemeOption.option(for: themeManager.theme)
|
ThemeOption.option(for: themeManager.theme)
|
||||||
}
|
}
|
||||||
@ -310,6 +404,7 @@ struct LoginView_Previews: PreviewProvider {
|
|||||||
static var previews: some View {
|
static var previews: some View {
|
||||||
let viewModel = LoginViewModel()
|
let viewModel = LoginViewModel()
|
||||||
viewModel.isLoading = false // чтобы убрать спиннер
|
viewModel.isLoading = false // чтобы убрать спиннер
|
||||||
|
viewModel.hasAcceptedTerms = true
|
||||||
return LoginView(viewModel: viewModel)
|
return LoginView(viewModel: viewModel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,6 @@ struct RegistrationView: View {
|
|||||||
@State private var isLoading: Bool = false
|
@State private var isLoading: Bool = false
|
||||||
@State private var showError: Bool = false
|
@State private var showError: Bool = false
|
||||||
@State private var errorMessage: String = ""
|
@State private var errorMessage: String = ""
|
||||||
@State private var isShowingTerms: Bool = false
|
|
||||||
|
|
||||||
@FocusState private var focusedField: Field?
|
@FocusState private var focusedField: Field?
|
||||||
|
|
||||||
@ -45,7 +44,7 @@ struct RegistrationView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private var isFormValid: Bool {
|
private var isFormValid: Bool {
|
||||||
isUsernameValid && isPasswordValid && isConfirmPasswordValid && viewModel.hasAcceptedTerms
|
isUsernameValid && isPasswordValid && isConfirmPasswordValid
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
@ -147,14 +146,6 @@ struct RegistrationView: View {
|
|||||||
.focused($focusedField, equals: .invite)
|
.focused($focusedField, equals: .invite)
|
||||||
}
|
}
|
||||||
|
|
||||||
TermsAgreementCard(
|
|
||||||
isAccepted: $viewModel.hasAcceptedTerms,
|
|
||||||
openTerms: {
|
|
||||||
viewModel.loadTermsIfNeeded()
|
|
||||||
isShowingTerms = true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
Button(action: registerUser) {
|
Button(action: registerUser) {
|
||||||
if isLoading {
|
if isLoading {
|
||||||
ProgressView()
|
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() {
|
private func registerUser() {
|
||||||
@ -228,7 +202,6 @@ struct RegistrationView: View {
|
|||||||
|
|
||||||
private func dismissSheet() {
|
private func dismissSheet() {
|
||||||
focusedField = nil
|
focusedField = nil
|
||||||
viewModel.hasAcceptedTerms = false
|
|
||||||
isPresented = false
|
isPresented = false
|
||||||
presentationMode.wrappedValue.dismiss()
|
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