[legal-api-links] Load Terms of Use and Privacy Policy from Atomenta API
Terms and Privacy screens now fetch the real PediFoods customer PDFs from the backend instead of showing static placeholder text. Also surfaces both links on the Profile screen below the version label, not just during registration.
This commit is contained in:
@@ -1,21 +1,116 @@
|
||||
import SwiftUI
|
||||
import WebKit
|
||||
|
||||
struct TermsOfUseView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
private struct RemotePDFView: View {
|
||||
let url: URL
|
||||
@Binding var isLoading: Bool
|
||||
@Binding var hasError: Bool
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
screenHeader
|
||||
Text("Termos de Uso")
|
||||
.font(AppTypography.heading1)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
Text("Conteúdo dos termos de uso...")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
RemotePDFRepresentable(url: url, isLoading: $isLoading, hasError: $hasError)
|
||||
}
|
||||
}
|
||||
|
||||
#if canImport(UIKit)
|
||||
private struct RemotePDFRepresentable: UIViewRepresentable {
|
||||
let url: URL
|
||||
@Binding var isLoading: Bool
|
||||
@Binding var hasError: Bool
|
||||
|
||||
func makeUIView(context: Context) -> WKWebView {
|
||||
let webView = WKWebView()
|
||||
webView.navigationDelegate = context.coordinator
|
||||
webView.load(URLRequest(url: url))
|
||||
return webView
|
||||
}
|
||||
|
||||
func updateUIView(_ uiView: WKWebView, context: Context) {}
|
||||
|
||||
func makeCoordinator() -> RemotePDFCoordinator {
|
||||
RemotePDFCoordinator(isLoading: $isLoading, hasError: $hasError)
|
||||
}
|
||||
}
|
||||
#elseif canImport(AppKit)
|
||||
private struct RemotePDFRepresentable: NSViewRepresentable {
|
||||
let url: URL
|
||||
@Binding var isLoading: Bool
|
||||
@Binding var hasError: Bool
|
||||
|
||||
func makeNSView(context: Context) -> WKWebView {
|
||||
let webView = WKWebView()
|
||||
webView.navigationDelegate = context.coordinator
|
||||
webView.load(URLRequest(url: url))
|
||||
return webView
|
||||
}
|
||||
|
||||
func updateNSView(_ nsView: WKWebView, context: Context) {}
|
||||
|
||||
func makeCoordinator() -> RemotePDFCoordinator {
|
||||
RemotePDFCoordinator(isLoading: $isLoading, hasError: $hasError)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private final class RemotePDFCoordinator: NSObject, WKNavigationDelegate {
|
||||
@Binding var isLoading: Bool
|
||||
@Binding var hasError: Bool
|
||||
|
||||
init(isLoading: Binding<Bool>, hasError: Binding<Bool>) {
|
||||
self._isLoading = isLoading
|
||||
self._hasError = hasError
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
|
||||
isLoading = true
|
||||
hasError = false
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
||||
isLoading = false
|
||||
hasError = true
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
||||
isLoading = false
|
||||
hasError = true
|
||||
}
|
||||
}
|
||||
|
||||
private struct LegalDocumentScreen: View {
|
||||
let title: String
|
||||
let headerTitle: String
|
||||
let document: LegalDocument
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
@State private var isLoading = true
|
||||
@State private var hasError = false
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 0) {
|
||||
screenHeader
|
||||
.padding(24)
|
||||
|
||||
ZStack {
|
||||
RemotePDFView(url: document.url, isLoading: $isLoading, hasError: $hasError)
|
||||
|
||||
if isLoading {
|
||||
ProgressView()
|
||||
}
|
||||
|
||||
if hasError {
|
||||
VStack(spacing: 8) {
|
||||
Text("Não foi possível carregar \(title.lowercased()).")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
||||
.appHiddenNavigationBar()
|
||||
@@ -24,7 +119,7 @@ struct TermsOfUseView: View {
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Termos de Uso")
|
||||
Text(headerTitle)
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
@@ -44,46 +139,14 @@ struct TermsOfUseView: View {
|
||||
}
|
||||
}
|
||||
|
||||
struct TermsOfUseView: View {
|
||||
var body: some View {
|
||||
LegalDocumentScreen(title: "os Termos de Uso", headerTitle: "Termos de Uso", document: .terms)
|
||||
}
|
||||
}
|
||||
|
||||
struct PrivacyPolicyView: View {
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
screenHeader
|
||||
Text("Política de Privacidade")
|
||||
.font(AppTypography.heading1)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
Text("Conteúdo da política de privacidade...")
|
||||
.font(AppTypography.body)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
}
|
||||
.padding(24)
|
||||
}
|
||||
.background((colorScheme == .dark ? Color.black : AppColors.backgroundLight).ignoresSafeArea())
|
||||
.appHiddenNavigationBar()
|
||||
.navigationBarBackButtonHidden(true)
|
||||
}
|
||||
|
||||
private var screenHeader: some View {
|
||||
ZStack {
|
||||
Text("Privacidade")
|
||||
.font(AppTypography.heading2)
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
HStack {
|
||||
Button(action: { dismiss() }) {
|
||||
Image(systemName: "chevron.left")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundStyle(AppColors.textPrimary)
|
||||
.frame(width: 52, height: 52)
|
||||
.background(AppColors.surface)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.06), radius: 8, y: 2)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
LegalDocumentScreen(title: "a Política de Privacidade", headerTitle: "Privacidade", document: .privacyPolicy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +90,27 @@ struct ProfileView: View {
|
||||
Text("Versão 1.0b")
|
||||
.font(.system(size: 16, weight: .medium))
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
.padding(.bottom, 12)
|
||||
|
||||
HStack(spacing: 6) {
|
||||
NavigationLink {
|
||||
TermsOfUseView()
|
||||
} label: {
|
||||
Text("Termos de Uso")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
|
||||
Text("·")
|
||||
|
||||
NavigationLink {
|
||||
PrivacyPolicyView()
|
||||
} label: {
|
||||
Text("Política de Privacidade")
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundStyle(AppColors.textMuted)
|
||||
.padding(.bottom, 12)
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea(edges: .top)
|
||||
|
||||
Reference in New Issue
Block a user