Compare commits
	
		
			3 Commits
		
	
	
		
			140e82e122
			...
			52cf7e3b1c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 52cf7e3b1c | |||
| 85fb780c96 | |||
| a28402136d | 
@ -18,11 +18,7 @@ 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 {
 | 
					    @Published var hasAcceptedTerms: Bool = false
 | 
				
			||||||
        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?
 | 
				
			||||||
@ -39,12 +35,10 @@ 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,6 +16,7 @@ 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 {
 | 
				
			||||||
@ -112,7 +113,7 @@ struct LoginView: View {
 | 
				
			|||||||
                        .font(.caption)
 | 
					                        .font(.caption)
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                TermsAgreementView(
 | 
					                TermsAgreementCard(
 | 
				
			||||||
                    isAccepted: $viewModel.hasAcceptedTerms,
 | 
					                    isAccepted: $viewModel.hasAcceptedTerms,
 | 
				
			||||||
                    openTerms: {
 | 
					                    openTerms: {
 | 
				
			||||||
                        viewModel.loadTermsIfNeeded()
 | 
					                        viewModel.loadTermsIfNeeded()
 | 
				
			||||||
@ -147,6 +148,7 @@ struct LoginView: View {
 | 
				
			|||||||
                // Кнопка регистрации
 | 
					                // Кнопка регистрации
 | 
				
			||||||
                Button(action: {
 | 
					                Button(action: {
 | 
				
			||||||
                    isShowingRegistration = true
 | 
					                    isShowingRegistration = true
 | 
				
			||||||
 | 
					                    viewModel.hasAcceptedTerms = false
 | 
				
			||||||
                }) {
 | 
					                }) {
 | 
				
			||||||
                    Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
 | 
					                    Text(NSLocalizedString("Нет аккаунта? Регистрация", comment: "Регистрация"))
 | 
				
			||||||
                        .foregroundColor(.blue)
 | 
					                        .foregroundColor(.blue)
 | 
				
			||||||
@ -168,6 +170,10 @@ struct LoginView: View {
 | 
				
			|||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            .onAppear {
 | 
					            .onAppear {
 | 
				
			||||||
 | 
					                if !hasResetTermsOnAppear {
 | 
				
			||||||
 | 
					                    viewModel.hasAcceptedTerms = false
 | 
				
			||||||
 | 
					                    hasResetTermsOnAppear = true
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                if shouldShowLegacySupportNotice {
 | 
					                if shouldShowLegacySupportNotice {
 | 
				
			||||||
                    showLegacySupportNotice = true
 | 
					                    showLegacySupportNotice = true
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@ -272,106 +278,6 @@ 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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -404,7 +310,6 @@ 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,6 +20,7 @@ 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?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -44,7 +45,7 @@ struct RegistrationView: View {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private var isFormValid: Bool {
 | 
					    private var isFormValid: Bool {
 | 
				
			||||||
        isUsernameValid && isPasswordValid && isConfirmPasswordValid
 | 
					        isUsernameValid && isPasswordValid && isConfirmPasswordValid && viewModel.hasAcceptedTerms
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    var body: some View {
 | 
					    var body: some View {
 | 
				
			||||||
@ -146,6 +147,14 @@ 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()
 | 
				
			||||||
@ -184,6 +193,23 @@ 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() {
 | 
				
			||||||
@ -202,6 +228,7 @@ 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()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										102
									
								
								yobble/Views/Login/TermsViews.swift
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								yobble/Views/Login/TermsViews.swift
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,102 @@
 | 
				
			|||||||
 | 
					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