TransactionsEmptyView.swift (7118B)
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 11 /// This view shows hints if a wallet is empty 12 /// It is the very first thing the user sees after installing the app 13 14 struct TransactionsEmptyView: View { 15 let stack: CallStack 16 let currency: String 17 18 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 19 20 var body: some View { 21 List { 22 Section { 23 Text("There are no transactions for \(currency).") 24 } 25 .talerFont(.title2) 26 } 27 .listStyle(myListStyle.style).anyView 28 .background(FullBackground()) 29 .onAppear() { 30 DebugViewC.shared.setViewID(VIEW_EMPTY_HISTORY, stack: stack.push()) // 20 31 } 32 } 33 } 34 35 // MARK: - 36 struct UnconfirmedKeyChangeView: View { 37 var baseURL: String 38 var balance: Balance 39 var isLegacy: Bool 40 41 @Environment(\.dismiss) var dismiss // pop back once 42 @EnvironmentObject private var model: WalletModel 43 @EnvironmentObject private var controller: Controller 44 @AppStorage("myListStyle") var myListStyle: MyListStyle = .automatic 45 46 @State private var exchange: Exchange? = nil 47 @State private var alertAccept: Bool = false 48 @State private var alertDelete: Bool = false 49 50 func notValid(_ amountString: String) -> String { 51 String(localized: "Unfortunately this means, that your \(amountString) are not valid anymore, because they were obtained with the old security identity.") 52 } 53 54 func keep(_ amountString: String) -> String { 55 String(localized: "Keep the \(amountString)") 56 } 57 58 func delete(_ amountString: String) -> String { 59 String(localized: "Delete the \(amountString)") 60 } 61 62 func acceptAction() { 63 if let exchange, let masterPub = exchange.unconfirmedKeyChange?.currentMasterPub ?? exchange.masterPub { 64 Task { 65 try? await model.confirmExchangeKeyChange(exchangeBaseUrl: baseURL, 66 currentMasterPub: masterPub) 67 dismiss() 68 } 69 } else { 70 // TODO: Yikes! 71 } 72 } 73 74 func deleteAction() { 75 if let exchange, let masterPub = exchange.masterPub { 76 Task { 77 try? await model.purgeExchangeLegacyKeys(exchangeBaseUrl: baseURL, 78 currentMasterPub: masterPub) 79 dismiss() 80 } 81 } else { 82 // TODO: Yikes! 83 } 84 } 85 86 func keepAction() { 87 withAnimation { 88 dismiss() 89 } 90 } 91 92 private var acceptButton: some View { 93 Button("Accept") { 94 alertAccept = false 95 acceptAction() 96 } 97 } 98 private var deleteButton: some View { 99 Button("Delete") { 100 alertDelete = false 101 deleteAction() 102 } 103 } 104 private var dismissAlertButton: some View { 105 Button("Cancel", role: .cancel) { 106 alertAccept = false 107 alertDelete = false 108 } 109 } 110 111 @MainActor 112 private func viewDidLoad() async { 113 // confirm and purge must send the master key wallet-core uses *now* 114 // (not the controller's cached entry) or they fail with KEY_CHANGE_MISMATCH / BAD_REQUEST 115 if let fresh = try? await model.getExchangeByUrl(url: baseURL, viewHandles: true) { 116 exchange = fresh 117 } else { 118 exchange = await controller.exchange(for: baseURL) 119 } 120 } 121 122 var body: some View { 123 let scope = balance.scopeInfo 124 let available = balance.available 125 let formatted = available.formatted(scope, isNegative: false) 126 let navTitle = String(localized: "Security Alert!", comment: "ViewTitle of UnconfirmedKeyChangeView") 127 let warning1 = Text("Only trust the new identity if the payment service has confirmed this change.") 128 let warning2 = Text("Otherwise, wait and try again later...") 129 List { 130 Section { 131 Text("The security identity of \(baseURL.trimURL) has changed unexpectedly.") 132 133 if isLegacy { 134 if !available.isZero { 135 let sorry = notValid(formatted.0) 136 let a11y = notValid(formatted.1) 137 Text(sorry) 138 .accessibilityLabel(a11y) 139 140 warning1 141 .talerFont(.body) 142 warning2 143 .talerFont(.headline) 144 145 Button(keep(formatted.0), action: keepAction) 146 .buttonStyle(TalerButtonStyle(type: .bordered)) 147 .padding(.horizontal) 148 .accessibilityLabel(keep(formatted.1)) 149 150 Text("If you are sure that the old security identity will not be used anymore, you can delete the invalid money.") 151 .talerFont(.body) 152 Button(delete(formatted.0), action: { alertDelete = true }) 153 .buttonStyle(TalerButtonStyle(type: .bordered)) 154 .padding(.horizontal) 155 .accessibilityLabel(delete(formatted.1)) 156 } 157 } else { 158 warning1 159 warning2 160 .bold() 161 Text("If you are sure that the old security identity will not be used anymore, you can withdraw new money with the new security identity.") 162 Button("Accept new security identity", action: { alertAccept = true }) 163 .buttonStyle(TalerButtonStyle(type: .bordered)) 164 .padding(.horizontal) 165 Text("Money you obtained with the old security identity will be invalid.") 166 } 167 } 168 .talerFont(.title2) 169 .listRowSeparator(.hidden) 170 } 171 .listStyle(myListStyle.style).anyView 172 .background(FullBackground()) 173 .navigationTitle(navTitle) 174 .task { 175 await viewDidLoad() 176 } 177 .alert("Accept the new payment service security identity.", 178 isPresented: $alertAccept, 179 actions: { acceptButton 180 dismissAlertButton }, 181 message: { Text("Are you sure? Your money obtained with the old security identity will be invalid.") }) 182 .alert("Delete money obtained with the old security identity.", 183 isPresented: $alertDelete, 184 actions: { deleteButton 185 dismissAlertButton }, 186 message: { Text("Are you sure? That money will be lost.") }) 187 } 188 } 189 // MARK: - 190 struct TransactionsEmptyView_Previews: PreviewProvider { 191 static var previews: some View { 192 TransactionsEmptyView(stack: CallStack("Preview"), currency: LONGCURRENCY) 193 } 194 }