taler-ios

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

BarGraph.swift (4873B)


      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         let isLegacy = scope.type == .exchangeLegacyKeys
     43         HStack (alignment: .center, spacing: 10) {
     44             if !minimalistic || currencyInfo.hasSymbol {
     45                 Text(currencyInfo.name)
     46                     .strikethrough(isLegacy, color: WalletColors().attention)
     47                     .talerFont(.title2)
     48                     .foregroundColor(WalletColors().secondary(colorScheme, colorSchemeContrast))
     49             }
     50             BarGraph(transactions: $completedTransactions,
     51                           maxBars: MAXBARS, barHeight: barHeight)
     52         }
     53 //        .headerProminence(.increased)         // unfortunately this is not useful
     54         .task(id: reloadTransactions + 2_000_000) { await loadCompleted() }
     55     }
     56 }
     57 // MARK: -
     58 struct BarGraph: View {
     59     @Binding var transactions: [TalerTransaction]
     60     let maxBars: Int
     61     let barHeight: Double
     62 
     63     func maxValue(_ someTransactions: [TalerTransaction]) -> Double {
     64         var maxValue = 0.0
     65         for transaction in someTransactions {
     66             let value = transaction.common.amountEffective.value
     67             if value > maxValue {
     68                 maxValue = value
     69             }
     70         }
     71         return maxValue
     72     }
     73 
     74     var body: some View {
     75         let slice = transactions.prefix(maxBars)
     76         let tenTransactions: [TalerTransaction] = Array(slice)
     77         let maxValue = maxValue(tenTransactions)
     78 
     79         HStack(alignment: .center, spacing: 1) {
     80             if !slice.isEmpty {
     81                 ForEach(tenTransactions, id: \.self) {transaction in
     82                     let common = transaction.common
     83                     let incoming = common.isIncoming
     84                     let netto = common.amountEffective.value
     85                     let valueColored = maxValue > 0 ? barHeight * netto / maxValue : 0
     86                     let valueTransparent = barHeight - valueColored
     87 //                    let _ = print("max: \(maxValue), ", incoming ? "+" : "-", netto)
     88                     VStack(spacing: 0) {
     89                         let width = barHeight / 3
     90                         let topHeight = incoming ? valueTransparent : barHeight
     91                         let botHeight = incoming ? barHeight : valueTransparent
     92                         if topHeight > 0 {
     93                             Rectangle()
     94                                 .opacity(INVISIBLE)
     95                                 .frame(width: width, height: topHeight)
     96                         }
     97                         Rectangle()
     98                             .foregroundColor(incoming ? WalletColors().positive : WalletColors().negative)
     99                             .frame(width: width, height: valueColored)
    100                         if botHeight > 0 {
    101                             Rectangle()
    102                                 .opacity(INVISIBLE)
    103                                 .frame(width: width, height: botHeight)
    104                         }
    105                     }
    106                 }
    107             }
    108         }
    109         .accessibilityHidden(true)      // cannot speak out this bar chart info
    110         .flippedDirection()             // draw first array item on trailing edge
    111     }
    112 }
    113 
    114 
    115 
    116 #if false
    117 #Preview {
    118     var sampleBars: [BarData] {
    119         var tempBars = [BarData]()
    120 
    121         for _ in 1...8 {
    122             let rand = Double.random(in: -100.0...100.0)
    123 
    124             let bar = BarData(value: rand)
    125             tempBars.append(bar)
    126         }
    127         return tempBars
    128     }
    129 
    130     return BarGraph(bars: sampleBars)
    131 }
    132 #endif