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 SVProgressHUD
2017-06-13 11:42:49 +08:00
import passKit
2017-01-19 21:15:47 +08:00
2017-04-03 22:40:17 +08:00
fileprivate class PasswordsTableEntry : NSObject {
2017-09-23 16:29:03 +08:00
@objc var title : String
2017-03-02 14:51:40 +08:00
var isDir : Bool
var passwordEntity : PasswordEntity ?
2017-04-03 22:40:17 +08:00
init ( title : String , isDir : Bool , passwordEntity : PasswordEntity ? ) {
self . title = title
self . isDir = isDir
self . passwordEntity = passwordEntity
}
2017-03-02 14:51:40 +08:00
}
2017-04-04 22:48:39 -07:00
class PasswordsViewController : UIViewController , UITableViewDataSource , UITableViewDelegate , UITabBarControllerDelegate , UISearchBarDelegate {
2017-03-02 14:51:40 +08:00
private var passwordsTableEntries : [ PasswordsTableEntry ] = [ ]
2017-04-04 22:48:39 -07:00
private var passwordsTableAllEntries : [ PasswordsTableEntry ] = [ ]
2017-03-02 14:51:40 +08:00
private var filteredPasswordsTableEntries : [ PasswordsTableEntry ] = [ ]
private var parentPasswordEntity : PasswordEntity ? = nil
2017-03-24 21:53:07 +08:00
private let passwordStore = PasswordStore . shared
2017-03-02 22:55:41 +08:00
private var tapTabBarTime : TimeInterval = 0
2017-03-02 14:51:40 +08:00
2017-04-03 22:40:17 +08:00
private var sections = [ ( title : String , entries : [ PasswordsTableEntry ] ) ] ( )
2017-03-24 21:53:07 +08:00
private var searchActive : Bool = false
private lazy var searchController : UISearchController = {
2017-03-02 16:46:23 +08:00
let uiSearchController = UISearchController ( searchResultsController : nil )
uiSearchController . searchResultsUpdater = self
uiSearchController . dimsBackgroundDuringPresentation = false
uiSearchController . searchBar . isTranslucent = false
uiSearchController . searchBar . sizeToFit ( )
return uiSearchController
} ( )
2017-03-24 21:53:07 +08:00
private lazy var syncControl : UIRefreshControl = {
let syncControl = UIRefreshControl ( )
syncControl . addTarget ( self , action : #selector ( handleRefresh ( _ : ) ) , for : UIControlEvents . valueChanged )
return syncControl
2017-02-04 11:30:57 +08:00
} ( )
2017-10-07 00:24:30 -07:00
private lazy var searchBarView : UIView ? = {
guard #available ( iOS 11 , * ) else {
2018-01-14 22:35:21 -08:00
let uiView = UIView ( frame : CGRect ( x : 0 , y : 64 , width : self . view . bounds . width , height : 44 ) )
2017-10-07 00:24:30 -07:00
uiView . addSubview ( self . searchController . searchBar )
return uiView
}
return nil
2017-03-02 16:46:23 +08:00
} ( )
2017-03-24 21:53:07 +08:00
private lazy var backUIBarButtonItem : UIBarButtonItem = {
2017-03-02 14:51:40 +08:00
let backUIBarButtonItem = UIBarButtonItem ( title : " Back " , style : . plain , target : self , action : #selector ( self . backAction ( _ : ) ) )
return backUIBarButtonItem
} ( )
2017-03-23 22:46:50 -07:00
2017-03-24 21:53:07 +08:00
private lazy var transitionFromRight : CATransition = {
2017-03-23 22:46:50 -07:00
let transition = CATransition ( )
transition . type = kCATransitionPush
transition . timingFunction = CAMediaTimingFunction ( name : kCAMediaTimingFunctionEaseInEaseOut )
transition . fillMode = kCAFillModeForwards
transition . duration = 0.25
transition . subtype = kCATransitionFromRight
return transition
} ( )
2017-03-24 21:53:07 +08:00
private lazy var transitionFromLeft : CATransition = {
2017-03-23 22:46:50 -07:00
let transition = CATransition ( )
transition . type = kCATransitionPush
transition . timingFunction = CAMediaTimingFunction ( name : kCAMediaTimingFunctionEaseInEaseOut )
transition . fillMode = kCAFillModeForwards
transition . duration = 0.25
transition . subtype = kCATransitionFromLeft
return transition
} ( )
2017-02-03 13:01:41 +08:00
@IBOutlet weak var tableView : UITableView !
2017-01-23 13:43:06 +08:00
2017-03-02 21:39:48 +08:00
private func initPasswordsTableEntries ( parent : PasswordEntity ? ) {
2017-03-02 16:46:23 +08:00
passwordsTableEntries . removeAll ( )
2017-04-04 22:48:39 -07:00
passwordsTableAllEntries . removeAll ( )
2017-03-02 16:46:23 +08:00
filteredPasswordsTableEntries . removeAll ( )
var passwordEntities = [ PasswordEntity ] ( )
2017-04-04 22:48:39 -07:00
var passwordAllEntities = [ PasswordEntity ] ( )
2017-06-13 11:42:49 +08:00
if SharedDefaults [ . isShowFolderOn ] {
2017-03-16 22:39:03 -07:00
passwordEntities = self . passwordStore . fetchPasswordEntityCoreData ( parent : parent )
2017-03-02 16:46:23 +08:00
} else {
2017-03-16 22:39:03 -07:00
passwordEntities = self . passwordStore . fetchPasswordEntityCoreData ( withDir : false )
2017-03-02 16:46:23 +08:00
}
passwordsTableEntries = passwordEntities . map {
PasswordsTableEntry ( title : $0 . name ! , isDir : $0 . isDir , passwordEntity : $0 )
}
2017-04-04 22:48:39 -07:00
passwordAllEntities = self . passwordStore . fetchPasswordEntityCoreData ( withDir : false )
passwordsTableAllEntries = passwordAllEntities . map {
PasswordsTableEntry ( title : $0 . name ! , isDir : $0 . isDir , passwordEntity : $0 )
}
2017-03-02 21:39:48 +08:00
parentPasswordEntity = parent
2017-03-02 16:46:23 +08:00
}
2017-02-10 22:15:01 +08:00
@IBAction func cancelAddPassword ( segue : UIStoryboardSegue ) {
}
@IBAction func saveAddPassword ( segue : UIStoryboardSegue ) {
if let controller = segue . source as ? AddPasswordTableViewController {
2017-02-11 19:48:47 +08:00
SVProgressHUD . setDefaultMaskType ( . black )
SVProgressHUD . setDefaultStyle ( . light )
SVProgressHUD . show ( withStatus : " Saving " )
DispatchQueue . global ( qos : . userInitiated ) . async {
2017-03-02 17:26:12 +08:00
do {
2017-04-23 10:03:09 -07:00
let _ = try self . passwordStore . add ( password : controller . password ! )
2017-02-11 19:48:47 +08:00
DispatchQueue . main . async {
2017-03-29 00:26:41 +08:00
// w i l l t r i g g e r r e l o a d T a b l e V i e w ( ) b y a n o t i f i c a t i o n
2017-03-02 17:26:12 +08:00
SVProgressHUD . showSuccess ( withStatus : " Done " )
SVProgressHUD . dismiss ( withDelay : 1 )
}
} catch {
DispatchQueue . main . async {
2017-03-16 23:12:31 -07:00
Utils . alert ( title : " Error " , message : error . localizedDescription , controller : self , completion : nil )
2017-02-11 19:48:47 +08:00
}
}
}
2017-02-10 22:15:01 +08:00
}
}
2017-03-02 14:51:40 +08:00
2017-03-24 21:53:07 +08:00
private func syncPasswords ( ) {
2017-04-28 20:33:41 -07:00
guard passwordStore . repositoryExisted ( ) else {
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + . milliseconds ( 800 ) ) {
Utils . alert ( title : " Error " , message : " There is no password store right now. " , controller : self , completion : nil )
}
return
}
2017-02-08 01:29:00 +08:00
SVProgressHUD . setDefaultMaskType ( . black )
SVProgressHUD . setDefaultStyle ( . light )
2017-02-04 15:23:14 +08:00
SVProgressHUD . show ( withStatus : " Sync Password Store " )
2017-03-22 19:07:41 -07:00
let numberOfLocalCommits = self . passwordStore . numberOfLocalCommits ( )
2017-04-28 20:33:41 -07:00
var gitCredential : GitCredential
2017-06-13 11:42:49 +08:00
if SharedDefaults [ . gitAuthenticationMethod ] = = " Password " {
2017-06-13 13:19:18 +08:00
gitCredential = GitCredential ( credential : GitCredential . Credential . http ( userName : SharedDefaults [ . gitUsername ] ! ) )
2017-04-28 20:33:41 -07:00
} else {
gitCredential = GitCredential (
credential : GitCredential . Credential . ssh (
2017-06-13 11:42:49 +08:00
userName : SharedDefaults [ . gitUsername ] ! ,
2017-06-13 13:19:18 +08:00
privateKeyFile : Globals . gitSSHPrivateKeyURL
2017-04-28 20:33:41 -07:00
)
)
}
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 {
2017-06-13 13:19:18 +08:00
try self . passwordStore . pullRepository ( credential : gitCredential , requestGitPassword : self . requestGitPassword ( credential : lastPassword : ) , transferProgressBlock : { ( git_transfer_progress , stop ) in
2017-02-04 14:24:59 +08:00
DispatchQueue . main . async {
SVProgressHUD . showProgress ( Float ( git_transfer_progress . pointee . received_objects ) / Float ( git_transfer_progress . pointee . total_objects ) , status : " Pull Remote Repository " )
}
} )
2017-03-22 19:07:41 -07:00
if numberOfLocalCommits > 0 {
2017-06-13 13:19:18 +08:00
try self . passwordStore . pushRepository ( credential : gitCredential , requestGitPassword : self . requestGitPassword ( credential : lastPassword : ) , transferProgressBlock : { ( current , total , bytes , stop ) in
2017-02-12 13:31:29 +08:00
DispatchQueue . main . async {
SVProgressHUD . showProgress ( Float ( current ) / Float ( total ) , status : " Push Remote Repository " )
}
} )
}
2017-01-23 17:36:10 +08:00
DispatchQueue . main . async {
2017-03-24 21:53:07 +08:00
self . reloadTableView ( parent : nil )
2017-01-23 17:36:10 +08:00
SVProgressHUD . showSuccess ( withStatus : " Done " )
SVProgressHUD . dismiss ( withDelay : 1 )
2017-09-24 22:55:24 -07:00
self . syncControl . endRefreshing ( )
2017-01-23 17:36:10 +08:00
}
2017-02-04 14:24:59 +08:00
} catch {
DispatchQueue . main . async {
2017-04-05 20:06:40 -07:00
SVProgressHUD . dismiss ( )
2017-04-05 19:21:50 -07:00
self . syncControl . endRefreshing ( )
2017-10-08 21:37:58 +08:00
let error = error as NSError
var message = error . localizedDescription
if let underlyingError = error . userInfo [ NSUnderlyingErrorKey ] as ? NSError {
message = " \( message ) \n Underlying error: \( underlyingError . localizedDescription ) "
if underlyingError . localizedDescription . contains ( " Wrong passphrase " ) {
message = " \( message ) \n Recovery suggestion: Wrong credential password/passphrase has been removed, please try again. "
gitCredential . delete ( )
}
}
2017-04-05 20:06:40 -07:00
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + . milliseconds ( 800 ) ) {
2017-10-08 21:37:58 +08:00
Utils . alert ( title : " Error " , message : message , controller : self , completion : nil )
2017-04-05 20:06:40 -07:00
}
2017-02-04 14:24:59 +08:00
}
2017-01-23 16:29:36 +08:00
}
}
}
2017-04-04 22:48:39 -07:00
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
2017-06-13 11:42:49 +08:00
if SharedDefaults [ . isShowFolderOn ] {
2017-04-04 22:48:39 -07:00
searchController . searchBar . scopeButtonTitles = [ " Current " , " All " ]
} else {
searchController . searchBar . scopeButtonTitles = nil
}
}
2017-03-02 15:58:26 +08:00
2017-01-19 21:15:47 +08:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
2017-04-04 22:48:39 -07:00
searchController . searchBar . delegate = self
2017-02-03 13:01:41 +08:00
tableView . delegate = self
tableView . dataSource = self
2017-01-23 12:48:20 +08:00
definesPresentationContext = true
2017-10-07 00:24:30 -07:00
if #available ( iOS 11.0 , * ) {
navigationItem . searchController = searchController
navigationController ? . navigationBar . prefersLargeTitles = true
navigationItem . largeTitleDisplayMode = . automatic
2017-10-07 23:14:00 -07:00
navigationItem . hidesSearchBarWhenScrolling = false
2017-10-07 00:24:30 -07:00
} else {
// F a l l b a c k o n e a r l i e r v e r s i o n s
2018-01-14 22:35:21 -08:00
tableView . contentInset = UIEdgeInsetsMake ( 44 , 0 , 0 , 0 )
2017-10-07 00:24:30 -07:00
view . addSubview ( searchBarView ! )
}
2017-09-24 22:53:00 -07:00
tableView . refreshControl = syncControl
2017-02-06 10:55:24 +08:00
SVProgressHUD . setDefaultMaskType ( . black )
2017-03-10 23:07:56 -08:00
tableView . register ( UINib ( nibName : " PasswordWithFolderTableViewCell " , bundle : nil ) , forCellReuseIdentifier : " passwordWithFolderTableViewCell " )
2017-03-24 21:53:07 +08:00
// i n i t i a l i z e t h e p a s s w o r d t a b l e
reloadTableView ( parent : nil )
// r e s e t t h e d a t a t a b l e i f s o m e p a s s w o r d ( m a y b e a n o t h e r o n e ) h a s b e e n u p d a t e d
2017-03-29 00:26:41 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( actOnReloadTableViewRelatedNotification ) , name : . passwordStoreUpdated , object : nil )
2017-03-24 21:53:07 +08:00
// r e s e t t h e d a t a t a b l e i f t h e d i s a p l y s e t t i n g s h a v e b e e n c h a n g e d
2017-03-29 00:26:41 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( actOnReloadTableViewRelatedNotification ) , name : . passwordDisplaySettingChanged , object : nil )
2017-03-24 21:53:07 +08:00
NotificationCenter . default . addObserver ( self , selector : #selector ( actOnSearchNotification ) , name : . passwordSearch , object : nil )
2018-04-12 01:07:19 +08:00
// l i s t e n t o t h e s w i p e b a c k g u e s t u r e
let swipeRight = UISwipeGestureRecognizer ( target : self , action : #selector ( self . respondToSwipeGesture ) )
swipeRight . direction = UISwipeGestureRecognizerDirection . right
self . view . addGestureRecognizer ( swipeRight )
2017-02-02 15:03:34 +08:00
}
2017-02-03 14:20:52 +08:00
override func viewWillAppear ( _ animated : Bool ) {
super . viewWillAppear ( animated )
2018-01-16 21:54:00 -08:00
tabBarController ! . delegate = self
2017-02-03 14:20:52 +08:00
if let path = tableView . indexPathForSelectedRow {
tableView . deselectRow ( at : path , animated : false )
}
}
2017-09-24 23:49:16 -07:00
override func viewWillLayoutSubviews ( ) {
super . viewWillLayoutSubviews ( )
2017-10-07 00:24:30 -07:00
guard #available ( iOS 11 , * ) else {
2018-01-14 22:35:21 -08:00
searchBarView ? . frame = CGRect ( x : 0 , y : navigationController ! . navigationBar . bounds . size . height + UIApplication . shared . statusBarFrame . height , width : UIScreen . main . bounds . width , height : 44 )
2017-10-07 00:24:30 -07:00
searchController . searchBar . sizeToFit ( )
return
}
2017-02-07 13:23:18 +08:00
}
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 {
2017-04-03 22:40:17 +08:00
return sections [ section ] . entries . count
2017-02-03 13:01:41 +08:00
}
func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
2017-03-10 23:07:56 -08:00
let longPressGestureRecognizer = UILongPressGestureRecognizer ( target : self , action : #selector ( longPressAction ( _ : ) ) )
longPressGestureRecognizer . minimumPressDuration = 0.6
2017-06-13 11:42:49 +08:00
if SharedDefaults [ . isShowFolderOn ] && searchController . searchBar . selectedScopeButtonIndex = = 0 {
2017-03-10 23:07:56 -08:00
let cell = tableView . dequeueReusableCell ( withIdentifier : " passwordTableViewCell " , for : indexPath )
let entry = getPasswordEntry ( by : indexPath )
2017-10-15 22:36:30 +08:00
if entry . passwordEntity ! . synced {
cell . textLabel ? . text = entry . title
} else {
cell . textLabel ? . text = " ↻ \( entry . title ) "
}
2017-03-10 23:07:56 -08:00
if ! entry . isDir {
cell . addGestureRecognizer ( longPressGestureRecognizer )
cell . accessoryType = . none
cell . detailTextLabel ? . text = " "
2017-03-02 14:51:40 +08:00
} else {
2017-03-10 23:07:56 -08:00
cell . accessoryType = . disclosureIndicator
2017-04-02 21:22:14 -07:00
cell . detailTextLabel ? . font = UIFont . preferredFont ( forTextStyle : . body )
2017-03-10 23:07:56 -08:00
cell . detailTextLabel ? . text = " \( entry . passwordEntity ? . children ? . count ? ? 0 ) "
2017-03-02 14:51:40 +08:00
}
2017-03-10 23:07:56 -08:00
return cell
2017-02-12 01:59:40 +08:00
} else {
2017-04-02 21:22:14 -07:00
let cell = tableView . dequeueReusableCell ( withIdentifier : " passwordTableViewCell " , for : indexPath )
2017-03-10 23:07:56 -08:00
let entry = getPasswordEntry ( by : indexPath )
if entry . passwordEntity ! . synced {
2017-04-02 21:22:14 -07:00
cell . textLabel ? . text = entry . title
2017-03-10 23:07:56 -08:00
} else {
2017-04-02 21:22:14 -07:00
cell . textLabel ? . text = " ↻ \( entry . title ) "
2017-03-10 23:07:56 -08:00
}
2017-04-02 21:22:14 -07:00
cell . accessoryType = . none
cell . detailTextLabel ? . font = UIFont . preferredFont ( forTextStyle : . footnote )
cell . detailTextLabel ? . text = entry . passwordEntity ? . getCategoryText ( )
cell . addGestureRecognizer ( longPressGestureRecognizer )
return cell
2017-02-12 01:59:40 +08:00
}
2017-03-10 23:07:56 -08:00
2017-02-03 13:01:41 +08:00
}
2017-03-02 22:10:41 +08:00
private func getPasswordEntry ( by indexPath : IndexPath ) -> PasswordsTableEntry {
2017-04-03 22:40:17 +08:00
return sections [ indexPath . section ] . entries [ indexPath . row ]
2017-03-02 14:51:40 +08:00
}
func tableView ( _ tableView : UITableView , didSelectRowAt indexPath : IndexPath ) {
let entry = getPasswordEntry ( by : indexPath )
if ! entry . isDir {
2017-03-11 21:09:24 -08:00
let segueIdentifier = " showPasswordDetail "
let sender = tableView . cellForRow ( at : indexPath )
if shouldPerformSegue ( withIdentifier : segueIdentifier , sender : sender ) {
performSegue ( withIdentifier : segueIdentifier , sender : sender )
}
2017-03-02 14:51:40 +08:00
} else {
tableView . deselectRow ( at : indexPath , animated : true )
2017-03-02 16:45:52 +08:00
searchController . isActive = false
2017-03-24 21:53:07 +08:00
reloadTableView ( parent : entry . passwordEntity , anim : transitionFromRight )
2017-03-02 14:51:40 +08:00
}
}
2018-04-12 01:07:19 +08:00
@objc func respondToSwipeGesture ( gesture : UIGestureRecognizer ) {
if let swipeGesture = gesture as ? UISwipeGestureRecognizer {
// s w i p e r i g h t - > s w i p e b a c k
if swipeGesture . direction = = . right && parentPasswordEntity != nil {
self . backAction ( nil )
}
}
}
2017-09-23 16:29:03 +08:00
@objc func backAction ( _ sender : Any ? ) {
2017-06-13 11:42:49 +08:00
guard SharedDefaults [ . isShowFolderOn ] else { return }
2017-04-05 20:20:26 -07:00
var anim : CATransition ? = transitionFromLeft
if parentPasswordEntity = = nil {
anim = nil
}
reloadTableView ( parent : parentPasswordEntity ? . parent , anim : anim )
2017-03-02 14:51:40 +08:00
}
2017-09-23 16:29:03 +08:00
@objc func longPressAction ( _ gesture : UILongPressGestureRecognizer ) {
2017-02-08 01:29:00 +08:00
if gesture . state = = UIGestureRecognizerState . began {
let touchPoint = gesture . location ( in : tableView )
if let indexPath = tableView . indexPathForRow ( at : touchPoint ) {
2017-07-27 22:38:07 +08:00
decryptThenCopyPassword ( from : indexPath )
2017-02-08 01:29:00 +08:00
}
}
}
2017-02-03 13:01:41 +08:00
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 ) {
2017-07-27 22:38:07 +08:00
decryptThenCopyPassword ( from : indexPath )
2017-02-08 01:29:00 +08:00
}
2017-04-23 10:03:09 -07:00
private func requestPGPKeyPassphrase ( ) -> String {
let sem = DispatchSemaphore ( value : 0 )
2017-02-28 12:25:52 +08:00
var passphrase = " "
2017-04-23 10:03:09 -07:00
DispatchQueue . main . async {
2017-02-28 12:25:52 +08:00
let alert = UIAlertController ( title : " Passphrase " , message : " Please fill in the passphrase of your PGP secret key. " , preferredStyle : UIAlertControllerStyle . alert )
alert . addAction ( UIAlertAction ( title : " OK " , style : UIAlertActionStyle . default , handler : { _ in
passphrase = alert . textFields ! . first ! . text !
2017-04-23 10:03:09 -07:00
sem . signal ( )
2017-02-28 12:25:52 +08:00
} ) )
alert . addTextField ( configurationHandler : { ( textField : UITextField ! ) in
textField . text = " "
textField . isSecureTextEntry = true
} )
2017-05-04 20:47:23 +08:00
// h i d e i t s o t h a t a l e r t i s o n t h e t o p o f t h e v i e w
SVProgressHUD . dismiss ( )
2017-02-28 12:25:52 +08:00
self . present ( alert , animated : true , completion : nil )
}
2017-04-23 10:03:09 -07:00
let _ = sem . wait ( timeout : DispatchTime . distantFuture )
2017-05-04 20:47:23 +08:00
DispatchQueue . main . async {
// b r i n g b a c k
SVProgressHUD . show ( withStatus : " Decrypting " )
}
2017-10-08 21:37:58 +08:00
if SharedDefaults [ . isRememberPGPPassphraseOn ] {
2017-04-23 10:03:09 -07:00
self . passwordStore . pgpKeyPassphrase = passphrase
}
return passphrase
2017-02-28 12:25:52 +08:00
}
2017-07-27 22:38:07 +08:00
private func decryptThenCopyPassword ( from indexPath : IndexPath ) {
guard self . passwordStore . privateKey != nil else {
Utils . alert ( title : " Cannot Copy Password " , message : " PGP Key is not set. Please set your PGP Key first. " , controller : self , completion : nil )
return
}
let passwordEntity = getPasswordEntry ( by : indexPath ) . passwordEntity !
UIImpactFeedbackGenerator ( style : . medium ) . impactOccurred ( )
2017-02-20 16:07:32 +08:00
SVProgressHUD . setDefaultMaskType ( . black )
2017-02-08 19:18:10 +08:00
SVProgressHUD . setDefaultStyle ( . dark )
SVProgressHUD . show ( withStatus : " Decrypting " )
DispatchQueue . global ( qos : . userInteractive ) . async {
var decryptedPassword : Password ?
do {
2017-04-23 10:03:09 -07:00
decryptedPassword = try self . passwordStore . decrypt ( passwordEntity : passwordEntity , requestPGPKeyPassphrase : self . requestPGPKeyPassphrase )
2017-02-15 11:37:19 +08:00
DispatchQueue . main . async {
2017-07-27 23:56:24 +08:00
SecurePasteboard . shared . copy ( textToCopy : decryptedPassword ? . password )
2018-09-25 00:51:18 +08:00
SVProgressHUD . showSuccess ( withStatus : " Password copied. We will clear the pasteboard in 45 seconds. " )
2017-02-15 11:37:19 +08:00
SVProgressHUD . dismiss ( withDelay : 0.6 )
}
2017-02-08 19:18:10 +08:00
} catch {
print ( error )
2017-02-15 11:37:19 +08:00
DispatchQueue . main . async {
2017-05-08 21:12:48 +08:00
// r e m o v e t h e w r o n g p a s s p h r a s e s o t h a t u s e r s c o u l d e n t e r i t n e x t t i m e
self . passwordStore . pgpKeyPassphrase = nil
Utils . alert ( title : " Cannot Copy Password " , message : error . localizedDescription , controller : self , completion : nil )
2017-02-15 11:37:19 +08:00
}
2017-02-08 19:18:10 +08:00
}
2017-02-06 14:28:57 +08:00
}
2017-02-04 11:30:57 +08:00
}
2017-03-24 21:53:07 +08:00
private func generateSections ( item : [ PasswordsTableEntry ] ) {
2017-04-03 22:40:17 +08:00
let collation = UILocalizedIndexedCollation . current ( )
let sectionTitles = collation . sectionIndexTitles
var newSections = [ ( title : String , entries : [ PasswordsTableEntry ] ) ] ( )
// i n i t i a l i z e a l l s e c t i o n s
for i in 0. . < sectionTitles . count {
newSections . append ( ( title : sectionTitles [ i ] , entries : [ PasswordsTableEntry ] ( ) ) )
2017-02-02 15:03:34 +08:00
}
2017-04-03 22:40:17 +08:00
// p u t e n t r i e s i n t o s e c t i o n s
for entry in item {
let sectionNumber = collation . section ( for : entry , collationStringSelector : #selector ( getter : PasswordsTableEntry . title ) )
newSections [ sectionNumber ] . entries . append ( entry )
2017-02-02 15:03:34 +08:00
}
2017-04-03 22:40:17 +08:00
// s o r t e a c h l i s t a n d s e t s e c t i o n T i t l e s
for i in 0. . < sectionTitles . count {
let entriesToSort = newSections [ i ] . entries
let sortedEntries = collation . sortedArray ( from : entriesToSort , collationStringSelector : #selector ( getter : PasswordsTableEntry . title ) )
newSections [ i ] . entries = sortedEntries as ! [ PasswordsTableEntry ]
}
// o n l y k e e p n o n - e m p t y s e c t i o n s
sections = newSections . filter { $0 . entries . count > 0 }
2017-01-23 12:48:20 +08:00
}
2017-09-23 16:29:03 +08:00
@objc func actOnSearchNotification ( ) {
2017-02-08 12:47:05 +08:00
searchController . searchBar . becomeFirstResponder ( )
}
2017-02-07 16:45:14 +08:00
2017-02-06 10:55:24 +08:00
override func shouldPerformSegue ( withIdentifier identifier : String , sender : Any ? ) -> Bool {
if identifier = = " showPasswordDetail " {
2017-03-16 22:39:03 -07:00
guard self . passwordStore . privateKey != nil else {
2017-02-16 00:54:42 +08:00
Utils . alert ( title : " Cannot Show Password " , message : " PGP Key is not set. Please set your PGP Key first. " , controller : self , completion : nil )
2017-02-06 10:55:24 +08:00
if let s = sender as ? UITableViewCell {
let selectedIndexPath = tableView . indexPath ( for : s ) !
tableView . deselectRow ( at : selectedIndexPath , animated : true )
}
return false
}
2017-04-05 19:33:06 -07:00
} else if identifier = = " addPasswordSegue " {
guard self . passwordStore . publicKey != nil , self . passwordStore . storeRepository != nil else {
Utils . alert ( title : " Cannot Add Password " , message : " Please make sure PGP Key and Git Server are properly set. " , controller : self , completion : nil )
return false
}
2017-02-06 10:55:24 +08:00
}
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 ) !
2017-03-02 14:51:40 +08:00
let passwordEntity = getPasswordEntry ( by : selectedIndexPath ) . passwordEntity !
2017-02-06 14:28:57 +08:00
viewController . passwordEntity = passwordEntity
2017-01-22 01:42:36 +08:00
}
2017-10-15 16:49:33 +08:00
} else if segue . identifier = = " addPasswordSegue " {
if let navController = segue . destination as ? UINavigationController {
if let viewController = navController . topViewController as ? AddPasswordTableViewController {
if let path = parentPasswordEntity ? . path {
viewController . defaultDirPrefix = " \( path ) / "
}
}
}
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 " ) {
2017-04-04 22:48:39 -07:00
switch scope {
case " All " :
filteredPasswordsTableEntries = passwordsTableAllEntries . filter { entry in
2017-06-23 07:10:11 -07:00
let name = entry . passwordEntity ? . nameWithCategory ? ? entry . title
return name . localizedCaseInsensitiveContains ( searchText )
2017-04-04 22:48:39 -07:00
}
if searchController . isActive && searchController . searchBar . text != " " {
reloadTableView ( data : filteredPasswordsTableEntries )
} else {
reloadTableView ( data : passwordsTableAllEntries )
}
case " Current " :
filteredPasswordsTableEntries = passwordsTableEntries . filter { entry in
return entry . title . lowercased ( ) . contains ( searchText . lowercased ( ) )
}
if searchController . isActive && searchController . searchBar . text != " " {
reloadTableView ( data : filteredPasswordsTableEntries )
} else {
reloadTableView ( data : passwordsTableEntries )
}
default :
break
2017-02-03 13:01:41 +08:00
}
2017-04-04 22:48:39 -07:00
2017-02-04 11:30:57 +08:00
}
2017-03-24 21:53:07 +08:00
private func reloadTableView ( data : [ PasswordsTableEntry ] , anim : CAAnimation ? = nil ) {
// s e t n a v i g a t i o n i t e m
let numberOfLocalCommits = self . passwordStore . numberOfLocalCommits ( )
2017-10-10 22:15:10 -07:00
if numberOfLocalCommits = = 0 {
navigationController ? . tabBarItem . badgeValue = nil
} else {
2017-10-10 00:37:50 -07:00
navigationController ? . tabBarItem . badgeValue = " \( numberOfLocalCommits ) "
2017-03-24 21:53:07 +08:00
}
2017-03-02 14:51:40 +08:00
if parentPasswordEntity != nil {
navigationItem . leftBarButtonItem = backUIBarButtonItem
} else {
navigationItem . leftBarButtonItem = nil
}
2017-03-24 21:53:07 +08:00
// s e t t h e p a s s w o r d t a b l e
2017-02-04 11:30:57 +08:00
generateSections ( item : data )
2017-03-23 22:46:50 -07:00
if anim != nil {
self . tableView . layer . add ( anim ! , forKey : " UITableViewReloadDataAnimationKey " )
}
2017-02-03 13:01:41 +08:00
tableView . reloadData ( )
2017-03-23 22:46:50 -07:00
self . tableView . layer . removeAnimation ( forKey : " UITableViewReloadDataAnimationKey " )
2017-03-24 21:53:07 +08:00
// s e t t h e s y n c c o n t r o l t i t l e
2018-09-23 22:00:06 +08:00
let atribbutedTitle = " Last Synced: \( passwordStore . getLastSyncedTimeString ( ) ) "
2017-03-24 21:53:07 +08:00
syncControl . attributedTitle = NSAttributedString ( string : atribbutedTitle )
}
private func reloadTableView ( parent : PasswordEntity ? , anim : CAAnimation ? = nil ) {
initPasswordsTableEntries ( parent : parent )
2017-03-25 00:44:53 +08:00
reloadTableView ( data : passwordsTableEntries , anim : anim )
2017-03-24 21:53:07 +08:00
}
2017-09-23 16:29:03 +08:00
@objc func actOnReloadTableViewRelatedNotification ( ) {
2017-03-29 00:26:41 +08:00
DispatchQueue . main . async { [ weak weakSelf = self ] in
guard let strongSelf = weakSelf else { return }
2017-04-23 10:39:28 -07:00
strongSelf . initPasswordsTableEntries ( parent : nil )
2017-03-29 00:26:41 +08:00
strongSelf . reloadTableView ( data : strongSelf . passwordsTableEntries )
}
2017-02-03 13:01:41 +08:00
}
2017-02-04 11:30:57 +08:00
2017-09-23 16:29:03 +08:00
@objc func handleRefresh ( _ syncControl : UIRefreshControl ) {
2017-02-04 11:47:57 +08:00
syncPasswords ( )
2017-02-04 11:30:57 +08:00
}
2017-03-02 22:55:41 +08:00
func tabBarController ( _ tabBarController : UITabBarController , didSelect viewController : UIViewController ) {
if viewController = = self . navigationController {
let currentTime = Date ( ) . timeIntervalSince1970
let duration = currentTime - self . tapTabBarTime
self . tapTabBarTime = currentTime
if duration < 0.35 {
let topIndexPath = IndexPath ( row : 0 , section : 0 )
2018-01-16 20:04:16 -08:00
if tableView . numberOfSections > 0 {
tableView . scrollToRow ( at : topIndexPath , at : . bottom , animated : true )
}
2017-03-02 22:55:41 +08:00
self . tapTabBarTime = 0
return
}
backAction ( self )
}
}
2018-08-24 02:02:57 +01:00
2017-04-04 22:48:39 -07:00
func searchBar ( _ searchBar : UISearchBar , selectedScopeButtonIndexDidChange selectedScope : Int ) {
2018-08-24 02:02:57 +01:00
// u p d a t e t h e d e f a u l t s e a r c h s c o p e
SharedDefaults [ . isSearchDefaultAll ] = searchController . searchBar . scopeButtonTitles ! [ selectedScope ] = = " All "
2017-04-04 22:48:39 -07:00
updateSearchResults ( for : searchController )
}
2018-08-24 02:02:57 +01:00
func searchBarShouldBeginEditing ( _ searchBar : UISearchBar ) -> Bool {
// s e t t h e d e f a u l t s e a r c h s c o p e t o " a l l "
if SharedDefaults [ . isShowFolderOn ] && SharedDefaults [ . isSearchDefaultAll ] {
searchController . searchBar . selectedScopeButtonIndex = searchController . searchBar . scopeButtonTitles ? . index ( of : " All " ) ? ? 0
} else {
searchController . searchBar . selectedScopeButtonIndex = 0
}
return true
}
func searchBarShouldEndEditing ( _ searchBar : UISearchBar ) -> Bool {
// s e t t h e d e f a u l t s e a r c h s c o p e t o " c u r r e n t "
2017-04-04 22:48:39 -07:00
searchController . searchBar . selectedScopeButtonIndex = 0
updateSearchResults ( for : searchController )
2018-08-24 02:02:57 +01:00
return true
2017-04-04 22:48:39 -07:00
}
2017-06-13 13:19:18 +08:00
private func requestGitPassword ( credential : GitCredential . Credential , lastPassword : String ? ) -> String ? {
let sem = DispatchSemaphore ( value : 0 )
var password : String ?
var message = " "
switch credential {
case . http :
message = " Please fill in the password of your Git account. "
case . ssh :
message = " Please fill in the password of your SSH key. "
}
DispatchQueue . main . async {
SVProgressHUD . dismiss ( )
let alert = UIAlertController ( title : " Password " , message : message , preferredStyle : UIAlertControllerStyle . alert )
alert . addTextField ( configurationHandler : { ( textField : UITextField ! ) in
textField . text = lastPassword ? ? " "
textField . isSecureTextEntry = true
} )
alert . addAction ( UIAlertAction ( title : " OK " , style : UIAlertActionStyle . default , handler : { _ in
password = alert . textFields ! . first ! . text
sem . signal ( )
} ) )
alert . addAction ( UIAlertAction ( title : " Cancel " , style : . cancel ) { _ in
password = nil
sem . signal ( )
} )
self . present ( alert , animated : true , completion : nil )
}
let _ = sem . wait ( timeout : . distantFuture )
return password
}
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 ) {
2017-04-04 22:48:39 -07:00
var scope = " All "
if let scopeButtonTitles = searchController . searchBar . scopeButtonTitles {
scope = scopeButtonTitles [ searchController . searchBar . selectedScopeButtonIndex ]
}
filterContentForSearchText ( searchText : searchController . searchBar . text ! , scope : scope )
2017-01-23 16:29:36 +08:00
}
}