WithdrawTOSView.swift (7190B)
1 /* 2 * This file is part of GNU Taler, ©2022-25 Taler Systems S.A. 3 * See LICENSE.md 4 */ 5 /** 6 * @author Marc Stibane 7 */ 8 import SwiftUI 9 import SymLog 10 import MarkdownUI 11 12 struct WithdrawTOSView: View { 13 private let symLog = SymLogV(0) 14 let stack: CallStack 15 let exchangeBaseUrl: String? 16 let viewID: Int // either VIEW_WITHDRAW_TOS or SHEET_WITHDRAW_TOS 17 let acceptAction: (() async -> Void)? 18 19 @Environment(\.dismiss) var dismiss // pop back once 20 @EnvironmentObject private var model: WalletModel 21 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 22 23 @State var exchangeTOS: ExchangeTermsOfService? 24 25 let navTitle = String(localized: "Terms of Service") 26 27 @MainActor 28 func loadToS(_ language: String) async { 29 if let exchangeBaseUrl { 30 let acceptedFormat: [String] = [MARKDOWN, PLAINTEXT] // MARKDOWN, HTML, PLAINTEXT 31 if let someTOS = try? await model.loadExchangeTermsOfService(exchangeBaseUrl, 32 acceptedFormat: acceptedFormat, 33 acceptLanguage: language) { 34 exchangeTOS = someTOS 35 } 36 } else { 37 // TODO: Yikes! No baseURL 38 } 39 } 40 41 @MainActor 42 func viewDidLoad() async { 43 if let exchangeTOS, let exchangeBaseUrl { 44 _ = try? await model.setExchangeTOSAccepted(exchangeBaseUrl) 45 if acceptAction != nil { 46 await acceptAction!() 47 } else { // just go back - caller will reload 48 dismiss() 49 } 50 } else { 51 // TODO: error 52 } 53 } 54 55 var body: some View { 56 let languageCode = Locale.preferredLanguageCode 57 // let languageName = Locale.current.localizedString(forLanguageCode: languageCode) 58 if let exchangeTOS, let exchangeBaseUrl { 59 Content(symLog: symLog, tos: exchangeTOS, myListStyle: $myListStyle, 60 language: languageCode, languageAction: loadToS) { 61 Task { await viewDidLoad() } 62 } 63 .navigationTitle(navTitle) 64 .refreshable { 65 try? await model.updateExchange(exchangeBaseUrl: exchangeBaseUrl, force: true) 66 await loadToS(languageCode) 67 } 68 .onAppear() { 69 if viewID > SHEET_WITHDRAWAL { 70 DebugViewC.shared.setSheetID(SHEET_WITHDRAW_TOS) 71 } else { 72 DebugViewC.shared.setViewID(VIEW_WITHDRAW_TOS, stack: stack.push()) 73 } 74 } 75 } else { 76 let fallback = String(localized: "No payment service", comment: "loading") 77 LoadingView(stack: stack.push(), scopeInfo: nil, 78 message: exchangeBaseUrl?.trimURL ?? fallback) 79 .task { 80 await loadToS(languageCode) 81 } 82 } 83 } 84 } 85 // MARK: - 86 extension WithdrawTOSView { 87 88 static func cleanupText(_ term0: String) -> String { 89 let term1 = term0.replacingOccurrences(of: "\n ", with: " ") // newline + 5 blanks 90 let term2 = term1.replacingOccurrences(of: "\n ", with: " ") // newline + 4 blanks 91 let term3 = term2.replacingOccurrences(of: "\n ", with: " ") // newline + 3 blanks 92 let term4 = term3.replacingOccurrences(of: "\n ", with: " ") // newline + 2 blanks 93 let term5 = term4.replacingOccurrences(of: "\n ", with: " ") // newline + 1 blank 94 let term6 = term5.replacingOccurrences(of: "\n", with: " ") // remove all other linebreaks 95 let term7 = term6.replacingOccurrences(of: " ====", with: "\n====") // add them back for underscoring 96 let term8 = term7.replacingOccurrences(of: " ----", with: "\n----") // special for "Highlights:" 97 let term9 = term8.replacingOccurrences(of: " ****", with: "\n****") // special for "Terms of Service:" 98 return term9 99 } 100 101 struct plaintextToSV: View { 102 let plaintext: String 103 104 var body: some View { 105 let components = plaintext.components(separatedBy: "\n\n") 106 ForEach (components, id: \.self) { term0 in 107 Section { 108 Text(cleanupText(term0)) 109 .talerFont(.footnote) 110 .foregroundColor(Color(UIColor.label)) 111 } 112 } // for 113 } 114 } 115 116 struct Content: View { 117 let symLog: SymLogV 118 var tos: ExchangeTermsOfService 119 @Binding var myListStyle: MyListStyle 120 let language: String 121 var languageAction: (String) async -> () 122 var acceptAction: () -> () 123 124 @State private var selectedLanguage = Locale.preferredLanguageCode 125 126 var body: some View { 127 let title = String(localized: "Language:", comment: "title of ToS language selection") 128 let list = List { 129 if tos.tosAvailableLanguages.count > 1 { 130 Picker(title, selection: $selectedLanguage) { 131 ForEach(tos.tosAvailableLanguages, id: \.self) { code in 132 let languageName = Locale.current.localizedString(forLanguageCode: code) 133 Text(languageName ?? code) 134 } 135 } 136 .talerFont(.title3) 137 .pickerStyle(.menu) 138 .onAppear() { 139 withAnimation { selectedLanguage = language } 140 } 141 .onChange(of: selectedLanguage) { selected in 142 Task { 143 await languageAction(selected) 144 } 145 } 146 } 147 if tos.contentType == MARKDOWN { 148 Section { 149 let content = MarkdownContent(tos.content) 150 Markdown(content) 151 } 152 } else { 153 let components = tos.content.components(separatedBy: "\n\n") 154 ForEach (components, id: \.self) { term0 in 155 Section { 156 Text(cleanupText(term0)) 157 .talerFont(.footnote) 158 .foregroundColor(Color(UIColor.label)) 159 } 160 } // for 161 } // plain text 162 }.listStyle(myListStyle.style).anyView 163 164 let currentEtag = tos.currentEtag 165 let showButton = tos.acceptedEtag == nil ? true 166 : tos.acceptedEtag! == tos.currentEtag ? false 167 : true 168 let button = Button(String(localized: "Accept Terms of Service", comment: "Button"), action: acceptAction) 169 .buttonStyle(TalerButtonStyle(type: .prominent)) 170 .padding(.horizontal) 171 list.safeAreaInset(edge: .bottom) { 172 if showButton { 173 button 174 // .padding(.bottom, 40) 175 } 176 } 177 } 178 } 179 }