taler-ios

iOS apps for GNU Taler (wallet)
Log | Files | Refs | README | LICENSE

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 }