Skip to content

Commit

Permalink
Fixed search bar transition
Browse files Browse the repository at this point in the history
    - Redesigned search bar with default UISearchController to meet HIG requirements and simplify code
    - Fixed missing cancel button on iPad
    - Removed account property (UUID type) from token searching filter

Fixes #229
  • Loading branch information
igor2890 authored and justin-stephenson committed Jul 15, 2022
1 parent 24bc30c commit 62951dc
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 129 deletions.
140 changes: 33 additions & 107 deletions FreeOTP/TokensViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import UIKit

class TokensViewController : UICollectionViewController, UICollectionViewDelegateFlowLayout, UIPopoverPresentationControllerDelegate,
UICollectionViewDragDelegate, UICollectionViewDropDelegate {

let defaultIcon = UIImage(contentsOfFile: Bundle.main.path(forResource: "default", ofType: "png")!)
fileprivate var lastPath: IndexPath? = nil
fileprivate var store = TokenStore()
Expand All @@ -33,29 +34,20 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
@IBOutlet weak var addButton: UIBarButtonItem!

private lazy var emptyStateView = EmptyStateView()

// the search bar
let searchBar = UISearchBar()

// bar buttons
private var scanQrCodeButton = UIBarButtonItem()
private var manualAddButton = UIBarButtonItem()
private var appInfoButton = UIBarButtonItem()

var searchController: UISearchController!

// the tokens array
private var tokensArray: [Token]! = [] // contains all the tokens as loaded from the store
private var searchedTokensArray: [Token]! = [] // contains the filtered tokens

// search params
private var searchingTokens = false


override func numberOfSections(in collectionView: UICollectionView) -> Int {
return 1
}

override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

return searchingTokens ? searchedTokensArray.count : store.count
}

Expand Down Expand Up @@ -191,14 +183,13 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
let width = (collectionViewLayout as! UICollectionViewFlowLayout).columnWidth(collectionView, numCols: numCols)
return CGSize(width: width, height: width / 3.25);
}

// Drag and drop delegate methods
func collectionView(_ collectionView: UICollectionView, itemsForBeginning session: UIDragSession, at indexPath: IndexPath) -> [UIDragItem] {
// no need for drag and drop when searching
if searchingTokens == false {
if let token = store.load(indexPath.row) {
let itemProvider = NSItemProvider(object: token)

let dragItem = UIDragItem(itemProvider: itemProvider)
return [dragItem]
} else {
Expand All @@ -207,7 +198,6 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
} else {
return []
}

}

func collectionView(_ collectionView: UICollectionView, dropSessionDidUpdate session: UIDropSession, withDestinationIndexPath destinationIndexPath: IndexPath?) -> UICollectionViewDropProposal {
Expand All @@ -228,7 +218,6 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
}
}
}


@objc func handleSwipe(_ gestureRecognizer: UISwipeGestureRecognizer) {
if gestureRecognizer.state == .ended {
Expand All @@ -254,9 +243,6 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
self.collectionView.deleteItems(at: array)
}

// reload the search button
self.showSearchButton()

// also reload the tokens array
self.tokensArray = self.store.getAllTokens()
}
Expand All @@ -275,9 +261,7 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
if let popoverController = actionSheetController.popoverPresentationController {
popoverController.sourceView = self.view
popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.maxY, width: 0, height: 0)

}

self.present(actionSheetController, animated: true)
})
}
Expand All @@ -299,6 +283,8 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
aboutButton.image = icon.getFontAwesomeIcon(faName: "fa-info-circle", faType: .solid)
}

scanButton.accessibilityIdentifier = "scanButton"

if #available(iOS 13.0, *) {
addButton.image = UIImage(systemName: "plus")
} else {
Expand Down Expand Up @@ -332,25 +318,11 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat

let swipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(self.handleSwipe))
collectionView?.addGestureRecognizer(swipeGesture)

// show the search bar only if there are items to be searched
showSearchButton()

}

func showSearchButton() {

let tokenCount = searchingTokens ? searchedTokensArray.count : store.count

if tokenCount > 0 {
configureSearchBar()
} else {
navigationItem.leftBarButtonItem = nil
}
}

override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
configureSearchBar()
reloadData()
}

Expand All @@ -362,62 +334,26 @@ class TokensViewController : UICollectionViewController, UICollectionViewDelegat
}
}

func configureSearchBar() {
// init search bar
searchBar.sizeToFit()
searchBar.delegate = self
searchBar.tintColor = UIColor.app.accent
searchBar.isAccessibilityElement = false
searchBar.accessibilityIdentifier = "search-bar"
searchBar.placeholder = "Search Tokens"
private func configureSearchBar() {
searchController = UISearchController(searchResultsController: nil)
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
definesPresentationContext = true
searchController.hidesNavigationBarDuringPresentation = true

// style the search bar
navigationController?.navigationBar.isTranslucent = false
//navigationController?.navigationBar.barStyle = .black
searchController.searchBar.delegate = self
searchController.searchBar.sizeToFit()
searchController.searchBar.tintColor = UIColor.app.accent
searchController.searchBar.isAccessibilityElement = false
searchController.searchBar.placeholder = "Search Tokens"

// set the button refs for later
self.scanQrCodeButton = self.scanButton
self.manualAddButton = self.addButton
self.appInfoButton = self.aboutButton

// add the search button
setSearchButton()

}

private func setSearchButton(){
let barButtonItem = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(handleShowSearchBar))
barButtonItem.accessibilityIdentifier = "navbarSearchItem"
navigationItem.leftBarButtonItem = barButtonItem
navigationItem.searchController = searchController
}

