taler-ios

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

OIMcurrencyButton.swift (5949B)


      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 taler_swift
     10 
     11 fileprivate
     12 struct OIMbuttonStyle: ButtonStyle {
     13 
     14     public func makeBody(configuration: OIMbuttonStyle.Configuration) -> some View {
     15         configuration.label
     16 //            .foregroundColor(.white)
     17 //            .padding(15)
     18 //            .background(RoundedRectangle(cornerRadius: 5).fill(color))
     19 //            .compositingGroup()
     20 //            .shadow(color: .black, radius: 3)
     21             .opacity(configuration.isPressed ? 0.7 : 1.0)
     22             .scaleEffect(configuration.isPressed ? 0.9 : 1.0)
     23     }
     24 }
     25 
     26 fileprivate
     27 struct OIMcurrencyImage {
     28     let image: Image
     29     let oWidth: CGFloat
     30     let oHeight: CGFloat
     31 
     32     init(_ name: String?) {
     33         if let name, let uiImage = UIImage(named: name) {
     34             self.oWidth = uiImage.size.width
     35             self.oHeight = uiImage.size.height
     36             self.image = Image(uiImage: uiImage)
     37         } else {
     38             self.image = Image(systemName: "exclamationmark.triangle")
     39             self.oWidth = 300
     40             self.oHeight = 300
     41         }
     42     }
     43 }
     44 
     45 fileprivate
     46 struct OIMmod: AnimatableModifier {
     47     let name: String?
     48     let flippedName: String?
     49     let flipTime: TimeInterval
     50     let isFlipped: Bool
     51     let flipBack: Bool            // if true, then don't animate flipping back
     52     let disabled: Bool
     53     let unavailable: Bool
     54     let invisible: Bool
     55     var pct: CGFloat
     56     let action: () -> Void
     57 
     58     var animatableData: CGFloat {
     59         get { pct }
     60         set { pct = newValue }
     61     }
     62 
     63     func body(content: Content) ->  some View {
     64         let currencyImage = OIMcurrencyImage(name)
     65         let oWidth = currencyImage.oWidth / 4
     66         let oHeight = currencyImage.oHeight / 4
     67         let image = currencyImage.image
     68                 .resizable()
     69                 .scaledToFit()
     70 
     71         let valueImage = image
     72             .rotation3DEffect(.degrees(isFlipped ? 90 : 0),
     73                               axis: (x: 0.0, y: 1.0, z: 0.0))
     74         let backImage = Group {
     75             if let flippedName {
     76                 let flippedImage = OIMcurrencyImage(flippedName)
     77                 flippedImage.image
     78                     .resizable()
     79                     .scaledToFit()
     80             } else {
     81                 EmptyView()
     82             }
     83         }
     84             .rotation3DEffect(.degrees(isFlipped ? 0 : -90),
     85                               axis: (x: 0.0, y: 1.0, z: 0.0))
     86 
     87         let linTime: Animation = .linear(duration:flipTime)
     88         let frontAnimation = isFlipped ? linTime
     89                                        : linTime.delay(flipTime)
     90         let backAnimation  = isFlipped ? linTime.delay(flipTime)
     91                                        : linTime
     92         let animatedImage = ZStack {
     93             valueImage
     94                 .animation(flipBack ? nil : frontAnimation, value: isFlipped)
     95             backImage
     96                 .animation(flipBack ? nil : backAnimation, value: isFlipped)
     97         }
     98 
     99         let shadow = (3 - 8) * pct + 8
    100         return Button(action: action) {
    101                     animatedImage.opacity(invisible ? INVISIBLE
    102                                       : unavailable ? 0.5
    103                                                     : 1)
    104                 }
    105                 .buttonStyle(OIMbuttonStyle())
    106                 .disabled(disabled)
    107                 .frame(width: oWidth, height: oHeight)
    108                 .shadow(radius: shadow)
    109     }
    110 
    111 }
    112 // MARK: -
    113 /// renders 1 denomination from a currency
    114 struct OIMcurrencyButton: View {
    115     let stack: CallStack
    116     let fund: OIMfund
    117     let currency: OIMcurrency
    118     let availableVal: UInt64
    119     let canEdit: Bool
    120     let isDrawer: Bool              // debugging - button in the drawer
    121     var pct: CGFloat
    122     let action: () -> Void
    123 
    124     func imgName(_ value: UInt64?) -> String? {
    125         if let value {
    126             return value > currency.bankCoins[0] ? currency.noteName(value)
    127                                                  : currency.coinName(value)
    128         }
    129         return nil
    130     }
    131 
    132     var body: some View {
    133         let value = fund.value
    134         let outValue = fund.outValue
    135         let state = fund.state
    136         let isMorphing = fund.morphTicker != nil
    137         let flippedVal = fund.flippedVal
    138         // Use EmptyView, because the modifier actually ignores
    139         // the value passed to its body() function.
    140         let isFlipped: Bool = switch state {
    141             case .morphing: true
    142             case .arriving: true
    143             default:        false
    144         }
    145 //    if fund.id >= 0 {
    146 //        let _ = print("***Fund", fund.id, value, state, flippedVal)
    147 //    }
    148         let mutating = state.mutating
    149         let flipTime = mutating ? 0.0001       // flip back quickly from morphed
    150                : fastAnimations ? 0.3
    151                                 : 0.5
    152         //                    checkDisabled = true    // TODO: don't run twice
    153         let unavailable = availableVal < value
    154         if isDrawer && value == 2000 {
    155             if unavailable {
    156                 let _ = print("OIMcurrencyButton❗️❗️", availableVal, value)
    157             }
    158         }
    159         let disabled = !canEdit || isMorphing || unavailable        // cannot edit morphing funds
    160         EmptyView().modifier(OIMmod(name: imgName(outValue ?? value),
    161                              flippedName: imgName(flippedVal),
    162                                 flipTime: flipTime,
    163                                isFlipped: isFlipped,
    164                                 flipBack: mutating,
    165                                 disabled: disabled,
    166                              unavailable: unavailable,
    167                                invisible: state.hiding || state.chestOpening,
    168                                      pct: pct,
    169                                   action: action))
    170         .accessibilityLabel(Text("\(value)", comment: "A11y"))         // TODO: currency name
    171     }
    172 }