TabBarView.swift (5749B)
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 TabBarView: View { 12 private let symLog = SymLogV(0) 13 @Binding var selection: Tab 14 @Binding var userAction: Int // must be held in main view; used to make Action button jump to indicate it can be dragged 15 @Binding var hidden: Int 16 let onActionTab: () -> Void 17 let onActionDrag: () -> Void 18 19 @Environment(\.keyboardShowing) var keyboardShowing 20 21 @AppStorage("minimalistic") var minimalistic: Bool = false 22 @AppStorage("tapped") var tapped: Int = 0 23 @AppStorage("dragged") var dragged: Int = 0 24 25 @State private var offset = CGSize.zero 26 @State private var didDrag = false 27 28 private func tabBarItem(for tab: Tab) -> some View { 29 VStack(spacing: 0) { 30 let isActions = (tab == .actions) 31 let withText = isActions ? tapped < TAPPED 32 : tapped < TAPPED || !minimalistic 33 if isActions { 34 let width = withText ? 48 : 72.0 35 let height = withText ? 36 : 57.6 36 tab.image 37 .resizable() 38 .scaledToFill() 39 .frame(width: width, height: height) 40 .clipped() // Crop the image to the frame size 41 .padding(.bottom, 4) 42 .offset(offset) 43 // .opacity(1 - Double(abs(offset.height / 35))) 44 .gesture( 45 DragGesture(minimumDistance: 10) 46 .onChanged { gesture in 47 var trans = gesture.translation 48 trans.width = .zero 49 if abs(trans.height) > 30 { 50 symLog.log(".onChanged: didDrag \(trans.height)") 51 offset = .zero 52 didDrag = true 53 onActionDrag() // switch to camera 54 if tapped >= TAPPED { 55 dragged += 1 56 } 57 } else { 58 symLog.log(".onChanged: \(trans.height)") 59 offset = trans 60 } 61 } 62 .onEnded { gesture in 63 var trans = gesture.translation 64 if didDrag { 65 symLog.log(".onEnded: didDrag \(trans.height)") 66 didDrag = false 67 } else { 68 symLog.log(".onActionTab: \(trans.height)") 69 onActionTab() 70 } 71 offset = .zero 72 } 73 ) 74 } else { 75 let size = withText ? 24.0 : 36.0 76 tab.image 77 .resizable() 78 .renderingMode(.template) 79 .tint(.black) 80 .aspectRatio(contentMode: .fit) 81 .frame(width: size, height: size) 82 } 83 if withText { 84 if selection == tab { 85 Text(tab.title) 86 .bold() 87 .lineLimit(1) 88 .talerFont(.picker) 89 } else { 90 Text(tab.title) 91 .lineLimit(1) 92 .talerFont(.body) 93 } 94 } 95 }.id(tab) 96 .foregroundColor(selection == tab ? .accentColor : .secondary) 97 .padding(.vertical, 8) 98 .accessibilityElement(children: .combine) 99 .accessibility(label: Text(tab.title)) 100 .accessibility(addTraits: [.isButton]) 101 .accessibility(removeTraits: [.isImage]) 102 .frame(maxWidth: .infinity) 103 .contentShape(Rectangle()) 104 } 105 106 private func userAction(_ newValue: Int) { 107 if tapped >= TAPPED && dragged < DRAGGED { 108 withAnimation(Animation.easeOut(duration: DRAGDURATION).delay(DRAGDELAY)) { 109 offset.height = -50 110 } 111 withAnimation(Animation.easeOut(duration: DRAGSPEED).delay(DRAGDELAY + DRAGDURATION + DRAGSPEED)) { 112 offset.height = 10 113 } 114 withAnimation(Animation.easeOut(duration: DRAGSPEED/2).delay(DRAGDELAY + DRAGDURATION + 2.5*DRAGSPEED)) { 115 offset.height = 0 116 } 117 } 118 } 119 var body: some View { 120 Group { 121 if keyboardShowing || hidden > 0 { 122 EmptyView() 123 } else { 124 let actionTab = tabBarItem(for: Tab.actions) 125 let balanceTab = tabBarItem(for: Tab.balances) 126 let settingsTab = tabBarItem(for: Tab.settings) 127 HStack(alignment: .bottom) { 128 balanceTab.onTapGesture { selection = .balances; userAction += 1 } 129 actionTab.onTapGesture { 130 onActionTab() 131 tapped += 1 132 } 133 settingsTab.onTapGesture { selection = .settings; userAction += 1 } 134 } 135 .background(WalletColors().backgroundColor.edgesIgnoringSafeArea(.bottom)) 136 .onChange(of: userAction) { newValue in 137 userAction(newValue) 138 } 139 } 140 } 141 } 142 } 143 // MARK: - 144 //#Preview { 145 // TabBarView() 146 //}