// helper func to return token at a certain position depending on the state of the UICollectionView
private func getTokenAtIndex(tokenIndex: Int) -> Token? {
return searchingTokens ? searchedTokensArray[tokenIndex] : store.load(tokenIndex)
}

@objc func handleShowSearchBar() {
search(shouldShow: true)
searchBar.becomeFirstResponder()
}

func showBarButtons(shouldShow: Bool){

if shouldShow {
setSearchButton()
navigationItem.rightBarButtonItems = [self.appInfoButton, self.scanQrCodeButton, self.manualAddButton]
} else {
navigationItem.rightBarButtonItems = nil
navigationItem.leftBarButtonItem = nil
}

}
func search(shouldShow: Bool){
showBarButtons(shouldShow: !shouldShow)
searchBar.showsCancelButton = shouldShow
navigationItem.titleView = shouldShow ? searchBar : nil
}

}

extension TokensViewController: TokenCellDelegate {
Expand All @@ -427,37 +363,27 @@ extension TokensViewController: TokenCellDelegate {
}
}

extension TokensViewController: UISearchBarDelegate {

func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
tokensArray = store.getAllTokens()
}


func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
search(shouldShow: false)
searchingTokens = false
tokensArray.removeAll()
searchedTokensArray.removeAll()
reloadData()

searchBar.text = ""
}

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {

extension TokensViewController: UISearchResultsUpdating {
func updateSearchResults(for searchController: UISearchController) {
let searchText = searchController.searchBar.text!
if searchText.isEmpty {
tokensArray = store.getAllTokens()
searchedTokensArray = tokensArray
} else {
searchingTokens = true
searchedTokensArray = tokensArray.filter {
$0.issuer.lowercased().contains(searchText.lowercased()) == true
|| $0.label.lowercased().contains(searchText.lowercased()) == true
|| $0.account.lowercased().contains(searchText.lowercased()) == true
$0.issuer.lowercased().contains(searchText.lowercased())
|| $0.label.lowercased().contains(searchText.lowercased())
}
}

reloadData()
}

}

extension TokensViewController: UISearchBarDelegate {
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
searchingTokens = false
tokensArray.removeAll()
searchedTokensArray.removeAll()
}
}
40 changes: 18 additions & 22 deletions FreeOTPUITests/FreeOTPUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,26 +20,27 @@ class FreeOTPUITests: XCTestCase {

app.launch()

// check the search icon is set
let leftNavBarSearchButton = app.navigationBars.buttons["navbarSearchItem"]
XCTAssert(leftNavBarSearchButton.exists)
leftNavBarSearchButton.tap()
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
XCTAssert(collectionView.exists)
collectionView.swipeDown()

// check if the search bar exists
let searchBarElement = app.descendants(matching: .any).matching(identifier: "search-bar").firstMatch
let searchBarElement = app.searchFields.firstMatch
XCTAssert(searchBarElement.exists)
searchBarElement.tap()

// check if the filtering works as expected
app.typeText("test")
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
XCTAssert(collectionView.exists)

XCTAssert(collectionView.cells.count > 0)

// filtering should fail for a random strting
app.typeText("blah " + String(arc4random()) + " blah")
XCTAssertFalse(collectionView.cells.count > 0)

let cancelButton = app.buttons["Cancel"].firstMatch
XCTAssert(cancelButton.exists)
cancelButton.tap()
}

func testManualAdd() throws {
Expand All @@ -55,17 +56,15 @@ class FreeOTPUITests: XCTestCase {
app.launch()

//search for the number of old tokens with the issuer name under test
let searchButton = app.navigationBars.buttons["navbarSearchItem"]
XCTAssert(searchButton.exists)
searchButton.tap()
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
XCTAssert(collectionView.exists)
collectionView.swipeDown()

let searchBarElement = app.descendants(matching: .any).matching(identifier: "search-bar").firstMatch
let searchBarElement = app.searchFields.firstMatch
XCTAssert(searchBarElement.exists)
searchBarElement.tap()

app.typeText(testedIssuerName)
let collectionView = app.otherElements.collectionViews.element(boundBy: 0)
XCTAssert(collectionView.exists)
testedIssuerTokensCount = collectionView.cells.count

let cancelButton = app.buttons["Cancel"].firstMatch
Expand Down Expand Up @@ -180,19 +179,16 @@ class FreeOTPUITests: XCTestCase {
sleep(1)

//detecting an increase in the number of cells with the issuer name under test
let secSearchButton = app.navigationBars.buttons["navbarSearchItem"]
XCTAssert(secSearchButton.exists)
secSearchButton.tap()

let secSearchBarElement = app.descendants(matching: .any).matching(identifier: "search-bar").firstMatch
XCTAssert(secSearchBarElement.exists)
secSearchBarElement.tap()
XCTAssert(collectionView.exists)
collectionView.swipeDown()
XCTAssert(searchBarElement.exists)
searchBarElement.tap()

app.typeText(testedIssuerName)
let secCollectionView = app.otherElements.collectionViews.element(boundBy: 0)
XCTAssert(secCollectionView.exists)
XCTAssert(secCollectionView.cells.count == testedIssuerTokensCount + 1)

//if error - try to remove all "blah123" tokens first
XCTAssert(secCollectionView.cells.count == testedIssuerTokensCount + 1)
}

}

0 comments on commit 62951dc

Please sign in to comment.