taler-ios

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

ArrowHistoryView.swift (8567B)


      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 Charts
     10 import os.log
     11 import SymLog
     12 import taler_swift
     13 
     14 fileprivate let lineWidth = 6.0
     15 fileprivate let river_back = "river-back"
     16 
     17 // MARK: -
     18 @available(iOS 16.4, *)
     19 struct ArrowHistoryView: View {
     20     let stack: CallStack
     21     private let logger = Logger(subsystem: "net.taler.gnu", category: "Arrow")
     22     let currency: OIMcurrency
     23     @Binding var shownItems: [HistoryItem]
     24     @Binding var dataPointWidth: CGFloat
     25     let scrollBack: Bool
     26 
     27     let maxXValue: Int           // #of dataPoints in history, plus spacers
     28     @State private var maxYValue: Double        // max balance
     29 
     30     @Namespace var riverID
     31     @State private var scrollPosition: Double = 0
     32     @State private var selectedTX: Int? = nil
     33     @State private var selectedY: Double? = nil
     34     @State private var selectedRange: ClosedRange<Double>?
     35     @State private var lastDelta: Double?
     36 
     37     init(stack: CallStack,
     38       currency: OIMcurrency, shownItems: Binding<[HistoryItem]>, dataPointWidth: Binding<CGFloat>,
     39          scrollBack: Bool, maxIndex: Int = 1, maxValue: Double = 200
     40     ) {
     41         self.stack = stack
     42         self.currency = currency
     43         self.scrollBack = scrollBack
     44         self._shownItems = shownItems
     45         self._dataPointWidth = dataPointWidth
     46         self.maxYValue = maxValue
     47         self.maxXValue = maxIndex
     48     }
     49 
     50     func selectTX(_ selected: Int?) {
     51         withAnimation {
     52             selectedTX = selected
     53         }
     54     }
     55 
     56     func historyItem(for xVal: Int) -> HistoryItem? {
     57         for item in shownItems {
     58             if item.distance == -xVal {
     59                 return item
     60             }
     61         }
     62         return nil
     63     }
     64 
     65     var body: some View {
     66         let count = shownItems.count
     67 //    let _ = logger.log("ArrowHistoryView \(width.pTwo)")
     68 
     69         ZStack(alignment: .top) {
     70             HStack(spacing: 0) {
     71                 VStack {
     72                     Image("Request")
     73                         .resizable()
     74                         .scaledToFit()
     75                         .frame(width: OIMbuttonSize, height: OIMbuttonSize)
     76                         .padding(.bottom, 30)
     77                     Image("SendMoney")
     78                         .resizable()
     79                         .scaledToFit()
     80                         .frame(width: OIMbuttonSize, height: OIMbuttonSize)
     81                         .padding(.vertical, 30)
     82                 }
     83                 ScrollViewReader { scrollProxy in
     84                     OptimalSize(.horizontal) {      // keep it small if we have only a few tx
     85                         ScrollView(.horizontal) {
     86                             if count > 0 {
     87                                 HStack(spacing: 0) {
     88                                     ForEach(-maxXValue...0, id: \.self) { xVal in
     89                                         ArrowTileView(historyItem: historyItem(for: xVal)) {
     90                                             selectTX(xVal)
     91                                         }
     92                                     }
     93                                 }.id(riverID)
     94                             }
     95                         }
     96                         //.border(.blue)
     97                         .scrollBounceBehavior(.basedOnSize, axes: .horizontal)      // don't bounce if it's small
     98                         .task(id: scrollBack) {
     99                             logger.log("Task \(scrollBack)")
    100                             if scrollBack {
    101                                 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    102                                     logger.log("Scrolling")
    103                                     withAnimation() {       // .easeOut(duration: 3.0)  doesn't work, always uses standard timing
    104                                         scrollProxy.scrollTo(riverID, anchor: .bottomTrailing)
    105                                     }
    106                                 }
    107                             }
    108                         }
    109                         .onTapGesture(count: 2) {
    110                             withAnimation(.easeOut(duration: 0.6)) {
    111                                 dataPointWidth = 100        // reset the scale first
    112                                 scrollProxy.scrollTo(riverID, anchor: .bottomTrailing)
    113                             }
    114                         }
    115                     }
    116                 } // ScrollViewReader
    117             }
    118             if let selectedTX {
    119                 if let item = historyItem(for: selectedTX) {
    120                     if let talerTX = item.talerTX {
    121                         HistoryDetailView(stack: stack.push(),
    122                                        currency: currency,
    123                                         balance: item.balance,
    124                                         talerTX: talerTX)
    125                         .onTapGesture { selectTX(nil) }
    126                     }
    127                 }
    128             }
    129         } // ZStack
    130     }
    131 }
    132 // MARK: -
    133 struct ArrowTileView: View {
    134     let historyItem: HistoryItem?
    135     let selectTX: () -> Void
    136 
    137     func sizeIn(for value: Double) -> Int {
    138         if value < 100 { return 0 }
    139         if value < 300 { return 1 }
    140         if value < 500 { return 2 }
    141         if value < 750 { return 3 }
    142         else { return 4 }
    143     }
    144 
    145     func sizeOut(for value: Double) -> Int {
    146         if value < 31 { return 0 }
    147         if value < 80 { return 1 }
    148         if value < 200 { return 2 }
    149         if value < 500 { return 3 }
    150         else { return 4 }
    151     }
    152 
    153     var body: some View {
    154         let txValue = historyItem?.talerTX?.common.amountEffective.value ?? 0
    155 //        let width = width(for: txValue)
    156         let tree = Image("Tree-without-shadow")
    157             .resizable()
    158 
    159         let treeSpace = 15.0
    160         let background = VStack(spacing: 0) {
    161             Color.brown     // add some random trees
    162                 .overlay(alignment: .topLeading) {
    163                     GeometryReader { geo in
    164                         let height = geo.size.height
    165                         let width = geo.size.width
    166 //                        let _ = print("width = \(width), height = \(height)")
    167                         let treeCount = Int.random(in: 23...57)
    168                         ForEach(0..<treeCount, id: \.self) { treeIndex in
    169                             let xOffset = Double.random(in: 3...width-treeSpace)
    170                             let yOffset = Double.random(in: 3...height-treeSpace)
    171                             let treeSize = Double.random(in: 10...20)
    172                             tree.frame(width: treeSize, height: treeSize)
    173                                 .offset(x: xOffset, y: yOffset)
    174                         }
    175                     }
    176                 }
    177             Color.yellow
    178         }
    179         let background2 = VStack(spacing: 0) {
    180             Color.brown
    181             Color.yellow
    182         }
    183 
    184         // transactions
    185         if let historyItem {
    186             if let talerTX = historyItem.talerTX {
    187                 let common = talerTX.common
    188                 let amount = common.amountEffective
    189                 if common.isIncoming {
    190                     let sizeIndex = sizeIn(for: amount.value)
    191                     Image("Empty-" + String(sizeIndex))
    192                         .resizable()
    193                         .scaledToFit()
    194 //                        .border(.red)
    195                         .onTapGesture { selectTX() }
    196                         .background { background }
    197                 } else if common.isOutgoing {
    198                     let sizeIndex = sizeOut(for: amount.value)
    199                     Image("Empty-" + String(sizeIndex))
    200                         .resizable()
    201                         .scaledToFit()
    202 //                        .border(.red)
    203                         .onTapGesture { selectTX() }
    204                         .background { background }
    205                 }
    206                 if historyItem.marker != .none {
    207                     let markerIndex = historyItem.marker.rawValue
    208                     Image("Empty-" + String(markerIndex))
    209                         .resizable()
    210                         .scaledToFit()
    211                         .background { background2 }
    212                         .overlay {
    213                             LinearGradient(colors: [.clear, .black, .clear], startPoint: .leading, endPoint: .trailing)
    214                                 .opacity(0.5)
    215                         }
    216                 }
    217             } else {
    218                 let _ = print("no talerTX")
    219             }
    220         } else {
    221             let _ = print("no historyItem")
    222         }
    223         //.border(.green)
    224         //.border(.blue)
    225     }
    226 }