taler-ios

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

BarGraph.swift (4719B)


      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 let MAXBARS = 15
     12 
     13 struct BarGraphHeader: View {
     14     private let symLog = SymLogV(0)
     15     let stack: CallStack
     16     let scope: ScopeInfo
     17     @Binding var reloadTransactions: Int
     18 
     19     @EnvironmentObject private var model: WalletModel
     20     @EnvironmentObject private var controller: Controller
     21     @Environment(\.colorScheme) private var colorScheme
     22     @Environment(\.colorSchemeContrast) private var colorSchemeContrast
     23     @AppStorage("minimalistic") var minimalistic: Bool = false
     24 
     25     @State private var completedTransactions: [TalerTransaction] = []
     26     @ScaledMetric var barHeight = 9       // relative to fontSize
     27 
     28     @MainActor
     29     private func loadCompleted() async {
     30         symLog.log(".task for BarGraphHeader(\(scope.currency)) - load \(MAXBARS) Transactions")
     31         if let response = try? await model.getTransactionsV2(stack.push("BarGraphHeader - \(scope.url?.trimURL)"),
     32                                                       scope: scope,
     33                                               filterByState: .done,
     34                                                       limit: MAXBARS
     35         ) {
     36             completedTransactions = response
     37         }
     38     }
     39 
     40     var body: some View {
     41         let currencyInfo = controller.info(for: scope, controller.currencyTicker)
     42         HStack (alignment: .center, spacing: 10) {
     43             if !minimalistic || currencyInfo.hasSymbol {
     44                 Text(currencyInfo.name)
     45                     .talerFont(.title2)
     46                     .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
     47             }
     48             BarGraph(transactions: $completedTransactions,
     49                           maxBars: MAXBARS, barHeight: barHeight)
     50         }
     51 //        .headerProminence(.increased)         // unfortunately this is not useful
     52         .task(id: reloadTransactions + 2_000_000) { await loadCompleted() }
     53     }
     54 }
     55 // MARK: -
     56 struct BarGraph: View {
     57     @Binding var transactions: [TalerTransaction]
     58     let maxBars: Int
     59     let barHeight: Double
     60 
     61     func maxValue(_ someTransactions: [TalerTransaction]) -> Double {
     62         var maxValue = 0.0
     63         for transaction in someTransactions {
     64             let value = transaction.common.amountEffective.value
     65             if value > maxValue {
     66                 maxValue = value
     67             }
     68         }
     69         return maxValue
     70     }
     71 
     72     var body: some View {
     73         let slice = transactions.prefix(maxBars)
     74         let tenTransactions: [TalerTransaction] = Array(slice)
     75         let maxValue = maxValue(tenTransactions)
     76 
     77         HStack(alignment: .center, spacing: 1) {
     78             if !slice.isEmpty {
     79                 ForEach(tenTransactions, id: \.self) {transaction in
     80                     let common = transaction.common
     81                     let incoming = common.isIncoming
     82                     let netto = common.amountEffective.value
     83                     let valueColored = barHeight * netto / maxValue
     84                     let valueTransparent = barHeight - valueColored
     85 //                    let _ = print("max: \(maxValue), ", incoming ? "+" : "-", netto)
     86                     VStack(spacing: 0) {
     87                         let width = barHeight / 3
     88                         let topHeight = incoming ? valueTransparent : barHeight
     89                         let botHeight = incoming ? barHeight : valueTransparent
     90                         if topHeight > 0 {
     91                             Rectangle()
     92                                 .opacity(INVISIBLE)
     93                                 .frame(width: width, height: topHeight)
     94                         }
     95                         Rectangle()
     96                             .foregroundColor(incoming ? WalletColors().positive : WalletColors().negative)
     97                             .frame(width: width, height: valueColored)
     98                         if botHeight > 0 {
     99                             Rectangle()
    100                                 .opacity(INVISIBLE)
    101                                 .frame(width: width, height: botHeight)
    102                         }
    103                     }
    104                 }
    105             }
    106         }
    107         .accessibilityHidden(true)      // cannot speak out this bar chart info
    108         .flippedDirection()             // draw first array item on trailing edge
    109     }
    110 }
    111 
    112 
    113 
    114 #if false
    115 #Preview {
    116     var sampleBars: [BarData] {
    117         var tempBars = [BarData]()
    118 
    119         for _ in 1...8 {
    120             let rand = Double.random(in: -100.0...100.0)
    121 
    122             let bar = BarData(value: rand)
    123             tempBars.append(bar)
    124         }
    125         return tempBars
    126     }
    127 
    128     return BarGraph(bars: sampleBars)
    129 }
    130 #endif