Skip to content

Commit

Permalink
Merge pull request #574 from gini/PP-628-Implement-default-and-custom…
Browse files Browse the repository at this point in the history
…-bottom-bar-navigation

Pp 628 implement default and custom bottom bar navigation
  • Loading branch information
mrkulik authored Jul 22, 2024
2 parents 8e25b3e + 48f3b63 commit ff5d5e0
Show file tree
Hide file tree
Showing 15 changed files with 628 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ struct Price {
return Price.stringWithoutSymbol(from: value)
}

var germanStringWithoutCurrencyCode: String? {
var localizedStringWithoutCurrencyCode: String? {
return Price.localizedStringWithoutCurrencyCode(from: value)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
//
// DefaultSkontoBottomNavigationBar.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import UIKit
import GiniCaptureSDK

final class DefaultSkontoBottomNavigationBar: UIView {
private lazy var configuration = GiniBankConfiguration.shared

private lazy var proceedButton: MultilineTitleButton = {
let button = MultilineTitleButton()
button.translatesAutoresizingMaskIntoConstraints = false
button.configure(with: configuration.primaryButtonConfiguration)
button.titleLabel?.font = configuration.textStyleFonts[.bodyBold]
let title = NSLocalizedStringPreferredGiniBankFormat("ginibank.skonto.paybutton.title",
comment: "Continue to pay")
button.setTitle(title, for: .normal)
button.accessibilityValue = title
button.setContentHuggingPriority(.defaultLow, for: .horizontal)
button.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
button.addTarget(self, action: #selector(proceedButtonClicked), for: .touchUpInside)
return button
}()

// MARK: Temporary remove help action
// private lazy var helpButton: GiniBarButton = {
// let button = GiniBarButton(ofType: .help)
// button.buttonView.translatesAutoresizingMaskIntoConstraints = false
// button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
// button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
// button.addAction(self, #selector(helpButtonClicked))
// return button
// }()

private lazy var backButton: GiniBarButton = {
let button = GiniBarButton(ofType: .back(title: ""))
button.buttonView.translatesAutoresizingMaskIntoConstraints = false
button.setContentHuggingPriority(.defaultHigh, for: .horizontal)
button.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
button.addAction(self, #selector(backButtonClicked))
return button
}()

private lazy var totalLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = configuration.textStyleFonts[.body]
label.textColor = .giniColorScheme().text.primary.uiColor()
label.adjustsFontForContentSizeCategory = true
label.setContentHuggingPriority(.required, for: .vertical)
let text = NSLocalizedStringPreferredGiniBankFormat("ginibank.skonto.total.title",
comment: "Total")
label.text = text
label.accessibilityValue = text
return label
}()

private lazy var totalValueLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.adjustsFontForContentSizeCategory = true
label.font = configuration.textStyleFonts[.title1Bold]
label.textColor = .giniColorScheme().text.primary.uiColor()
label.setContentHuggingPriority(.required, for: .vertical)
return label
}()

private lazy var skontoBadgeLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = configuration.textStyleFonts[.caption1]
label.textColor = .giniColorScheme().chips.textSuggestionEnabled.uiColor()
label.numberOfLines = 0
label.adjustsFontForContentSizeCategory = true
return label
}()

private lazy var skontoBadgeView: UIView = {
let view = UIView()
view.backgroundColor = .giniColorScheme().chips.suggestionEnabled.uiColor()
view.layer.cornerRadius = Constants.cornerRadius
view.layer.masksToBounds = true
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(skontoBadgeLabel)
return view
}()

private lazy var dividerView: UIView = {
let dividerView = UIView()
dividerView.backgroundColor = .giniColorScheme().bg.divider.uiColor()
dividerView.translatesAutoresizingMaskIntoConstraints = false
return dividerView
}()

private var proceedAction: (() -> Void)?
// MARK: Temporary remove help action
// private var helpAction: (() -> Void)?
private var backAction: (() -> Void)?

init(proceedAction: (() -> Void)?,
backAction: (() -> Void)?) {
self.proceedAction = proceedAction
self.backAction = backAction
super.init(frame: .zero)
setupView()
setupConstraints()
}

required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

func updatePrice(with price: String?) {
totalValueLabel.text = price
totalValueLabel.accessibilityValue = price
}

func updateDiscountValue(with discount: String?) {
skontoBadgeLabel.text = discount
skontoBadgeLabel.accessibilityValue = discount
}

func updateDiscountBadge(enabled: Bool) {
skontoBadgeView.isHidden = !enabled
}

private func setupView() {
backgroundColor = .giniColorScheme().bg.surface.uiColor()

addSubview(proceedButton)
addSubview(totalLabel)
addSubview(totalValueLabel)
addSubview(skontoBadgeView)
addSubview(backButton.buttonView)
addSubview(dividerView)
skontoBadgeView.addSubview(skontoBadgeLabel)
}

private func setupConstraints() {
NSLayoutConstraint.activate([
dividerView.topAnchor.constraint(equalTo: topAnchor),
dividerView.leadingAnchor.constraint(equalTo: leadingAnchor),
dividerView.trailingAnchor.constraint(equalTo: trailingAnchor),
dividerView.heightAnchor.constraint(equalToConstant: Constants.dividerViewHeight),

totalLabel.topAnchor.constraint(equalTo: dividerView.bottomAnchor, constant: Constants.padding),
totalLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.padding),

totalValueLabel.topAnchor.constraint(equalTo: totalLabel.bottomAnchor),
totalValueLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.padding),

skontoBadgeView.centerYAnchor.constraint(equalTo: totalValueLabel.centerYAnchor),
skontoBadgeView.leadingAnchor.constraint(equalTo: totalValueLabel.trailingAnchor,
constant: Constants.badgeSpacing),
skontoBadgeView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor,
constant: -Constants.padding),

skontoBadgeLabel.topAnchor.constraint(equalTo: skontoBadgeView.topAnchor,
constant: Constants.badgeVerticalPadding),
skontoBadgeLabel.bottomAnchor.constraint(equalTo: skontoBadgeView.bottomAnchor,
constant: -Constants.badgeVerticalPadding),
skontoBadgeLabel.leadingAnchor.constraint(equalTo: skontoBadgeView.leadingAnchor,
constant: Constants.badgeHorizontalPadding),
skontoBadgeLabel.trailingAnchor.constraint(equalTo: skontoBadgeView.trailingAnchor,
constant: -Constants.badgeHorizontalPadding),

backButton.buttonView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: Constants.padding),
backButton.buttonView.centerYAnchor.constraint(equalTo: proceedButton.centerYAnchor),

proceedButton.topAnchor.constraint(equalTo: totalValueLabel.bottomAnchor,
constant: Constants.verticalPadding),
proceedButton.leadingAnchor.constraint(equalTo: backButton.buttonView.trailingAnchor),
proceedButton.centerXAnchor.constraint(equalTo: centerXAnchor),
proceedButton.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor,
constant: -Constants.verticalPadding),
proceedButton.heightAnchor.constraint(equalToConstant: Constants.payButtonHeight)
])
}

