2019-12-04 06:54:46 +01:00
2019-12-06 01:54:04 +01:00
final class SettingsVC : UIViewController , AvatarViewHelperDelegate {
private var profilePictureToBeUploaded : UIImage ?
private var displayNameToBeUploaded : String ?
private var isEditingDisplayName = false { didSet { handleIsEditingDisplayNameChanged ( ) } }
2019-12-04 06:54:46 +01:00
private lazy var userHexEncodedPublicKey : String = {
2019-12-05 04:31:45 +01:00
if let masterHexEncodedPublicKey = UserDefaults . standard . string ( forKey : " masterDeviceHexEncodedPublicKey " ) {
2019-12-04 06:54:46 +01:00
return masterHexEncodedPublicKey
} else {
2020-01-30 10:09:02 +01:00
return getUserHexEncodedPublicKey ( )
2019-12-04 06:54:46 +01:00
}
} ( )
// MARK: S e t t i n g s
override var preferredStatusBarStyle : UIStatusBarStyle { return . lightContent }
// MARK: C o m p o n e n t s
2019-12-05 00:10:09 +01:00
private lazy var profilePictureView : ProfilePictureView = {
let result = ProfilePictureView ( )
let size = Values . largeProfilePictureSize
result . size = size
result . set ( . width , to : size )
result . set ( . height , to : size )
return result
} ( )
2019-12-06 01:54:04 +01:00
private lazy var profilePictureUtilities : AvatarViewHelper = {
let result = AvatarViewHelper ( )
result . delegate = self
return result
} ( )
2019-12-05 00:10:09 +01:00
private lazy var displayNameLabel : UILabel = {
let result = UILabel ( )
result . textColor = Colors . text
2019-12-05 23:25:26 +01:00
result . font = . boldSystemFont ( ofSize : Values . veryLargeFontSize )
2019-12-05 00:10:09 +01:00
result . lineBreakMode = . byTruncatingTail
2019-12-06 01:54:04 +01:00
result . textAlignment = . center
return result
} ( )
private lazy var displayNameTextField : TextField = {
let result = TextField ( placeholder : NSLocalizedString ( " Enter a display name " , comment : " " ) , usesDefaultHeight : false )
result . textAlignment = . center
2019-12-05 00:10:09 +01:00
return result
} ( )
2019-12-04 06:54:46 +01:00
private lazy var copyButton : Button = {
2019-12-06 04:42:43 +01:00
let result = Button ( style : . prominentOutline , size : . medium )
2019-12-04 06:54:46 +01:00
result . setTitle ( NSLocalizedString ( " Copy " , comment : " " ) , for : UIControl . State . normal )
result . addTarget ( self , action : #selector ( copyPublicKey ) , for : UIControl . Event . touchUpInside )
return result
} ( )
// MARK: L i f e c y c l e
override func viewDidLoad ( ) {
// S e t g r a d i e n t b a c k g r o u n d
view . backgroundColor = . clear
let gradient = Gradients . defaultLokiBackground
view . setGradient ( gradient )
// S e t n a v i g a t i o n b a r b a c k g r o u n d c o l o r
let navigationBar = navigationController ! . navigationBar
navigationBar . setBackgroundImage ( UIImage ( ) , for : UIBarMetrics . default )
navigationBar . shadowImage = UIImage ( )
navigationBar . isTranslucent = false
navigationBar . barTintColor = Colors . navigationBarBackground
// S e t u p n a v i g a t i o n b a r b u t t o n s
2019-12-05 04:31:45 +01:00
let backButton = UIBarButtonItem ( title : NSLocalizedString ( " Back " , comment : " " ) , style : . plain , target : nil , action : nil )
backButton . tintColor = Colors . text
navigationItem . backBarButtonItem = backButton
2019-12-06 01:54:04 +01:00
updateNavigationBarButtons ( )
2019-12-04 06:54:46 +01:00
// C u s t o m i z e t i t l e
let titleLabel = UILabel ( )
titleLabel . text = NSLocalizedString ( " Settings " , comment : " " )
titleLabel . textColor = Colors . text
2019-12-05 04:31:45 +01:00
titleLabel . font = . boldSystemFont ( ofSize : Values . veryLargeFontSize )
2019-12-04 06:54:46 +01:00
navigationItem . titleView = titleLabel
// S e t u p p r o f i l e p i c t u r e v i e w
2019-12-06 01:54:04 +01:00
let profilePictureTapGestureRecognizer = UITapGestureRecognizer ( target : self , action : #selector ( showEditProfilePictureUI ) )
profilePictureView . addGestureRecognizer ( profilePictureTapGestureRecognizer )
2019-12-04 06:54:46 +01:00
profilePictureView . hexEncodedPublicKey = userHexEncodedPublicKey
profilePictureView . update ( )
// S e t u p d i s p l a y n a m e l a b e l
displayNameLabel . text = OWSProfileManager . shared ( ) . profileName ( forRecipientId : userHexEncodedPublicKey )
2019-12-06 01:54:04 +01:00
// S e t u p d i s p l a y n a m e c o n t a i n e r
let displayNameContainer = UIView ( )
displayNameContainer . addSubview ( displayNameLabel )
displayNameLabel . pin ( to : displayNameContainer )
displayNameContainer . addSubview ( displayNameTextField )
displayNameTextField . pin ( to : displayNameContainer )
displayNameContainer . set ( . height , to : 40 )
displayNameTextField . alpha = 0
2020-01-08 06:10:12 +01:00
let displayNameContainerTapGestureRecognizer = UITapGestureRecognizer ( target : self , action : #selector ( showEditDisplayNameUI ) )
displayNameContainer . addGestureRecognizer ( displayNameContainerTapGestureRecognizer )
2019-12-04 06:54:46 +01:00
// S e t u p h e a d e r v i e w
2019-12-06 01:54:04 +01:00
let headerStackView = UIStackView ( arrangedSubviews : [ profilePictureView , displayNameContainer ] )
2019-12-04 06:54:46 +01:00
headerStackView . axis = . vertical
headerStackView . spacing = Values . smallSpacing
headerStackView . alignment = . center
// S e t u p s e p a r a t o r
2020-01-08 06:10:12 +01:00
let separator = Separator ( title : NSLocalizedString ( " Your Session ID " , comment : " " ) )
2019-12-04 06:54:46 +01:00
// S e t u p p u b l i c k e y l a b e l
let publicKeyLabel = UILabel ( )
publicKeyLabel . textColor = Colors . text
2020-01-17 05:53:56 +01:00
publicKeyLabel . font = Fonts . spaceMono ( ofSize : isSmallScreen ? Values . mediumFontSize : Values . largeFontSize )
2019-12-04 06:54:46 +01:00
publicKeyLabel . numberOfLines = 0
publicKeyLabel . textAlignment = . center
publicKeyLabel . lineBreakMode = . byCharWrapping
publicKeyLabel . text = userHexEncodedPublicKey
// S e t u p s h a r e b u t t o n
2019-12-05 00:10:09 +01:00
let shareButton = Button ( style : . regular , size : . medium )
2019-12-04 06:54:46 +01:00
shareButton . setTitle ( NSLocalizedString ( " Share " , comment : " " ) , for : UIControl . State . normal )
shareButton . addTarget ( self , action : #selector ( sharePublicKey ) , for : UIControl . Event . touchUpInside )
// S e t u p b u t t o n c o n t a i n e r
let buttonContainer = UIStackView ( arrangedSubviews : [ copyButton , shareButton ] )
buttonContainer . axis = . horizontal
buttonContainer . spacing = Values . mediumSpacing
buttonContainer . distribution = . fillEqually
2019-12-05 00:10:09 +01:00
// S e t u p t o p s t a c k v i e w
let topStackView = UIStackView ( arrangedSubviews : [ headerStackView , separator , publicKeyLabel , buttonContainer ] )
topStackView . axis = . vertical
topStackView . spacing = Values . largeSpacing
topStackView . alignment = . fill
topStackView . layoutMargins = UIEdgeInsets ( top : 0 , left : Values . largeSpacing , bottom : 0 , right : Values . largeSpacing )
topStackView . isLayoutMarginsRelativeArrangement = true
// S e t u p s e t t i n g b u t t o n s s t a c k v i e w
2019-12-09 03:24:03 +01:00
let settingButtonsStackView = UIStackView ( arrangedSubviews : getSettingButtons ( ) )
2019-12-05 00:10:09 +01:00
settingButtonsStackView . axis = . vertical
settingButtonsStackView . alignment = . fill
2019-12-04 06:54:46 +01:00
// S e t u p s t a c k v i e w
2019-12-05 00:10:09 +01:00
let stackView = UIStackView ( arrangedSubviews : [ topStackView , settingButtonsStackView ] )
2019-12-04 06:54:46 +01:00
stackView . axis = . vertical
stackView . spacing = Values . largeSpacing
stackView . alignment = . fill
2019-12-05 00:10:09 +01:00
stackView . layoutMargins = UIEdgeInsets ( top : Values . mediumSpacing , left : 0 , bottom : Values . mediumSpacing , right : 0 )
2019-12-04 06:54:46 +01:00
stackView . isLayoutMarginsRelativeArrangement = true
2019-12-05 00:10:09 +01:00
stackView . set ( . width , to : UIScreen . main . bounds . width )
// S e t u p s c r o l l v i e w
let scrollView = UIScrollView ( )
scrollView . showsVerticalScrollIndicator = false
scrollView . addSubview ( stackView )
stackView . pin ( to : scrollView )
view . addSubview ( scrollView )
scrollView . pin ( to : view )
}
2019-12-09 03:24:03 +01:00
private func getSettingButtons ( ) -> [ UIView ] {
func getSeparator ( ) -> UIView {
let result = UIView ( )
result . backgroundColor = Colors . separator
result . set ( . height , to : Values . separatorThickness )
return result
}
2019-12-05 00:10:09 +01:00
func getSettingButton ( withTitle title : String , color : UIColor , action selector : Selector ) -> UIButton {
let button = UIButton ( )
button . setTitle ( title , for : UIControl . State . normal )
button . setTitleColor ( color , for : UIControl . State . normal )
button . titleLabel ! . font = . boldSystemFont ( ofSize : Values . mediumFontSize )
button . titleLabel ! . textAlignment = . center
func getImage ( withColor color : UIColor ) -> UIImage {
let rect = CGRect ( origin : CGPoint . zero , size : CGSize ( width : 1 , height : 1 ) )
UIGraphicsBeginImageContext ( rect . size )
let context = UIGraphicsGetCurrentContext ( ) !
context . setFillColor ( color . cgColor )
context . fill ( rect )
let image = UIGraphicsGetImageFromCurrentImageContext ( )
UIGraphicsEndImageContext ( )
return image !
}
2019-12-05 01:42:31 +01:00
button . setBackgroundImage ( getImage ( withColor : Colors . buttonBackground ) , for : UIControl . State . normal )
2019-12-05 00:10:09 +01:00
button . setBackgroundImage ( getImage ( withColor : Colors . settingButtonSelected ) , for : UIControl . State . highlighted )
button . addTarget ( self , action : selector , for : UIControl . Event . touchUpInside )
2019-12-05 04:31:45 +01:00
button . set ( . height , to : Values . settingButtonHeight )
2019-12-05 00:10:09 +01:00
return button
}
2019-12-05 04:31:45 +01:00
var result = [
2019-12-09 03:24:03 +01:00
getSeparator ( ) ,
2019-12-05 00:10:09 +01:00
getSettingButton ( withTitle : NSLocalizedString ( " Privacy " , comment : " " ) , color : Colors . text , action : #selector ( showPrivacySettings ) ) ,
2019-12-09 03:24:03 +01:00
getSeparator ( ) ,
2019-12-05 04:31:45 +01:00
getSettingButton ( withTitle : NSLocalizedString ( " Notifications " , comment : " " ) , color : Colors . text , action : #selector ( showNotificationSettings ) )
2019-12-05 00:10:09 +01:00
]
2019-12-05 04:31:45 +01:00
let isMasterDevice = ( UserDefaults . standard . string ( forKey : " masterDeviceHexEncodedPublicKey " ) = = nil )
if isMasterDevice {
2019-12-09 03:24:03 +01:00
result . append ( getSeparator ( ) )
2020-01-23 05:01:31 +01:00
result . append ( getSettingButton ( withTitle : NSLocalizedString ( " Devices " , comment : " " ) , color : Colors . text , action : #selector ( showLinkedDevices ) ) )
2019-12-09 03:24:03 +01:00
result . append ( getSeparator ( ) )
2020-01-23 05:01:31 +01:00
result . append ( getSettingButton ( withTitle : NSLocalizedString ( " Recovery Phrase " , comment : " " ) , color : Colors . text , action : #selector ( showSeed ) ) )
2019-12-05 04:31:45 +01:00
}
2019-12-09 03:24:03 +01:00
result . append ( getSeparator ( ) )
2019-12-05 04:31:45 +01:00
result . append ( getSettingButton ( withTitle : NSLocalizedString ( " Clear All Data " , comment : " " ) , color : Colors . destructive , action : #selector ( clearAllData ) ) )
2019-12-09 03:24:03 +01:00
result . append ( getSeparator ( ) )
2019-12-05 04:31:45 +01:00
return result
2019-12-05 00:10:09 +01:00
}
// MARK: G e n e r a l
2019-12-04 06:54:46 +01:00
@objc private func enableCopyButton ( ) {
copyButton . isUserInteractionEnabled = true
UIView . transition ( with : copyButton , duration : 0.25 , options : . transitionCrossDissolve , animations : {
self . copyButton . setTitle ( NSLocalizedString ( " Copy " , comment : " " ) , for : UIControl . State . normal )
} , completion : nil )
}
2019-12-06 01:54:04 +01:00
func avatarActionSheetTitle ( ) -> String ? {
return NSLocalizedString ( " Update Profile Picture " , comment : " " )
}
func fromViewController ( ) -> UIViewController {
return self
}
func hasClearAvatarAction ( ) -> Bool {
return true
}
func clearAvatarActionLabel ( ) -> String {
return NSLocalizedString ( " Clear " , comment : " " )
}
// MARK: U p d a t i n g
private func handleIsEditingDisplayNameChanged ( ) {
updateNavigationBarButtons ( )
UIView . animate ( withDuration : 0.25 ) {
self . displayNameLabel . alpha = self . isEditingDisplayName ? 0 : 1
self . displayNameTextField . alpha = self . isEditingDisplayName ? 1 : 0
}
if isEditingDisplayName {
displayNameTextField . becomeFirstResponder ( )
} else {
displayNameTextField . resignFirstResponder ( )
}
}
private func updateNavigationBarButtons ( ) {
if isEditingDisplayName {
let cancelButton = UIBarButtonItem ( barButtonSystemItem : . cancel , target : self , action : #selector ( handleCancelDisplayNameEditingButtonTapped ) )
cancelButton . tintColor = Colors . text
navigationItem . leftBarButtonItem = cancelButton
let doneButton = UIBarButtonItem ( barButtonSystemItem : . done , target : self , action : #selector ( handleSaveDisplayNameButtonTapped ) )
doneButton . tintColor = Colors . text
navigationItem . rightBarButtonItem = doneButton
} else {
let closeButton = UIBarButtonItem ( image : # imageLiteral ( resourceName : " X " ) , style : . plain , target : self , action : #selector ( close ) )
closeButton . tintColor = Colors . text
navigationItem . leftBarButtonItem = closeButton
2019-12-10 00:26:09 +01:00
let qrCodeButton = UIBarButtonItem ( image : # imageLiteral ( resourceName : " QRCode " ) , style : . plain , target : self , action : #selector ( showQRCode ) )
2019-12-06 01:54:04 +01:00
qrCodeButton . tintColor = Colors . text
navigationItem . rightBarButtonItem = qrCodeButton
}
}
func avatarDidChange ( _ image : UIImage ) {
let maxSize = Int ( kOWSProfileManager_MaxAvatarDiameter )
profilePictureToBeUploaded = image . resizedImage ( toFillPixelSize : CGSize ( width : maxSize , height : maxSize ) )
updateProfile ( isUpdatingDisplayName : false , isUpdatingProfilePicture : true )
}
func clearAvatar ( ) {
profilePictureToBeUploaded = nil
updateProfile ( isUpdatingDisplayName : false , isUpdatingProfilePicture : true )
}
private func updateProfile ( isUpdatingDisplayName : Bool , isUpdatingProfilePicture : Bool ) {
let displayName = displayNameToBeUploaded ? ? OWSProfileManager . shared ( ) . profileName ( forRecipientId : userHexEncodedPublicKey )
let profilePicture = profilePictureToBeUploaded ? ? OWSProfileManager . shared ( ) . profileAvatar ( forRecipientId : userHexEncodedPublicKey )
ModalActivityIndicatorViewController . present ( fromViewController : navigationController ! , canCancel : false ) { [ weak self ] modalActivityIndicator in
OWSProfileManager . shared ( ) . updateLocalProfileName ( displayName , avatarImage : profilePicture , success : {
DispatchQueue . main . async {
modalActivityIndicator . dismiss {
guard let self = self else { return }
self . profilePictureView . update ( )
self . displayNameLabel . text = displayName
self . profilePictureToBeUploaded = nil
self . displayNameToBeUploaded = nil
}
}
} , failure : {
DispatchQueue . main . async {
modalActivityIndicator . dismiss {
let alert = UIAlertController ( title : NSLocalizedString ( " Couldn't Update Profile " , comment : " " ) , message : NSLocalizedString ( " Please check your internet connection and try again " , comment : " " ) , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default , handler : nil ) )
self ? . present ( alert , animated : true , completion : nil )
}
}
} )
}
}
2019-12-04 06:54:46 +01:00
// MARK: I n t e r a c t i o n
@objc private func close ( ) {
dismiss ( animated : true , completion : nil )
}
@objc private func showQRCode ( ) {
2019-12-05 05:28:32 +01:00
let qrCodeVC = QRCodeVC ( )
navigationController ! . pushViewController ( qrCodeVC , animated : true )
2019-12-04 06:54:46 +01:00
}
2019-12-06 01:54:04 +01:00
@objc private func handleCancelDisplayNameEditingButtonTapped ( ) {
isEditingDisplayName = false
}
@objc private func handleSaveDisplayNameButtonTapped ( ) {
func showError ( title : String , message : String = " " ) {
let alert = UIAlertController ( title : title , message : message , preferredStyle : . alert )
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default , handler : nil ) )
presentAlert ( alert )
}
let displayName = displayNameTextField . text ! . trimmingCharacters ( in : CharacterSet . whitespacesAndNewlines )
guard ! displayName . isEmpty else {
return showError ( title : NSLocalizedString ( " Please pick a display name " , comment : " " ) )
}
let allowedCharacters = CharacterSet ( charactersIn : " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_ " )
let hasInvalidCharacters = ! displayName . allSatisfy { $0 . unicodeScalars . allSatisfy { allowedCharacters . contains ( $0 ) } }
guard ! hasInvalidCharacters else {
return showError ( title : NSLocalizedString ( " Please pick a display name that consists of only a-z, A-Z, 0-9 and _ characters " , comment : " " ) )
}
guard ! OWSProfileManager . shared ( ) . isProfileNameTooLong ( displayName ) else {
return showError ( title : NSLocalizedString ( " Please pick a shorter display name " , comment : " " ) )
}
isEditingDisplayName = false
displayNameToBeUploaded = displayName
updateProfile ( isUpdatingDisplayName : true , isUpdatingProfilePicture : false )
}
@objc private func showEditProfilePictureUI ( ) {
profilePictureUtilities . showChangeAvatarUI ( )
}
@objc private func showEditDisplayNameUI ( ) {
isEditingDisplayName = true
}
2019-12-04 06:54:46 +01:00
@objc private func copyPublicKey ( ) {
UIPasteboard . general . string = userHexEncodedPublicKey
copyButton . isUserInteractionEnabled = false
UIView . transition ( with : copyButton , duration : 0.25 , options : . transitionCrossDissolve , animations : {
self . copyButton . setTitle ( NSLocalizedString ( " Copied " , comment : " " ) , for : UIControl . State . normal )
} , completion : nil )
Timer . scheduledTimer ( timeInterval : 4 , target : self , selector : #selector ( enableCopyButton ) , userInfo : nil , repeats : false )
}
@objc private func sharePublicKey ( ) {
let shareVC = UIActivityViewController ( activityItems : [ userHexEncodedPublicKey ] , applicationActivities : nil )
navigationController ! . present ( shareVC , animated : true , completion : nil )
}
2019-12-05 00:10:09 +01:00
@objc private func showPrivacySettings ( ) {
let privacySettingsVC = PrivacySettingsTableViewController ( )
navigationController ! . pushViewController ( privacySettingsVC , animated : true )
2019-12-04 06:54:46 +01:00
}
2019-12-05 00:10:09 +01:00
@objc private func showNotificationSettings ( ) {
let notificationSettingsVC = NotificationSettingsViewController ( )
navigationController ! . pushViewController ( notificationSettingsVC , animated : true )
2019-12-04 06:54:46 +01:00
}
2019-12-05 00:10:09 +01:00
@objc private func showLinkedDevices ( ) {
let deviceLinksVC = DeviceLinksVC ( )
navigationController ! . pushViewController ( deviceLinksVC , animated : true )
2019-12-04 06:54:46 +01:00
}
2019-12-05 00:10:09 +01:00
@objc private func showSeed ( ) {
let seedModal = SeedModal ( )
seedModal . modalPresentationStyle = . overFullScreen
2019-12-05 01:42:31 +01:00
seedModal . modalTransitionStyle = . crossDissolve
2019-12-05 00:10:09 +01:00
present ( seedModal , animated : true , completion : nil )
2019-12-04 06:54:46 +01:00
}
2019-12-05 00:10:09 +01:00
@objc private func clearAllData ( ) {
let nukeDataModal = NukeDataModal ( )
nukeDataModal . modalPresentationStyle = . overFullScreen
2019-12-05 01:42:31 +01:00
nukeDataModal . modalTransitionStyle = . crossDissolve
2019-12-05 00:10:09 +01:00
present ( nukeDataModal , animated : true , completion : nil )
2019-12-04 06:54:46 +01:00
}
}