2017-02-02 21:04:31 +08:00
//
// P a s s w o r d D e t a i l T a b l e V i e w C o n t r o l l e r . s w i f t
// p a s s
//
// C r e a t e d b y M i n g s h e n S u n o n 2 / 2 / 2 0 1 7 .
// 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
2017-02-09 13:17:11 +08:00
import FavIcon
2017-02-14 11:16:30 +08:00
import SwiftyUserDefaults
2017-02-15 20:01:17 +08:00
import SVProgressHUD
2017-02-02 21:04:31 +08:00
2017-02-05 00:35:23 +08:00
class PasswordDetailTableViewController : UITableViewController , UIGestureRecognizerDelegate {
2017-02-02 21:04:31 +08:00
var passwordEntity : PasswordEntity ?
2017-02-06 21:53:54 +08:00
var passwordCategoryText = " "
2017-02-11 16:07:59 +08:00
var password : Password ?
2017-02-09 13:17:11 +08:00
var passwordImage : UIImage ?
2017-03-05 02:54:36 +08:00
var oneTimePasswordIndexPath : IndexPath ?
2017-02-28 12:25:52 +08:00
let indicatorLable : UILabel = {
let label = UILabel ( frame : CGRect ( x : 0 , y : 0 , width : UIScreen . main . bounds . width , height : 21 ) )
label . center = CGPoint ( x : UIScreen . main . bounds . width / 2 , y : UIScreen . main . bounds . height * 0.382 + 22 )
label . backgroundColor = UIColor . clear
label . textColor = UIColor . gray
label . text = " decrypting password "
label . textAlignment = . center
label . font = UIFont . preferredFont ( forTextStyle : . footnote )
return label
} ( )
let indicator : UIActivityIndicatorView = {
let indicator = UIActivityIndicatorView ( activityIndicatorStyle : . gray )
indicator . center = CGPoint ( x : UIScreen . main . bounds . width / 2 , y : UIScreen . main . bounds . height * 0.382 )
return indicator
} ( )
2017-02-28 17:33:45 +08:00
lazy var editUIBarButtonItem : UIBarButtonItem = {
let uiBarButtonItem = UIBarButtonItem ( barButtonSystemItem : . edit , target : self , action : #selector ( pressEdit ( _ : ) ) )
return uiBarButtonItem
} ( )
2017-02-28 12:25:52 +08:00
2017-02-02 21:04:31 +08:00
2017-02-04 20:38:40 +08:00
struct TableCell {
2017-02-02 21:04:31 +08:00
var title : String
var content : String
2017-02-09 15:47:42 +08:00
init ( ) {
title = " "
content = " "
}
init ( title : String , content : String ) {
self . title = title
self . content = content
}
2017-02-02 21:04:31 +08:00
}
2017-02-04 20:38:40 +08:00
struct TableSection {
var title : String
var item : Array < TableCell >
}
var tableData = Array < TableSection > ( )
2017-02-02 21:04:31 +08:00
2017-03-02 14:51:40 +08:00
private func generateCategoryText ( ) -> String {
var passwordCategoryArray : [ String ] = [ ]
var parent = passwordEntity ? . parent
while parent != nil {
passwordCategoryArray . append ( parent ! . name ! )
parent = parent ! . parent
}
passwordCategoryArray . reverse ( )
return passwordCategoryArray . joined ( separator : " > " )
}
2017-02-02 21:04:31 +08:00
override func viewDidLoad ( ) {
super . viewDidLoad ( )
tableView . register ( UINib ( nibName : " LabelTableViewCell " , bundle : nil ) , forCellReuseIdentifier : " labelCell " )
2017-02-06 20:48:20 +08:00
tableView . register ( UINib ( nibName : " PasswordDetailTitleTableViewCell " , bundle : nil ) , forCellReuseIdentifier : " passwordDetailTitleTableViewCell " )
2017-02-05 00:35:23 +08:00
2017-03-02 14:51:40 +08:00
passwordCategoryText = generateCategoryText ( )
2017-02-06 21:53:54 +08:00
2017-02-05 00:35:23 +08:00
let tapGesture = UITapGestureRecognizer ( target : self , action : #selector ( PasswordDetailTableViewController . tapMenu ( recognizer : ) ) )
tableView . addGestureRecognizer ( tapGesture )
tapGesture . delegate = self
2017-02-05 14:08:19 +08:00
2017-02-06 20:48:20 +08:00
tableView . contentInset = UIEdgeInsetsMake ( - 36 , 0 , 0 , 0 ) ;
2017-02-05 14:08:19 +08:00
tableView . rowHeight = UITableViewAutomaticDimension
tableView . estimatedRowHeight = 52
2017-02-28 12:25:52 +08:00
2017-02-06 15:03:34 +08:00
indicator . startAnimating ( )
tableView . addSubview ( indicator )
2017-02-06 15:31:28 +08:00
tableView . addSubview ( indicatorLable )
2017-02-15 21:25:03 +08:00
editUIBarButtonItem . isEnabled = false
navigationItem . rightBarButtonItem = editUIBarButtonItem
2017-02-06 15:03:34 +08:00
2017-02-09 14:41:59 +08:00
if let imageData = passwordEntity ? . image {
let image = UIImage ( data : imageData as Data )
passwordImage = image
}
2017-02-28 12:25:52 +08:00
var passphrase = " "
if Defaults [ . isRememberPassphraseOn ] && PasswordStore . shared . pgpKeyPassphrase != nil {
passphrase = PasswordStore . shared . pgpKeyPassphrase !
self . decryptThenShowPassword ( passphrase : passphrase )
} else {
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 !
self . decryptThenShowPassword ( passphrase : passphrase )
} ) )
alert . addTextField ( configurationHandler : { ( textField : UITextField ! ) in
textField . text = " "
textField . isSecureTextEntry = true
} )
self . present ( alert , animated : true , completion : nil )
}
2017-03-05 02:54:36 +08:00
self . setupUdateOneTimePassword ( )
2017-02-28 12:25:52 +08:00
}
func decryptThenShowPassword ( passphrase : String ) {
if Defaults [ . isRememberPassphraseOn ] {
PasswordStore . shared . pgpKeyPassphrase = passphrase
}
2017-02-08 18:57:15 +08:00
DispatchQueue . global ( qos : . userInitiated ) . async {
2017-02-06 15:03:34 +08:00
do {
2017-02-28 12:25:52 +08:00
self . password = try self . passwordEntity ! . decrypt ( passphrase : passphrase ) !
2017-02-06 15:03:34 +08:00
} catch {
2017-02-22 12:56:06 +08:00
DispatchQueue . main . async {
let alert = UIAlertController ( title : " Cannot Show Password " , message : error . localizedDescription , preferredStyle : UIAlertControllerStyle . alert )
alert . addAction ( UIAlertAction ( title : " OK " , style : UIAlertActionStyle . default , handler : { ( UIAlertAction ) -> Void in
self . navigationController ! . popViewController ( animated : true )
} ) )
self . present ( alert , animated : true , completion : nil )
}
return
2017-02-06 15:03:34 +08:00
}
2017-02-28 12:25:52 +08:00
2017-02-11 16:07:59 +08:00
let password = self . password !
2017-02-09 13:33:50 +08:00
DispatchQueue . main . async { [ weak self ] in
2017-02-28 12:25:52 +08:00
self ? . showPassword ( password : password )
}
}
}
func showPassword ( password : Password ) {
setTableData ( )
self . tableView . reloadData ( )
indicator . stopAnimating ( )
indicatorLable . isHidden = true
editUIBarButtonItem . isEnabled = true
if let url = password . getURL ( ) {
if self . passwordEntity ? . image = = nil {
self . updatePasswordImage ( url : url )
2017-02-09 13:17:11 +08:00
}
}
}
2017-03-05 02:54:36 +08:00
func setupUdateOneTimePassword ( ) {
Timer . scheduledTimer ( withTimeInterval : 1 , repeats : true ) {
[ weak self ] timer in
// b a i l o u t o f t h e t i m e r c o d e i f t h e o b j e c t h a s b e e n f r e e d
guard let strongSelf = self ,
let token = strongSelf . password ? . otpToken ,
let indexPath = strongSelf . oneTimePasswordIndexPath ,
let cell = strongSelf . tableView . cellForRow ( at : indexPath ) as ? LabelTableViewCell else {
return
}
switch token . generator . factor {
case . counter :
// h t o p
break
case . timer ( let period ) :
// t o t p
let timeSinceEpoch = Date ( ) . timeIntervalSince1970
let validTime = Int ( period - timeSinceEpoch . truncatingRemainder ( dividingBy : period ) )
strongSelf . tableData [ indexPath . section ] . item [ indexPath . row ] . title = " time-based (expiring in \( validTime ) s) "
cell . cellData ? . title = " time-based (valid within \( validTime ) s) "
if validTime <= 1 || validTime >= Int ( period - 1 ) {
let otp = token . currentPassword ? ? " error "
strongSelf . tableData [ indexPath . section ] . item [ indexPath . row ] . content = otp
cell . cellData ? . content = otp
}
}
}
}
2017-02-13 01:15:42 +08:00
func pressEdit ( _ sender : Any ? ) {
2017-02-28 17:33:45 +08:00
print ( " pressEdit " )
2017-02-13 01:15:42 +08:00
performSegue ( withIdentifier : " editPasswordSegue " , sender : self )
}
@IBAction func cancelEditPassword ( segue : UIStoryboardSegue ) {
}
@IBAction func saveEditPassword ( segue : UIStoryboardSegue ) {
2017-02-15 20:01:17 +08:00
if self . password ! . changed {
SVProgressHUD . show ( withStatus : " Saving " )
DispatchQueue . global ( qos : . userInitiated ) . async {
PasswordStore . shared . update ( passwordEntity : self . passwordEntity ! , password : self . password ! , progressBlock : { progress in
2017-02-15 21:11:22 +08:00
DispatchQueue . main . async {
SVProgressHUD . showProgress ( progress , status : " Encrypting " )
}
2017-02-15 20:01:17 +08:00
} )
DispatchQueue . main . async {
self . passwordEntity ! . synced = false
PasswordStore . shared . saveUpdated ( passwordEntity : self . passwordEntity ! )
NotificationCenter . default . post ( Notification ( name : Notification . Name ( " passwordUpdated " ) ) )
self . setTableData ( )
self . tableView . reloadData ( )
SVProgressHUD . showSuccess ( withStatus : " Success " )
SVProgressHUD . dismiss ( withDelay : 1 )
}
}
2017-02-13 01:15:42 +08:00
}
}
func setTableData ( ) {
self . tableData = Array < TableSection > ( )
tableData . append ( TableSection ( title : " " , item : [ ] ) )
tableData [ 0 ] . item . append ( TableCell ( ) )
var tableDataIndex = 1
self . tableData . append ( TableSection ( title : " " , item : [ ] ) )
let password = self . password !
if let username = password . getUsername ( ) {
self . tableData [ tableDataIndex ] . item . append ( TableCell ( title : " username " , content : username ) )
}
self . tableData [ tableDataIndex ] . item . append ( TableCell ( title : " password " , content : password . password ) )
2017-03-03 17:12:25 +08:00
2017-03-05 02:54:36 +08:00
// s h o w o n e t i m e p a s s w o r d
if let token = password . otpToken {
switch token . generator . factor {
case . counter ( _ ) :
// c o u n t e r - b a s e d o n e t i m e p a s s w o r d
break
case . timer ( let period ) :
// t i m e - b a s e d o n e t i m e p a s s w o r d
self . tableData . append ( TableSection ( title : " One time password " , item : [ ] ) )
tableDataIndex += 1
oneTimePasswordIndexPath = IndexPath ( row : 0 , section : tableDataIndex )
if let crtPassword = password . otpToken ? . currentPassword {
let timeSinceEpoch = Date ( ) . timeIntervalSince1970
let validTime = Int ( period - timeSinceEpoch . truncatingRemainder ( dividingBy : period ) )
self . tableData [ tableDataIndex ] . item . append ( TableCell ( title : " time-based (expiring in \( validTime ) s) " , content : crtPassword ) )
}
2017-03-03 14:45:16 +08:00
}
}
2017-03-03 17:12:25 +08:00
2017-03-05 02:54:36 +08:00
// s h o w a d d i t i o n a l i n f o r m a t i o n
2017-03-03 17:12:25 +08:00
let filteredAdditionKeys = password . additionKeys . filter {
$0 . lowercased ( ) != " username " &&
$0 . lowercased ( ) != " password " &&
( ! $0 . hasPrefix ( " unknown " ) || ! Defaults [ . isHideOTPOn ] ) &&
( ! Password . otpKeywords . contains ( $0 ) || ! Defaults [ . isHideOTPOn ] ) }
if filteredAdditionKeys . count > 0 {
self . tableData . append ( TableSection ( title : " additions " , item : [ ] ) )
tableDataIndex += 1
for additionKey in filteredAdditionKeys {
self . tableData [ tableDataIndex ] . item . append ( TableCell ( title : additionKey , content : password . additions [ additionKey ] ! ) )
}
}
2017-02-13 01:15:42 +08:00
}
override func prepare ( for segue : UIStoryboardSegue , sender : Any ? ) {
if segue . identifier = = " editPasswordSegue " {
if let controller = segue . destination as ? UINavigationController {
if let editController = controller . viewControllers . first as ? EditPasswordTableViewController {
editController . password = password
}
}
}
}
2017-02-09 13:17:11 +08:00
func updatePasswordImage ( url : String ) {
do {
2017-02-09 13:33:50 +08:00
try FavIcon . downloadPreferred ( url ) { [ weak self ] result in
2017-02-09 13:17:11 +08:00
switch result {
case . success ( let image ) :
let indexPath = IndexPath ( row : 0 , section : 0 )
2017-02-09 13:33:50 +08:00
self ? . passwordImage = image
self ? . tableView . reloadRows ( at : [ indexPath ] , with : UITableViewRowAnimation . automatic )
2017-02-09 14:41:59 +08:00
let imageData = UIImageJPEGRepresentation ( image , 1 )
2017-02-16 13:24:41 +08:00
if let entity = self ? . passwordEntity {
PasswordStore . shared . updateImage ( passwordEntity : entity , image : imageData )
}
2017-02-09 13:17:11 +08:00
case . failure ( let error ) :
print ( error )
}
2017-02-06 15:03:34 +08:00
}
2017-02-09 13:17:11 +08:00
} catch {
print ( error )
2017-02-06 15:03:34 +08:00
}
2017-02-05 00:35:23 +08:00
}
func tapMenu ( recognizer : UITapGestureRecognizer ) {
if recognizer . state = = UIGestureRecognizerState . ended {
let tapLocation = recognizer . location ( in : self . tableView )
if let tapIndexPath = self . tableView . indexPathForRow ( at : tapLocation ) {
if let tappedCell = self . tableView . cellForRow ( at : tapIndexPath ) as ? LabelTableViewCell {
tappedCell . becomeFirstResponder ( )
let menuController = UIMenuController . shared
2017-02-06 11:55:27 +08:00
let revealItem = UIMenuItem ( title : " Reveal " , action : #selector ( LabelTableViewCell . revealPassword ( _ : ) ) )
let concealItem = UIMenuItem ( title : " Conceal " , action : #selector ( LabelTableViewCell . concealPassword ( _ : ) ) )
2017-02-08 17:55:18 +08:00
let openURLItem = UIMenuItem ( title : " Copy Password & Open Link " , action : #selector ( LabelTableViewCell . openLink ( _ : ) ) )
menuController . menuItems = [ revealItem , concealItem , openURLItem ]
2017-02-05 00:35:23 +08:00
menuController . setTargetRect ( tappedCell . contentLabel . frame , in : tappedCell . contentLabel . superview ! )
menuController . setMenuVisible ( true , animated : true )
}
}
}
2017-02-04 20:38:40 +08:00
}
2017-02-08 17:55:18 +08:00
2017-02-02 21:04:31 +08:00
override func numberOfSections ( in tableView : UITableView ) -> Int {
2017-02-09 15:47:42 +08:00
return tableData . count
2017-02-02 21:04:31 +08:00
}
override func tableView ( _ tableView : UITableView , numberOfRowsInSection section : Int ) -> Int {
2017-02-09 15:47:42 +08:00
return tableData [ section ] . item . count
2017-02-02 21:04:31 +08:00
}
override func tableView ( _ tableView : UITableView , cellForRowAt indexPath : IndexPath ) -> UITableViewCell {
2017-02-06 20:48:20 +08:00
let sectionIndex = indexPath . section
let rowIndex = indexPath . row
2017-02-05 13:56:37 +08:00
2017-02-06 20:48:20 +08:00
if sectionIndex = = 0 && rowIndex = = 0 {
let cell = tableView . dequeueReusableCell ( withIdentifier : " passwordDetailTitleTableViewCell " , for : indexPath ) as ! PasswordDetailTitleTableViewCell
2017-02-09 13:17:11 +08:00
cell . passwordImageImageView . image = passwordImage ? ? # imageLiteral ( resourceName : " PasswordImagePlaceHolder " )
2017-02-19 00:55:13 +08:00
var passwordName = passwordEntity ! . name !
if passwordEntity ! . synced = = false {
passwordName = " \( passwordName ) ↻ "
}
cell . nameLabel . text = passwordName
2017-02-06 21:53:54 +08:00
cell . categoryLabel . text = passwordCategoryText
2017-02-06 20:48:20 +08:00
return cell
} else {
let cell = tableView . dequeueReusableCell ( withIdentifier : " labelCell " , for : indexPath ) as ! LabelTableViewCell
2017-02-09 15:47:42 +08:00
let titleData = tableData [ sectionIndex ] . item [ rowIndex ] . title
let contentData = tableData [ sectionIndex ] . item [ rowIndex ] . content
2017-02-08 17:55:18 +08:00
cell . password = password
cell . isPasswordCell = ( titleData . lowercased ( ) = = " password " ? true : false )
cell . isURLCell = ( titleData . lowercased ( ) = = " url " ? true : false )
2017-02-06 20:48:20 +08:00
cell . cellData = LabelTableViewCellData ( title : titleData , content : contentData )
return cell
}
2017-02-02 21:04:31 +08:00
}
2017-02-04 20:38:40 +08:00
override func tableView ( _ tableView : UITableView , titleForHeaderInSection section : Int ) -> String ? {
2017-02-09 15:47:42 +08:00
return tableData [ section ] . title
2017-02-04 20:38:40 +08:00
}
2017-02-27 08:54:51 +08:00
override func tableView ( _ tableView : UITableView , viewForFooterInSection section : Int ) -> UIView ? {
if section = = tableData . count - 1 {
let view = UIView ( )
2017-02-27 15:21:25 +08:00
let footerLabel = UILabel ( frame : CGRect ( x : 15 , y : 15 , width : tableView . frame . width , height : 60 ) )
2017-02-27 08:54:51 +08:00
footerLabel . numberOfLines = 0
footerLabel . font = UIFont . preferredFont ( forTextStyle : . footnote )
2017-02-27 17:37:53 +08:00
footerLabel . textColor = UIColor . gray
2017-03-02 15:32:11 +08:00
let dateString = PasswordStore . shared . getLatestUpdateInfo ( filename : ( passwordEntity ? . path ) ! )
2017-03-02 00:06:27 +08:00
footerLabel . text = " Last Updated: \( dateString ) "
2017-02-27 08:54:51 +08:00
view . addSubview ( footerLabel )
return view
}
return nil
}
2017-02-03 18:02:40 +08:00
override func tableView ( _ tableView : UITableView , performAction action : Selector , forRowAt indexPath : IndexPath , withSender sender : Any ? ) {
if action = = #selector ( copy ( _ : ) ) {
2017-02-23 17:56:12 +08:00
Utils . copyToPasteboard ( textToCopy : tableData [ indexPath . section ] . item [ indexPath . row ] . content )
2017-02-03 18:02:40 +08:00
}
}
override func tableView ( _ tableView : UITableView , canPerformAction action : Selector , forRowAt indexPath : IndexPath , withSender sender : Any ? ) -> Bool {
return action = = #selector ( UIResponderStandardEditActions . copy ( _ : ) )
}
override func tableView ( _ tableView : UITableView , shouldShowMenuForRowAt indexPath : IndexPath ) -> Bool {
return true
}
2017-02-02 21:04:31 +08:00
}