2017-01-19 21:15:47 +08:00
//
2017-02-03 13:01:41 +08:00
// P a s s w o r d s V i e w C o n t r o l l e r . s w i f t
2017-01-19 21:15:47 +08:00
// p a s s
//
2017-02-03 13:01:41 +08:00
// C r e a t e d b y M i n g s h e n S u n o n 3 / 2 / 2 0 1 7 .
2017-01-19 21:15:47 +08:00
// C o p y r i g h t © 2 0 1 7 B o b S u n . A l l r i g h t s r e s e r v e d .
//
import UIKit
import Result
import SVProgressHUD
2017-02-06 10:55:24 +08:00
import SwiftyUserDefaults
2017-01-19 21:15:47 +08:00
2017-02-03 13:01:41 +08:00
class PasswordsViewController : UIViewController , UITableViewDataSource , UITableViewDelegate {
2017-01-19 21:15:47 +08:00
private var passwordEntities : [ PasswordEntity ] ?
2017-01-23 12:48:20 +08:00
var filteredPasswordEntities = [ PasswordEntity ] ( )
2017-02-02 15:03:34 +08:00
var sections : [ ( index : Int , length : Int , title : String ) ] = Array ( )
2017-02-03 13:01:41 +08:00
var searchActive : Bool = false
let searchController = UISearchController ( searchResultsController : nil )
2017-02-04 11:30:57 +08:00
lazy var refreshControl : UIRefreshControl = {
let refreshControl = UIRefreshControl ( )
refreshControl . addTarget ( self , action : #selector ( PasswordsViewController . handleRefresh ( _ : ) ) , for : UIControlEvents . valueChanged )
return refreshControl
} ( )
let searchBarView = UIView ( frame : CGRect ( x : 0 , y : 64 , width : UIScreen . main . bounds . width , height : 44 ) )
2017-02-03 13:01:41 +08:00
@IBOutlet weak var tableView : UITableView !
2017-01-23 13:43:06 +08:00
2017-02-04 11:47:57 +08:00
func syncPasswords ( ) {
2017-02-04 15:23:14 +08:00
SVProgressHUD . show ( withStatus : " Sync Password Store " )
2017-02-04 11:35:28 +08:00
DispatchQueue . global ( qos : . userInitiated ) . async { [ unowned self ] in
2017-02-04 14:24:59 +08:00
do {
try PasswordStore . shared . pullRepository ( transferProgressBlock : { ( git_transfer_progress , stop ) in
DispatchQueue . main . async {
SVProgressHUD . showProgress ( Float ( git_transfer_progress . pointee . received_objects ) / Float ( git_transfer_progress . pointee . total_objects ) , status : " Pull Remote Repository " )
}
} )
2017-01-23 17:36:10 +08:00
DispatchQueue . main . async {
2017-02-04 15:15:57 +08:00
self . passwordEntities = PasswordStore . shared . fetchPasswordEntityCoreData ( )
self . reloadTableView ( data : self . passwordEntities ! )
2017-02-06 19:13:33 +08:00
Defaults [ . lastUpdatedTime ] = Date ( )
2017-01-23 17:36:10 +08:00
SVProgressHUD . showSuccess ( withStatus : " Done " )
SVProgressHUD . dismiss ( withDelay : 1 )
}
2017-02-04 14:24:59 +08:00
} catch {
DispatchQueue . main . async {
SVProgressHUD . showError ( withStatus : error . localizedDescription )
SVProgressHUD . dismiss ( withDelay : 3 )
}
2017-01-23 16:29:36 +08:00
}
}
}
2017-01-19 21:15:47 +08:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
passwordEntities = PasswordStore . shared . fetchPasswordEntityCoreData ( )
2017-02-03 13:20:03 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( PasswordsViewController . actOnPasswordUpdatedNotification ) , name : NSNotification . Name ( rawValue : " passwordUpdated " ) , object : nil )
2017-02-07 16:45:14 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( PasswordsViewController . actOnPasswordStoreErasedNotification ) , name : NSNotification . Name ( rawValue : " passwordStoreErased " ) , object : nil )
2017-02-03 13:01:41 +08:00
generateSections ( item : passwordEntities ! )
tableView . delegate = self
tableView . dataSource = self
2017-01-23 12:48:20 +08:00
searchController . searchResultsUpdater = self
searchController . dimsBackgroundDuringPresentation = false
2017-02-07 17:24:23 +08:00
searchController . searchBar . isTranslucent = false
searchController . searchBar . backgroundColor = UIColor . gray
2017-02-03 13:01:41 +08:00
searchController . searchBar . sizeToFit ( )
2017-01-23 12:48:20 +08:00
definesPresentationContext = true
2017-02-03 13:01:41 +08:00
searchBarView . addSubview ( searchController . searchBar )
view . addSubview ( searchBarView )
2017-02-04 11:30:57 +08:00
tableView . insertSubview ( refreshControl , at : 0 )
2017-02-06 10:55:24 +08:00
SVProgressHUD . setDefaultMaskType ( . black )
2017-02-06 19:13:33 +08:00
updateRefreshControlTitle ( )
2017-02-02 15:03:34 +08:00
}
2017-02-03 14:20:52 +08:00
override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
if let path = tableView . indexPathForSelectedRow {
tableView . deselectRow ( at : path , animated : false )
}
}
2017-02-07 13:23:18 +08:00
override func viewDidLayoutSubviews ( ) {
super . viewDidLayoutSubviews ( )
searchBarView . frame = CGRect ( x : 0 , y : navigationController ! . navigationBar . bounds . size . height + UIApplication . shared . statusBarFrame . height , width : UIScreen . main . bounds . width , height : 44 )
searchController . searchBar . sizeToFit ( )
}
2017-02-03 13:01:41 +08:00
func numberOfSections ( in tableView : UITableView ) -> Int {
2017-02-02 15:03:34 +08:00
return sections . count
}
2017-02-03 13:01:41 +08:00
func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
return sections [ section ] . length
}
func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
let cell = tableView . dequeueReusableCell ( withIdentifier : " passwordTableViewCell " , for : indexPath )
var password : PasswordEntity
let index = sections [ indexPath . section ] . index + indexPath . row
if searchController . isActive && searchController . searchBar . text != " " {
password = filteredPasswordEntities [ index ]
} else {
password = passwordEntities ! [ index ]
}
cell . textLabel ? . text = password . name
return cell
}
func tableView ( _ tableView : UITableView , titleForHeaderInSection section : Int ) -> String ? {
return sections [ section ] . title
}
func sectionIndexTitles ( for tableView : UITableView ) -> [ String ] ? {
return sections . map { $0 . title }
}
func tableView ( _ tableView : UITableView , sectionForSectionIndexTitle title : String , at index : Int ) -> Int {
return index
}
2017-02-04 11:30:57 +08:00
func tableView ( _ tableView : UITableView , accessoryButtonTappedForRowWith indexPath : IndexPath ) {
let index = sections [ indexPath . section ] . index + indexPath . row
let password : PasswordEntity
if searchController . isActive && searchController . searchBar . text != " " {
password = filteredPasswordEntities [ index ]
} else {
password = passwordEntities ! [ index ]
}
2017-02-06 14:28:57 +08:00
do {
let decryptedPassword = try password . decrypt ( ) !
UIPasteboard . general . string = decryptedPassword . password
} catch {
print ( error )
}
2017-02-04 11:30:57 +08:00
}
2017-02-02 15:03:34 +08:00
func generateSections ( item : [ PasswordEntity ] ) {
sections . removeAll ( )
if item . count = = 0 {
return
}
var index = 0
for i in 0 . . < item . count {
let name = item [ index ] . name ! . uppercased ( )
let commonPrefix = item [ i ] . name ! . commonPrefix ( with : name , options : . caseInsensitive )
if commonPrefix . characters . count = = 0 {
let firstCharacter = name [ name . startIndex ]
let newSection = ( index : index , length : i - index , title : " \( firstCharacter ) " )
sections . append ( newSection )
index = i
}
}
let name = item [ index ] . name ! . uppercased ( )
let firstCharacter = name [ name . startIndex ]
let newSection = ( index : index , length : item . count - index , title : " \( firstCharacter ) " )
sections . append ( newSection )
2017-01-23 12:48:20 +08:00
}
2017-01-19 21:15:47 +08:00
func actOnPasswordUpdatedNotification ( ) {
passwordEntities = PasswordStore . shared . fetchPasswordEntityCoreData ( )
2017-02-04 11:30:57 +08:00
reloadTableView ( data : passwordEntities ! )
2017-01-19 21:15:47 +08:00
print ( " actOnPasswordUpdatedNotification " )
}
2017-02-06 10:55:24 +08:00
2017-02-07 16:45:14 +08:00
func actOnPasswordStoreErasedNotification ( ) {
passwordEntities = PasswordStore . shared . fetchPasswordEntityCoreData ( )
reloadTableView ( data : passwordEntities ! )
print ( " actOnPasswordErasedNotification " )
}
2017-02-06 10:55:24 +08:00
override func shouldPerformSegue ( withIdentifier identifier : String , sender : Any ? ) -> Bool {
if identifier = = " showPasswordDetail " {
if Defaults [ . pgpKeyID ] = = " " {
2017-02-06 11:11:26 +08:00
let alert = UIAlertController ( title : " Cannot Show Password " , message : " PGP Key is not set. Please set your PGP Key first. " , preferredStyle : UIAlertControllerStyle . alert )
2017-02-06 10:55:24 +08:00
alert . addAction ( UIAlertAction ( title : " OK " , style : UIAlertActionStyle . default , handler : nil ) )
self . present ( alert , animated : true , completion : nil )
if let s = sender as ? UITableViewCell {
let selectedIndexPath = tableView . indexPath ( for : s ) !
tableView . deselectRow ( at : selectedIndexPath , animated : true )
}
return false
}
}
return true
}
2017-02-03 13:01:41 +08:00
2017-01-22 01:42:36 +08:00
override func prepare ( for segue : UIStoryboardSegue , sender : Any ? ) {
if segue . identifier = = " showPasswordDetail " {
2017-02-02 21:04:31 +08:00
if let viewController = segue . destination as ? PasswordDetailTableViewController {
2017-02-02 15:03:34 +08:00
let selectedIndexPath = self . tableView . indexPath ( for : sender as ! UITableViewCell ) !
let index = sections [ selectedIndexPath . section ] . index + selectedIndexPath . row
2017-02-06 14:28:57 +08:00
let passwordEntity : PasswordEntity
2017-01-23 12:48:20 +08:00
if searchController . isActive && searchController . searchBar . text != " " {
2017-02-06 14:28:57 +08:00
passwordEntity = filteredPasswordEntities [ index ]
2017-01-23 12:48:20 +08:00
} else {
2017-02-06 14:28:57 +08:00
passwordEntity = passwordEntities ! [ index ]
2017-01-23 12:48:20 +08:00
}
2017-02-06 14:28:57 +08:00
viewController . passwordEntity = passwordEntity
2017-02-06 21:53:54 +08:00
let passwordCategoryEntities = PasswordStore . shared . fetchPasswordCategoryEntityCoreData ( password : passwordEntity )
viewController . passwordCategoryEntities = passwordCategoryEntities
2017-01-22 01:42:36 +08:00
}
}
}
2017-02-04 11:30:57 +08:00
2017-02-03 13:01:41 +08:00
func filterContentForSearchText ( searchText : String , scope : String = " All " ) {
filteredPasswordEntities = passwordEntities ! . filter { password in
return password . name ! . lowercased ( ) . contains ( searchText . lowercased ( ) )
}
if searchController . isActive && searchController . searchBar . text != " " {
2017-02-04 11:30:57 +08:00
reloadTableView ( data : filteredPasswordEntities )
2017-02-03 13:01:41 +08:00
} else {
2017-02-04 11:30:57 +08:00
reloadTableView ( data : passwordEntities ! )
2017-02-03 13:01:41 +08:00
}
2017-02-04 11:30:57 +08:00
}
2017-02-06 19:13:33 +08:00
func updateRefreshControlTitle ( ) {
var atribbutedTitle = " Pull to Sync Password Store "
if let lastUpdatedTime = Defaults [ . lastUpdatedTime ] {
let formatter = DateFormatter ( )
formatter . dateStyle = . long
formatter . timeStyle = . short
let dateString = formatter . string ( from : lastUpdatedTime )
atribbutedTitle = " Last Sync: \( dateString ) "
}
refreshControl . attributedTitle = NSAttributedString ( string : atribbutedTitle )
}
2017-02-04 11:30:57 +08:00
func reloadTableView ( data : [ PasswordEntity ] ) {
generateSections ( item : data )
2017-02-03 13:01:41 +08:00
tableView . reloadData ( )
2017-02-06 19:13:33 +08:00
updateRefreshControlTitle ( )
2017-02-03 13:01:41 +08:00
}
2017-02-04 11:30:57 +08:00
func handleRefresh ( _ refreshControl : UIRefreshControl ) {
2017-02-04 11:47:57 +08:00
syncPasswords ( )
2017-02-04 11:30:57 +08:00
refreshControl . endRefreshing ( )
}
2017-01-19 21:15:47 +08:00
}
2017-01-23 16:29:36 +08:00
2017-02-03 13:01:41 +08:00
extension PasswordsViewController : UISearchResultsUpdating {
2017-01-23 16:29:36 +08:00
func updateSearchResults ( for searchController : UISearchController ) {
filterContentForSearchText ( searchText : searchController . searchBar . text ! )
}
}