taler-ios

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

TabBarView.swift (7340B)


      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 struct ActionItem: View {
     12     private let symLog = SymLogV(0)
     13     let tab: TalerTab
     14     let onTap: () -> Void
     15     let onDrag: () -> Void
     16 
     17     @EnvironmentObject private var controller: Controller
     18     @AppStorage("tapped") var tapped: Int = 0
     19     @AppStorage("dragged") var dragged: Int = 0
     20 
     21     @State private var offset = CGSize.zero
     22     @State private var didDrag = false
     23 
     24     private func hopAction(_ newValue: Int) {
     25         if tapped >= TAPPED && dragged < DRAGGED {
     26             withAnimation(Animation.easeOut(duration: DRAGDURATION).delay(DRAGDELAY)) {
     27                 offset.height = -50
     28             }
     29             withAnimation(Animation.easeOut(duration: DRAGSPEED).delay(DRAGDELAY + DRAGDURATION + DRAGSPEED)) {
     30                 offset.height = 10
     31             }
     32             withAnimation(Animation.easeOut(duration: DRAGSPEED/2).delay(DRAGDELAY + DRAGDURATION + 2.5*DRAGSPEED)) {
     33                 offset.height = 0
     34             }
     35         }
     36     }
     37 
     38     private func dragGesture() -> some Gesture {
     39         DragGesture(minimumDistance: 6)
     40             .onChanged { gesture in
     41                 var trans = gesture.translation
     42                 trans.width = .zero
     43                 if trans.height < -20 {
     44                     symLog.log(".onChanged: didDrag \(trans.height)")
     45                     withAnimation {
     46                         offset = .zero
     47                     }
     48                     didDrag = true
     49                     onDrag()    // switch to camera
     50                     if tapped >= TAPPED {
     51                         dragged += 1
     52                     }
     53                 } else if trans.height < 0 {    // only drag up
     54                     symLog.log(".onChanged: \(trans.height)")
     55                     withAnimation {
     56                         offset = trans
     57                     }
     58                 }
     59             }
     60             .onEnded { gesture in
     61                 let trans = gesture.translation
     62                 if didDrag {
     63                     symLog.log(".onEnded: didDrag \(trans.height)")
     64                     didDrag = false
     65                 } else {
     66                     symLog.log(".onActionTab: \(trans.height)")
     67                     onTap()
     68                 }
     69                 withAnimation {
     70                     offset = .zero
     71                 }
     72             }
     73     }
     74 
     75     var body: some View {
     76         let vStack = VStack(spacing: 0) {
     77     // show floating actions
     78             let withText = if #available(iOS 26.0, *) { false } else { tapped < TAPPED }
     79             let width = withText ? 48 : 72.0
     80             let height = withText ? 36 : 57.6
     81             let image = tab.image
     82                 .resizable()
     83                 .scaledToFill()
     84                 .frame(width: width, height: height)
     85                 .clipped() // Crop the image to the frame size
     86 //                .opacity(1 - Double(abs(offset.height / 35)))
     87                 .gesture(dragGesture())
     88                 .onLongPressGesture(minimumDuration: 0.4) {
     89                     onDrag()
     90                 }
     91                 .onTapGesture {
     92                     onTap()
     93                     tapped += 1
     94                 }
     95             if #available(iOS 26.0, *) {
     96                 image
     97             } else {
     98                 image
     99                     .padding(.bottom, 4)
    100                     .offset(offset)
    101             }
    102             if withText {
    103                 Text(tab.title)
    104                     .lineLimit(1)
    105                     .talerFont(.body)
    106             }
    107         }   .id(tab)
    108 //            .foregroundColor(isActive ? .accentColor : .secondary)
    109             .padding(.vertical, 4)
    110             .accessibilityElement(children: .combine)
    111             .accessibility(label: Text(tab.title))
    112             .accessibility(addTraits: [.isButton])
    113             .accessibility(removeTraits: [.isImage])
    114             .onChange(of: controller.userAction) { newValue in
    115                 hopAction(newValue)         // make Action button jump to indicate it can be dragged
    116             }
    117 
    118         if #available(iOS 26.0, *) {
    119             vStack
    120                 .padding(.horizontal, 6)
    121                 .glassEffect(.clear.interactive())
    122                 .offset(offset)
    123         } else {
    124             vStack
    125                 .frame(maxWidth: .infinity)
    126         }
    127     }
    128 }
    129 // MARK: -
    130 //struct TabBarItem: View {
    131 //    let tab: TalerTab
    132 //
    133 //    var body: some View {
    134 //    }
    135 //}
    136 // MARK: -
    137 struct TabBarView: View {
    138     private let symLog = SymLogV(0)
    139     @Binding var selection: TalerTab
    140     @Binding var hidden: Int
    141     let onActionTab: () -> Void
    142     let onActionDrag: () -> Void
    143 
    144     @Environment(\.keyboardShowing) var keyboardShowing
    145     @EnvironmentObject private var controller: Controller
    146 
    147     @AppStorage("minimalistic") var minimalistic: Bool = false
    148     @AppStorage("tapped") var tapped: Int = 0
    149 
    150     @Namespace private var namespace
    151 
    152     private func tabBarItem(for tab: TalerTab) -> some View {
    153         
    154         let isActive = selection == tab
    155         let vStack = VStack(spacing: 0) {
    156             let withText = tapped < TAPPED || !minimalistic
    157             let size = withText ? 24.0 : 36.0
    158             tab.image
    159                 .resizable()
    160                 .renderingMode(.template)
    161                 .tint(.black)
    162                 .aspectRatio(contentMode: .fit)
    163                 .frame(width: size, height: size)
    164 
    165             if withText {
    166                 if isActive {
    167                     Text(tab.title)
    168                         .bold()
    169                         .lineLimit(1)
    170                         .talerFont(.picker)
    171                 } else {
    172                     Text(tab.title)
    173                         .lineLimit(1)
    174                         .talerFont(.body)
    175                 }
    176             }
    177         }   .id(tab)
    178             .foregroundColor(isActive ? .accentColor : .secondary)
    179             .padding(.vertical, 8)
    180             .accessibilityElement(children: .combine)
    181             .accessibility(label: Text(tab.title))
    182             .accessibility(addTraits: [.isButton])
    183             .accessibility(removeTraits: [.isImage])
    184         return vStack
    185             .frame(maxWidth: .infinity)
    186             .contentShape(Rectangle())
    187     }
    188 
    189     var body: some View {
    190         Group {
    191             if keyboardShowing || hidden > 0 {
    192                 EmptyView()
    193             } else {
    194                 let actionTab = ActionItem(tab: TalerTab.actions,
    195                                          onTap: onActionTab,
    196                                         onDrag: onActionDrag)
    197                 let balanceTab = tabBarItem(for: TalerTab.balances)
    198                     .onTapGesture {
    199                         selection = .balances
    200                         controller.userAction += 1
    201                     }
    202                 let settingsTab = tabBarItem(for: TalerTab.settings)
    203                     .onTapGesture {
    204                         selection = .settings
    205                         controller.userAction += 1
    206                     }
    207                 HStack(alignment: .bottom) {
    208                     balanceTab
    209                     actionTab
    210                     settingsTab
    211                 }
    212                     .background(WalletColors().backgroundColor.ignoresSafeArea(edges: .bottom))
    213             }
    214         }
    215     }
    216 }
    217 // MARK: -
    218 //#Preview {
    219 //    TabBarView()
    220 //}