splittestview.swift sqrl
--
//
// SplitTestView.swift
// SQRL
//
// Created by Torres, Adriana on 6/15/22.
//
import SwiftUI
import Combine
// create new files for Save & Spend Split Screens
struct SplitTestView: View {
// @State private var amount: String
@StateObject var vm = SaveSpendSplitViewModel()
var body: some View {
ZStack {
VStack {
VStack {
Spacer().frame(height: 9)
Image(“bloom_icon”)
.frame(width: 38, height: 34)
.accessibilityHidden(true)
Spacer().frame(height: 16)
Text(“How much would you like to transfer to Bloom?”)
//.fontWithLineHeight(font: .fidelityExtraBold(size: 24), lineHeight: 30)
.multilineTextAlignment(.center)
.padding(.horizontal, 24)
.accessibilityAddTraits(.isHeader)
Spacer().frame(height: 30)
Text(“Transfer amount”)
// .fidelityBoldFont(size: 16)
.padding(.bottom, -15)
if #available(iOS 15.0, *) {
TextField(“$25”, value: $vm.amount, format: .number)
.onChange(of: vm.amount) {
print($0)
vm.textChange()
}
.overlay(
RoundedRectangle(cornerRadius: 7)
.stroke(Color.gray, lineWidth: 2)
)
.font(.largeTitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
.multilineTextAlignment(.center)
.padding(.vertical, 20)
.padding(.horizontal, 20)
.keyboardType(.decimalPad)
} else {
// Fallback on earlier versions
}
}
HStack {
Text(“Save”)
// .foregroundColor(.textPrimary)
// .fidelityRegularFont(size: 14)
Spacer()
Text(“Spend”)
// .foregroundColor(.textPrimary)
// .fidelityRegularFont(size: 14)
}
.padding(.vertical, 25)
.padding(.horizontal, 25)
.padding(.bottom, -35)
CustomSlider(value: $vm.split, range: (0, vm.amount), knobWidth: 4) { modifiers in
ZStack {
Group {
Color(UIColor(.blue))
.modifier(modifiers.barLeft)
Color(UIColor(.yellow))
.modifier(modifiers.barRight)
}.cornerRadius(10)
Image(“back”)
.resizable()
.frame(width: 40, height: 40)
.shadow(radius: 3)
.foregroundColor(Color.white)
.modifier(modifiers.knob)
}
}
.frame(height: 20)
.padding(.vertical, 20)
.padding(.horizontal, 20)
.padding(.bottom, 10)
HStack {
VStack(spacing: 5) {
RoundedRectangle(cornerRadius: 12)
// .fill(ColorStyle.bloom.save.color)
// .frame(width: 48, height: 12)
Text(“Save”)
// .foregroundColor(.textPrimary)
// .fidelityRegularFont(size: 14)
Text(String(format: “%.2f”, vm.saveAmount()))
// if slider goes to save side, savings amount increases
// .fidelityBoldFont(size: 16)
Button(action: {
vm.showSaveEdit = true
}, label: {
Text(“Edit”)
// .fidelityRegularFont(size: 16)
// .foregroundColor(.actionTextLink)
})
.foregroundColor(.green)
}
Divider()
.padding([.trailing, .leading], 53)
VStack(spacing: 5) {
RoundedRectangle(cornerRadius: 12)
// .fill(ColorStyle.bloom.spend.color)
.frame(width: 48, height: 12)
Text(“Spend”)
// .foregroundColor(.textPrimary)
// .fidelityRegularFont(size: 14)
Text(String(format: “%.2f”, vm.spendAmount()))
// if slider goes to spend side, spending amount increases
// .fidelityBoldFont(size: 16)
Button(action: {
vm.showSpendEdit = true
}, label: {
Text(“Edit”)
// .fidelityRegularFont(size: 16)
// .foregroundColor(.actionTextLink)
})
.foregroundColor(.green)
}
}
.frame(height: 100)
Spacer()
HStack{
Text(“$10 transfer min. per account.”)
// .fidelityRegularFont(size: 16)
// .foregroundColor(ColorStyle.textSecondary)
Button(action: {
vm.showDetails = true
}, label: {
Text(“See details”)
})
// .fidelityRegularFont(size: 16)
// .foregroundColor(.actionTextLink)
}
.padding()
Button(action: {
vm.showWelcomeDrawer = true
}, label: {
Text(“fake button to test drawer”)
})
// .fidelityRegularFont(size: 16)
// .foregroundColor(.actionTextLink)
Button {
// isPresentingReview = true
print(“button works”)
//print(isPresentingReview)
} label: {
Text(“Review transfer”)
// .foregroundColor(.buttonTextPrimary)
// .fidelityBoldFont(size: 16)
}
// .buttonStyle(SQRLPrimaryButtonStyle())
// add accessibility
.padding(.horizontal, 20)
.padding(.bottom, 40)
}
.onAppear(){
vm.setDefaultValueSlider()
}
.ignoresSafeArea()
.padding(.top, 30)
//.background(ColorStyle.background.color.ignoresSafeArea())
if vm.showDetails {
SaveSpendSplitDrawer(isDetailsPresent: $vm.showDetails)
}
if vm.showSaveEdit {
SaveSpendEdit(showEdit: $vm.showSaveEdit, totalAmount: vm.amount, editAmount: vm.saveAmount(), editingSave: true, vm: vm)
}
if vm.showSpendEdit {
SaveSpendEdit(showEdit: $vm.showSpendEdit, totalAmount: vm.amount, editAmount: vm.spendAmount(), editingSave: false, vm: vm)
}
if vm.showWelcomeDrawer {
SaveSpendSplitWelcomeDrawer(isDetailsPresent: $vm.showWelcomeDrawer)
}
}
}
}
// Create viewModel file
class SaveSpendSplitViewModel: ObservableObject {
@Published var amount: Double = 70.00
@Published var split: Double = 35
//split should have 80% on spend and 20% on save by default but amount will change if user slides
@Published var showDetails: Bool = false
@Published var showWelcomeDrawer: Bool = false
@Published var showSaveEdit: Bool = false
@Published var showSpendEdit: Bool = false
// @State var isSaveEditSelected = false
//var lastSplitValue = 25.0
var spendPercent = 80.0
var minimumPercentage = 20.0
private var cancellable : AnyCancellable?
init() {
cancellable = self.objectWillChange
.receive(on: RunLoop.main)
.sink { newValue in
let _ = self.check()
}
}
func updateAmount(enteredAmount: Double, isSave: Bool) {
let calculatedPercentage = enteredAmount / amount * 100
if isSave {
//minimum percent for save
minimumPercentage = calculatedPercentage
spendPercent = 100 — calculatedPercentage
} else {
//maximum for spend
spendPercent = calculatedPercentage
minimumPercentage = 100 — calculatedPercentage
}
setDefaultValueSlider()
}
var lastSplitValue = 0.0
func setDefaultValueSlider(){
split = amount * minimumPercentage / 100
lastSplitValue = split
}
func textChange(){
//notes: if textfiled get’s change then perventage needs to be set default like 20,80
//spendPercent = 80
//minimumPercentage = 20
if amount <= 0 {
split = 0
} else {
split = amount * minimumPercentage / 100
}
print(“ammount \(amount) and value \(split)”)
}
func check() {
// let changeVal = split — lastSplitValue
let calculatedPercentage = split / amount * 100
if split > lastSplitValue {
//Save percent increase
print(“Save percent increase”)
spendPercent = calculatedPercentage
minimumPercentage = 100 — calculatedPercentage
} else {
//Spend percent increase
print(“Spend percent increase”)
minimumPercentage = calculatedPercentage
spendPercent = 100 — calculatedPercentage
}
lastSplitValue = split
print(split)
}
func spendAmount() -> Double {
let spendAmount = amount * spendPercent / 100
// split = spendAmount
return spendAmount
}
func saveAmount() -> Double {
let saveAmount = amount * minimumPercentage / 100
// split = saveAmount
return saveAmount
}
}
// Create Review Screen File
struct ReviewSplitTransferView: View {
var body: some View {
ZStack {
VStack {
VStack {
Spacer().frame(height: 9)
Image(“bloom_icon”)
.frame(width: 38, height: 34)
.accessibilityHidden(true)
Spacer().frame(height: 16)
Text(“Review your transfer”)
//.fontWithLineHeight(font: .fidelityExtraBold(size: 24), lineHeight: 30)
.multilineTextAlignment(.center)
.padding(.horizontal, 24)
.accessibilityAddTraits(.isHeader)
Spacer().frame(height: 30)
HStack(alignment: .top, spacing: 0) {
Text(“$”)
//.fidelityBoldFont(size: 28)
Text(“50.00”)
//.fidelityBoldFont(size: 48)
}
.frame(height: 40)
}
.padding()
HStack {
VStack(spacing: 5) {
RoundedRectangle(cornerRadius: 12)
// .fill(ColorStyle.bloom.save.color)
.frame(width: 48, height: 12)
Text(“Save”)
// .foregroundColor(.textPrimary)
// .fidelityRegularFont(size: 16)
Text(“$25.00”)
// .foregroundColor(.textPrimary)
// .fidelityBoldFont(size: 24)
}
Divider()
//.background(ColorStyle.separatorColor.color)
.padding([.trailing, .leading], 53)
.frame(height: 50)
VStack(spacing: 5) {
RoundedRectangle(cornerRadius: 12)
// .fill(ColorStyle.bloom.spend.color)
// .frame(width: 48, height: 12)
Text(“Spend”)
// .foregroundColor(.textPrimary)
// .fidelityRegularFont(size: 16)
Text(“$25.00”)
// .foregroundColor(.textPrimary)
// .fidelityBoldFont(size: 24)
}
}
Spacer().frame(height: 32)
VStack {
Divider()
// .background(ColorStyle.separatorColor.color)
.padding([.trailing, .leading], 53)
HStack() {
VStack{
Text(“From”)
// .fidelityRegularFont(size: 12)
// .foregroundColor(.textPrimary)
Text(“Bank of America (*1234)”)
// .fidelityBoldFont(size: 16)
// .foregroundColor(.textPrimary)
}
Spacer()
Image(systemName: “chevron.down”)
.accessibility(hidden: true)
.frame(width: 24, height: 13)
// .foregroundColor(ColorStyle.bloom.disclosureIndicator)
}
.padding([.trailing, .leading], 53)
}
VStack {
Divider()
// .background(ColorStyle.separatorColor.color)
.padding([.trailing, .leading], 53)
VStack{
Text(“Transfer date”)
// .fidelityRegularFont(size: 12)
// .foregroundColor(.textPrimary)
Text(“Today”)
// .fidelityBoldFont(size: 16)
// .foregroundColor(.textPrimary)
}
.padding([.trailing, .leading], 53)
Divider()
//.background(ColorStyle.separatorColor.color)
.padding([.trailing, .leading], 53)
}
Spacer()
HStack {
Image(systemName: “clock”)
.resizable()
.frame(width: 15, height: 15)
.foregroundColor(.green)
// update clock color
Text(“This transfer will be available in 1–3 days”)
//.fidelityRegularFont(size: 14)
//.foregroundColor(ColorStyle.textSecondary)
}
.padding()
Button { } label: {
Text(“Confirm transfer”)
// .foregroundColor(.buttonTextPrimary)
// .fidelityBoldFont(size: 16)
}
//.buttonStyle(SQRLPrimaryButtonStyle())
// add accessibility
.padding(.horizontal, 20)
.padding(.bottom, 40)
}
.ignoresSafeArea()
.padding(.top, 30)
// .background(ColorStyle.background.color.ignoresSafeArea())
}
}
}
// Separate files
struct SaveSpendSplitDrawer: View {
@Binding var isDetailsPresent: Bool
var body: some View {
ZStack(alignment: .bottom) {
BlankView()
.opacity(isDetailsPresent ? 0.4 : 0.0)
.onTapGesture {
withAnimation {
isDetailsPresent = false
}
}
if isDetailsPresent {
SaveSpendSplitDetails(closeAction: {
withAnimation {
isDetailsPresent = false
}
})
.transition(.offset(y: 400)
.combined(with: .opacity))
}
}
.ignoresSafeArea(.all, edges: .bottom)
}
}
struct SaveSpendSplitDetails: View {
// @EnvironmentObject private var store: MoreScreenStore
var closeAction: () -> Void
@State private var isPresentingFAQ = false
var body: some View {
VStack {
Group {
Text(“Minimums and delivery”)
.fixedSize(horizontal: false, vertical: true)
// .fontWithLineHeight(font: .fidelityExtraBold(size: 18), lineHeight: 30)
.multilineTextAlignment(.center)
// .foregroundColor(.textPrimary)
.frame(width: 250, height: 60, alignment: .center)
.padding(.top, 10)
// Spacer().frame(height: 24)
Text(“There is a $10 minimum per account (Save & Spend) when transferring money into Fidelity BloomSM from an outside account. You can also choose to send the full transfer into just one account, and you can always move money between Bloom account with no minimums or fees. Transferring money between any Fidelity accounts will have same-day delivery…”)
.fixedSize(horizontal: false, vertical: true)
// .fontWithLineHeight(font: .fidelityRegular(size: 14), lineHeight: 20)
.multilineTextAlignment(.center)
// check / update color #DC1616
// .foregroundColor(.bloom.textError) // #DC1515
// .foregroundColor(.textPrimary)
.padding(.horizontal, 30)
}
Spacer().frame(height: 20)
Button(action: {}, label: {
Text(“Money movement FAQs”)
})
// .fidelityRegularFont(size: 16)
// .foregroundColor(.actionTextLink)
}
.padding(.bottom, 40)
.frame(maxWidth: .infinity)
//.background(ColorStyle.bloom.surface.color)
// .cornerRadius(radius: 8.0, corners: [.topLeft, .topRight])
.overlay(
Button(action: closeAction, label: {
Image(“icon_navigation_bar_close”)
.resizable()
.fixedSize()
// .foregroundColor(.buttonIcon)
})
.accessibilityLabel(“Accessibility.closeButton”)
.padding(21.0),
alignment: .topTrailing
)
NavigationLink(isActive: $isPresentingFAQ) {
// FAQView(selectFaqCategory: .moneyMovement)
//// .environmentObject(store)
// .navigationTitle(LocalizedCMRStr(“More.moreGetHelp”))
// //.navigationBarItems(trailing: FAQVirtualAssistantButton() .padding())
} label: { EmptyView() }
.accessibilityHidden(true)
}
}
// Separate files
struct SaveSpendEdit: View {
@Binding var showEdit: Bool
@State var totalAmount: Double
@State var editAmount: Double
@State var editingSave: Bool
@StateObject var vm = SaveSpendSplitViewModel()
var body: some View {
ZStack(alignment: .bottom) {
BlankView()
.opacity(showEdit ? 0.4 : 0.0)
.onTapGesture {
withAnimation {
showEdit = false
}
}
if showEdit {
SaveSpendEditDetails(closeAction: {
withAnimation {
showEdit = false
}
}, value: editAmount, totalAmount: totalAmount, editingSave: editingSave, vm: vm)
.transition(.offset(y: 400)
.combined(with: .opacity))
}
}
.ignoresSafeArea(.all, edges: .bottom)
}
}
struct SaveSpendEditDetails: View {
var closeAction: () -> Void
@State var value: Double
@State var totalAmount: Double
@State var editingSave: Bool
@StateObject var vm = SaveSpendSplitViewModel()
// @State var text: String = “TESTEST”
var body: some View {
VStack {
Group {
Text(“How much of your $ \(String(format: “%.2f”, totalAmount)) transfer should go into Spend?”)
.fixedSize(horizontal: false, vertical: true)
// .fontWithLineHeight(font: .fidelityExtraBold(size: 18), lineHeight: 30)
.multilineTextAlignment(.center)
// .foregroundColor(.textPrimary)
.padding(.horizontal, 50)
CustomTextFieldNumber(text: $value, isFirstResponder: true)
.overlay(
RoundedRectangle(cornerRadius: 7)
.stroke(Color.gray, lineWidth: 2)
)
.font(.largeTitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
// .multilineTextAlignment(.center)
.padding(.vertical, 20)
.padding(.horizontal, 20)
.padding(.bottom, -15)
.frame(height: 60)
Text(“$10.00 transfer min per account.”)
// .fidelityRegularFont(size: 16)
// .foregroundColor(ColorStyle.textSecondary)
Spacer().frame(height: 30)
Button {
vm.updateAmount(enteredAmount: value, isSave: editingSave)
} label: {
Text(“Update”)
// .foregroundColor(.buttonTextPrimary)
// .fidelityBoldFont(size: 16)
}
// .buttonStyle(SQRLPrimaryButtonStyle())
// add accessibility
.frame(maxWidth: .infinity)
.padding(.horizontal, 20)
.padding(.bottom, 40)
}
Spacer()
}
.frame(maxHeight: 600)
.ignoresSafeArea(.keyboard)
.padding(.top, 40)
.padding(.bottom, 40)
.frame(maxWidth: .infinity)
// .background(ColorStyle.bloom.surface.color)
// .background(.white)
// .cornerRadius(radius: 8.0, corners: [.topLeft, .topRight])
.overlay(
Button(action: closeAction, label: {
Image(“icon_navigation_bar_close”)
.resizable()
.fixedSize()
// .foregroundColor(.buttonIcon)
})
.accessibilityLabel(“Accessibility.closeButton”)
.padding(21.0),
alignment: .topTrailing
)
}
}
// Separate files
struct SaveSpendSplitWelcomeDrawer: View {
@Binding var isDetailsPresent: Bool
var body: some View {
ZStack(alignment: .bottom) {
BlankView()
.opacity(isDetailsPresent ? 0.4 : 0.0)
.onTapGesture {
withAnimation {
isDetailsPresent = false
}
}
if isDetailsPresent {
SaveSpendSplitWelcomeDrawerDetails(closeAction: {
withAnimation {
isDetailsPresent = false
}
})
.transition(.offset(y: 400)
.combined(with: .opacity))
}
}
.ignoresSafeArea(.all, edges: .bottom)
}
}
struct SaveSpendSplitWelcomeDrawerDetails: View {
// @EnvironmentObject private var store: MoreScreenStore
var closeAction: () -> Void
@State private var isPresentingFAQ = false
var body: some View {
VStack {
Group {
Text(“Two accounts, one goal”)
.fixedSize(horizontal: false, vertical: true)
// .fontWithLineHeight(font: .fidelityExtraBold(size: 24), lineHeight: 30)
.multilineTextAlignment(.center)
// .foregroundColor(.textPrimary)
.padding(.top, 30)
.padding(.horizontal, 20)
// Spacer().frame(height: 24)
Image(“Split_DrawerIcon”)
.resizable()
.frame(width: 100, height: 56)
.padding(.top, 10)
Text(“We believe in saving first and spending second, so when you transfer money into Bloom you can choose how much of that goes into your Bloom Save account, and how much goes to Bloom Spend. Even a small amount can add up over time, and you can always move money between your Bloom accounts later.”)
.fixedSize(horizontal: false, vertical: true)
// .fontWithLineHeight(font: .fidelityRegular(size: 16), lineHeight: 20)
.multilineTextAlignment(.center)
// check / update color #DC1616
// .foregroundColor(.textPrimary)
.padding(.top, 10)
.padding(.horizontal, 30)
}
Spacer().frame(height: 20)
}
.padding(.bottom, 40)
.frame(maxWidth: .infinity)
// .background(ColorStyle.bloom.surface.color)
// .cornerRadius(radius: 8.0, corners: [.topLeft, .topRight])
.overlay(
Button(action: closeAction, label: {
Image(“icon_navigation_bar_close”)
.resizable()
.fixedSize()
// .foregroundColor(.buttonIcon)
})
.accessibilityLabel(“Accessibility.closeButton”)
.padding(21.0),
alignment: .topTrailing
)
NavigationLink(isActive: $isPresentingFAQ) {
// FAQView(selectFaqCategory: .moneyMovement)
//// .environmentObject(store)
// .navigationTitle(LocalizedCMRStr(“More.moreGetHelp”))
// .navigationBarItems(trailing: FAQVirtualAssistantButton()
// .padding())
} label: { EmptyView() }
.accessibilityHidden(true)
}
}
// Create CustomSlider.swift file
extension Double {
func convert(fromRange: (Double, Double), toRange: (Double, Double)) -> Double {
var value = self
value -= fromRange.0
value /= Double(fromRange.1 — fromRange.0)
value *= toRange.1 — toRange.0
value += toRange.0
return value
}
}
struct CustomSliderComponents {
let barLeft: CustomSliderModifier
let barRight: CustomSliderModifier
let knob: CustomSliderModifier
}
struct CustomSliderModifier: ViewModifier {
enum Name {
case barLeft
case barRight
case knob
}
let name: Name
let size: CGSize
let offset: CGFloat
func body(content: Content) -> some View {
content
.frame(width: size.width)
.position(x: size.width*0.5, y: size.height*0.5)
.offset(x: offset)
}
}
struct CustomSlider<Component: View>: View {
@Binding var value: Double
var range: (Double, Double)
var knobWidth: CGFloat?
let viewBuilder: (CustomSliderComponents) -> Component
init(value: Binding<Double>, range: (Double, Double), knobWidth: CGFloat? = nil,
_ viewBuilder: @escaping (CustomSliderComponents) -> Component
) {
_value = value
self.range = range
self.viewBuilder = viewBuilder
self.knobWidth = knobWidth
}
var body: some View {
return GeometryReader { geometry in
self.view(geometry: geometry)
}
}
func view(geometry: GeometryProxy) -> some View {
let frame = geometry.frame(in: .global)
let drag = DragGesture(minimumDistance: 0).onChanged({ drag in
self.onDragChange(drag, frame) }
)
let offsetX = self.getOffsetX(frame: frame)
let knobSize = CGSize(width: knobWidth ?? frame.height, height: frame.height)
let barLeftSize = CGSize(width: CGFloat(offsetX + knobSize.width * 0.5), height: frame.height)
let barRightSize = CGSize(width: frame.width — barLeftSize.width, height: frame.height)
let modifiers = CustomSliderComponents(
barLeft: CustomSliderModifier(name: .barLeft, size: barLeftSize, offset: 0),
barRight: CustomSliderModifier(name: .barRight, size: barRightSize, offset: barLeftSize.width),
knob: CustomSliderModifier(name: .knob, size: knobSize, offset: offsetX)
)
return ZStack { viewBuilder(modifiers).gesture(drag) }
}
func onDragChange(_ drag: DragGesture.Value, _ frame: CGRect) {
let width = (knob: Double(knobWidth ?? frame.size.height), view: Double(frame.size.width))
let xrange = (min: Double(0), max: Double(width.view — width.knob))
var value = Double(drag.startLocation.x + drag.translation.width) // knob center x
value -= 0.5*width.knob // offset from center to leading edge of knob
value = value > xrange.max ? xrange.max : value // limit to leading edge
value = value < xrange.min ? xrange.min : value // limit to trailing edge
value = value.convert(fromRange: (xrange.min, xrange.max), toRange: range)
self.value = value
//print(value)
}
private func getOffsetX(frame: CGRect) -> CGFloat {
let width = (knob: knobWidth ?? frame.size.height, view: frame.size.width)
let xrange: (Double, Double) = (0, Double(width.view — width.knob))
let result = self.value.convert(fromRange: range, toRange: xrange)
return CGFloat(result)
}
}
struct CustomTextFieldNumber: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: Double
var didBecomeFirstResponder = false
init(text: Binding<Double>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
if let enteredtTxt = textField.text{
text = Double(enteredtTxt) ?? 00.00
}
}
}
@Binding var text: Double
var isFirstResponder: Bool = false
func makeUIView(context: UIViewRepresentableContext<CustomTextFieldNumber>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.keyboardType = .numberPad
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> CustomTextFieldNumber.Coordinator {
return Coordinator(text: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextFieldNumber>) {
uiView.text = String(text)
if isFirstResponder && !context.coordinator.didBecomeFirstResponder {
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder = true
}
}
}