@objc private func proceedButtonClicked() {
proceedAction?()
}

// MARK: Temporary remove help action
// @objc private func helpButtonClicked() {
// helpAction?()
// }

@objc private func backButtonClicked() {
backAction?()
}
}

extension DefaultSkontoBottomNavigationBar {
private enum Constants {
static let padding: CGFloat = 24
static let verticalPadding: CGFloat = 16
static let payButtonHeight: CGFloat = 50
static let dividerViewHeight: CGFloat = 1
static let badgeHorizontalPadding: CGFloat = 6
static let badgeVerticalPadding: CGFloat = 2
static let badgeSpacing: CGFloat = 12
static let cornerRadius: CGFloat = 4
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//
// DefaultSkontoNavigationBarBottomAdapter.swift
//
// Copyright © 2024 Gini GmbH. All rights reserved.
//

import UIKit

final class DefaultSkontoNavigationBarBottomAdapter: SkontoNavigationBarBottomAdapter {

private var proceedButtonCallback: (() -> Void)?
// MARK: Temporary remove help action
// private var helpButtonCallback: (() -> Void)?
private var backButtonCallback: (() -> Void)?
private var view: DefaultSkontoBottomNavigationBar?

func setProceedButtonClickedActionCallback(_ callback: @escaping () -> Void) {
proceedButtonCallback = callback
}

// MARK: Temporary remove help action
// func setHelpButtonClickedActionCallback(_ callback: @escaping () -> Void) {
// helpButtonCallback = callback
// }

func setBackButtonClickedActionCallback(_ callback: @escaping () -> Void) {
backButtonCallback = callback
}

func updateTotalPrice(priceWithCurrencyCode price: String?) {
view?.updatePrice(with: price)
}

func updateDiscountValue(with discount: String?) {
view?.updateDiscountValue(with: discount)
}

func updateDiscountBadge(enabled: Bool) {
view?.updateDiscountBadge(enabled: enabled)
}

func injectedView() -> UIView {
let navigationBar = DefaultSkontoBottomNavigationBar(proceedAction: proceedButtonCallback,
backAction: backButtonCallback)
view = navigationBar
return navigationBar
}

func onDeinit() {
proceedButtonCallback = nil
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,9 @@ public protocol SkontoNavigationBarBottomAdapter: InjectedViewAdapter {
*
* - Parameter price: A string which contains the currency and the price
*/
func updateTotalPrice(priceWithCurrencySymbol price: String?)

/**
* Set the proceed button state. Called when state of the button should be changed
*
* - Parameter enabled: A bool value to reflect the state of the button
*/
func updateProceedButtonState(enabled: Bool)
func updateTotalPrice(priceWithCurrencyCode price: String?)

// TODO: To specify what we exactly need to expose
/**
* Set the discount value on the bottom navigation bar. Called when Skonto applies
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,18 @@ public class SkontoViewController: UIViewController {
comment: "Back")
edgesForExtendedLayout = []
view.backgroundColor = .giniColorScheme().bg.background.uiColor()
if configuration.bottomNavigationBarEnabled {
let cancelButton = GiniBarButton(ofType: .back(title: backButtonTitle))
cancelButton.addAction(self, #selector(backButtonTapped))
navigationItem.rightBarButtonItem = cancelButton.barButton
navigationItem.hidesBackButton = true
} else {
if !configuration.bottomNavigationBarEnabled {
// MARK: Temporary remove help button
// let helpButton = GiniBarButton(ofType: .help)
// helpButton.addAction(self, #selector(helpButtonTapped))
// navigationItem.rightBarButtonItem = helpButton.barButton

let cancelButton = GiniBarButton(ofType: .back(title: backButtonTitle))
cancelButton.addAction(self, #selector(backButtonTapped))
navigationItem.leftBarButtonItem = cancelButton.barButton
let backButton = GiniBarButton(ofType: .back(title: backButtonTitle))
backButton.addAction(self, #selector(backButtonTapped))
navigationItem.leftBarButtonItem = backButton.barButton
} else {
navigationItem.hidesBackButton = true
}

view.addSubview(scrollView)
scrollView.addSubview(stackView)
stackView.addArrangedSubview(appliedGroupView)
Expand Down Expand Up @@ -229,11 +225,10 @@ public class SkontoViewController: UIViewController {

private func setupBottomNavigationBar() {
guard configuration.bottomNavigationBarEnabled else { return }

if let bottomBarAdapter = configuration.skontoNavigationBarBottomAdapter {
navigationBarBottomAdapter = bottomBarAdapter
} else {
// TODO: Implement default navigation bar when design will be available
navigationBarBottomAdapter = DefaultSkontoNavigationBarBottomAdapter()
}

navigationBarBottomAdapter?.setProceedButtonClickedActionCallback { [weak self] in
Expand All @@ -245,6 +240,10 @@ public class SkontoViewController: UIViewController {
// self?.helpButtonTapped()
// }

navigationBarBottomAdapter?.setBackButtonClickedActionCallback { [weak self] in
self?.backButtonTapped()
}

if let navigationBar = navigationBarBottomAdapter?.injectedView() {
bottomNavigationBar = navigationBar
view.addSubview(navigationBar)
Expand All @@ -262,11 +261,24 @@ public class SkontoViewController: UIViewController {
}

private func bindViewModel() {
configure()
viewModel.addStateChangeHandler { [weak self] in
guard let self else { return }
self.configure()
}
viewModel.endEditingAction = {
self.endEditing()
}
}

private func configure() {
let isSkontoApplied = viewModel.isSkontoApplied
navigationBarBottomAdapter?.updateDiscountBadge(enabled: isSkontoApplied)
navigationBarBottomAdapter?.updateDiscountValue(with: viewModel.localizedDiscountString)
let localizedStringWithCurrencyCode = viewModel.totalPrice.localizedStringWithCurrencyCode
navigationBarBottomAdapter?.updateTotalPrice(priceWithCurrencyCode: localizedStringWithCurrencyCode)
}

// MARK: Temporary remove help action
// @objc private func helpButtonTapped() {
// viewModel.helpButtonTapped()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ class SkontoViewModel {
}
}

var localizedDiscountString: String {
return String.localizedStringWithFormat(
NSLocalizedStringPreferredGiniBankFormat("ginibank.skonto.total.amount.skonto",
comment: "%@ Skonto discount"),
skontoFormattedPercentageDiscounted
)
}

weak var delegate: SkontoViewModelDelegate?

init(isSkontoApplied: Bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class SkontoAmountView: UIView {
price: Price,
isEditable: Bool = true) {
self.titleLabelText = title
self.textFieldInitialText = price.germanStringWithoutCurrencyCode ?? ""
self.textFieldInitialText = price.localizedStringWithCurrencyCode ?? ""
self.currencyLabelText = price.currencyCode
self.isEditable = isEditable
super.init(frame: .zero)
Expand Down Expand Up @@ -124,7 +124,7 @@ class SkontoAmountView: UIView {

func configure(isEditable: Bool, price: Price) {
if isEditable {
textField.text = price.germanStringWithoutCurrencyCode ?? ""
textField.text = price.localizedStringWithoutCurrencyCode ?? ""
} else {
textField.text = price.localizedStringWithCurrencyCode ?? ""
}
Expand Down
Loading

0 comments on commit ff5d5e0

Please sign in to comment.