2019-12-05 05:28:32 +01:00
final class QRCodeVC : UIViewController , UIPageViewControllerDataSource , UIPageViewControllerDelegate , OWSQRScannerDelegate {
private let pageVC = UIPageViewController ( transitionStyle : . scroll , navigationOrientation : . horizontal , options : nil )
private var pages : [ UIViewController ] = [ ]
private var targetVCIndex : Int ?
private var tabBarTopConstraint : NSLayoutConstraint !
// 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
private lazy var tabBar : TabBar = {
let tabs = [
TabBar . Tab ( title : NSLocalizedString ( " View My QR Code " , comment : " " ) ) { [ weak self ] in
guard let self = self else { return }
self . pageVC . setViewControllers ( [ self . pages [ 0 ] ] , direction : . forward , animated : false , completion : nil )
} ,
TabBar . Tab ( title : NSLocalizedString ( " Scan QR Code " , comment : " " ) ) { [ weak self ] in
guard let self = self else { return }
self . pageVC . setViewControllers ( [ self . pages [ 1 ] ] , direction : . forward , animated : false , completion : nil )
}
]
return TabBar ( tabs : tabs )
} ( )
private lazy var viewMyQRCodeVC : ViewMyQRCodeVC = {
let result = ViewMyQRCodeVC ( )
result . qrCodeVC = self
return result
} ( )
private lazy var scanQRCodePlaceholderVC : ScanQRCodePlaceholderVC = {
let result = ScanQRCodePlaceholderVC ( )
result . qrCodeVC = self
return result
} ( )
private lazy var scanQRCodeWrapperVC : ScanQRCodeWrapperVC = {
let message = NSLocalizedString ( " Scan someone's QR code to start a conversation with them " , comment : " " )
let result = ScanQRCodeWrapperVC ( message : message )
result . delegate = self
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
// C u s t o m i z e t i t l e
let titleLabel = UILabel ( )
titleLabel . text = NSLocalizedString ( " QR Code " , comment : " " )
titleLabel . textColor = Colors . text
titleLabel . font = . boldSystemFont ( ofSize : Values . veryLargeFontSize )
navigationItem . titleView = titleLabel
// S e t u p p a g e V C
let hasCameraAccess = ( AVCaptureDevice . authorizationStatus ( for : . video ) = = . authorized )
pages = [ viewMyQRCodeVC , ( hasCameraAccess ? scanQRCodeWrapperVC : scanQRCodePlaceholderVC ) ]
pageVC . dataSource = self
pageVC . delegate = self
pageVC . setViewControllers ( [ viewMyQRCodeVC ] , direction : . forward , animated : false , completion : nil )
// S e t u p t a b b a r
view . addSubview ( tabBar )
tabBar . pin ( . leading , to : . leading , of : view )
2020-01-21 01:30:01 +01:00
tabBarTopConstraint = tabBar . autoPinEdge ( toSuperviewSafeArea : . top )
2019-12-05 05:28:32 +01:00
view . pin ( . trailing , to : . trailing , of : tabBar )
// S e t u p p a g e V C c o n s t r a i n t s
let pageVCView = pageVC . view !
view . addSubview ( pageVCView )
pageVCView . pin ( . leading , to : . leading , of : view )
pageVCView . pin ( . top , to : . bottom , of : tabBar )
view . pin ( . trailing , to : . trailing , of : pageVCView )
view . pin ( . bottom , to : . bottom , of : pageVCView )
let screen = UIScreen . main . bounds
pageVCView . set ( . width , to : screen . width )
2020-01-21 01:30:01 +01:00
let height : CGFloat
if #available ( iOS 13 , * ) {
height = navigationController ! . view . bounds . height - navigationBar . height ( ) - Values . tabBarHeight
} else {
2020-01-22 00:10:16 +01:00
let statusBarHeight = UIApplication . shared . statusBarFrame . height
2020-01-21 01:30:01 +01:00
height = navigationController ! . view . bounds . height - navigationBar . height ( ) - Values . tabBarHeight - statusBarHeight
}
2019-12-05 05:28:32 +01:00
pageVCView . set ( . height , to : height )
viewMyQRCodeVC . constrainHeight ( to : height )
scanQRCodePlaceholderVC . constrainHeight ( to : height )
}
override func viewDidAppear ( _ animated : Bool ) {
super . viewDidAppear ( animated )
tabBarTopConstraint . constant = navigationController ! . navigationBar . height ( )
}
// MARK: G e n e r a l
func pageViewController ( _ pageViewController : UIPageViewController , viewControllerBefore viewController : UIViewController ) -> UIViewController ? {
guard let index = pages . firstIndex ( of : viewController ) , index != 0 else { return nil }
return pages [ index - 1 ]
}
func pageViewController ( _ pageViewController : UIPageViewController , viewControllerAfter viewController : UIViewController ) -> UIViewController ? {
guard let index = pages . firstIndex ( of : viewController ) , index != ( pages . count - 1 ) else { return nil }
return pages [ index + 1 ]
}
fileprivate func handleCameraAccessGranted ( ) {
pages [ 1 ] = scanQRCodeWrapperVC
pageVC . setViewControllers ( [ scanQRCodeWrapperVC ] , direction : . forward , animated : false , completion : nil )
}
// MARK: U p d a t i n g
func pageViewController ( _ pageViewController : UIPageViewController , willTransitionTo pendingViewControllers : [ UIViewController ] ) {
guard let targetVC = pendingViewControllers . first , let index = pages . firstIndex ( of : targetVC ) else { return }
targetVCIndex = index
}
func pageViewController ( _ pageViewController : UIPageViewController , didFinishAnimating isFinished : Bool , previousViewControllers : [ UIViewController ] , transitionCompleted isCompleted : Bool ) {
guard isCompleted , let index = targetVCIndex else { return }
tabBar . selectTab ( at : index )
}
// MARK: I n t e r a c t i o n
@objc private func close ( ) {
dismiss ( animated : true , completion : nil )
}
func controller ( _ controller : OWSQRCodeScanningViewController , didDetectQRCodeWith string : String ) {
let hexEncodedPublicKey = string
startNewPrivateChatIfPossible ( with : hexEncodedPublicKey )
}
fileprivate func startNewPrivateChatIfPossible ( with hexEncodedPublicKey : String ) {
if ! ECKeyPair . isValidHexEncodedPublicKey ( candidate : hexEncodedPublicKey ) {
2020-01-08 06:10:12 +01:00
let alert = UIAlertController ( title : NSLocalizedString ( " Invalid Session ID " , comment : " " ) , message : NSLocalizedString ( " Please check the Session ID you entered and try again. " , comment : " " ) , preferredStyle : . alert )
2019-12-05 05:28:32 +01:00
alert . addAction ( UIAlertAction ( title : NSLocalizedString ( " OK " , comment : " " ) , style : . default , handler : nil ) )
presentAlert ( alert )
} else {
let thread = TSContactThread . getOrCreateThread ( contactId : hexEncodedPublicKey )
presentingViewController ? . dismiss ( animated : true , completion : nil )
SignalApp . shared ( ) . presentConversation ( for : thread , action : . compose , animated : false )
}
}
}
private final class ViewMyQRCodeVC : UIViewController {
weak var qrCodeVC : QRCodeVC !
private var bottomConstraint : NSLayoutConstraint !
private lazy var userHexEncodedPublicKey : String = {
if let masterHexEncodedPublicKey = UserDefaults . standard . string ( forKey : " masterDeviceHexEncodedPublicKey " ) {
return masterHexEncodedPublicKey
} else {
return OWSIdentityManager . shared ( ) . identityKeyPair ( ) ! . hexEncodedPublicKey
}
} ( )
// MARK: L i f e c y c l e
override func viewDidLoad ( ) {
// R e m o v e b a c k g r o u n d c o l o r
view . backgroundColor = . clear
// S e t u p t i t l e l a b e l
let titleLabel = UILabel ( )
titleLabel . textColor = Colors . text
2020-01-17 05:53:56 +01:00
titleLabel . font = . boldSystemFont ( ofSize : isSmallScreen ? CGFloat ( 40 ) : Values . massiveFontSize )
2019-12-05 05:28:32 +01:00
titleLabel . text = NSLocalizedString ( " Scan Me " , comment : " " )
titleLabel . numberOfLines = 0
titleLabel . textAlignment = . center
titleLabel . lineBreakMode = . byWordWrapping
// S e t u p Q R c o d e i m a g e v i e w
let qrCodeImageView = UIImageView ( )
2020-01-17 05:53:56 +01:00
let qrCode = QRCode . generate ( for : userHexEncodedPublicKey , hasBackground : true )
2019-12-05 05:28:32 +01:00
qrCodeImageView . image = qrCode
qrCodeImageView . contentMode = . scaleAspectFit
2020-01-17 05:53:56 +01:00
qrCodeImageView . set ( . height , to : isSmallScreen ? 180 : 240 )
qrCodeImageView . set ( . width , to : isSmallScreen ? 180 : 240 )
2019-12-12 01:10:26 +01:00
// S e t u p Q R c o d e i m a g e v i e w c o n t a i n e r
let qrCodeImageViewContainer = UIView ( )
qrCodeImageViewContainer . addSubview ( qrCodeImageView )
qrCodeImageView . center ( . horizontal , in : qrCodeImageViewContainer )
qrCodeImageView . pin ( . top , to : . top , of : qrCodeImageViewContainer )
qrCodeImageView . pin ( . bottom , to : . bottom , of : qrCodeImageViewContainer )
2019-12-05 05:28:32 +01:00
// S e t u p e x p l a n a t i o n l a b e l
let explanationLabel = UILabel ( )
explanationLabel . textColor = Colors . text
2020-01-08 06:10:12 +01:00
explanationLabel . font = . systemFont ( ofSize : Values . mediumFontSize )
let text = NSLocalizedString ( " This is your unique public QR code. Other users can scan this to start a conversation with you. " , comment : " " )
2019-12-05 05:28:32 +01:00
let attributedText = NSMutableAttributedString ( string : text )
2020-01-08 06:10:12 +01:00
attributedText . addAttribute ( . font , value : UIFont . boldSystemFont ( ofSize : Values . mediumFontSize ) , range : ( text as NSString ) . range ( of : " your unique public QR code " ) )
2019-12-05 05:28:32 +01:00
explanationLabel . attributedText = attributedText
explanationLabel . numberOfLines = 0
explanationLabel . textAlignment = . center
explanationLabel . lineBreakMode = . byWordWrapping
// S e t u p s h a r e b u t t o n
let shareButton = Button ( style : . regular , size : . large )
shareButton . setTitle ( NSLocalizedString ( " Share " , comment : " " ) , for : UIControl . State . normal )
shareButton . addTarget ( self , action : #selector ( shareQRCode ) , for : UIControl . Event . touchUpInside )
// S e t u p s h a r e b u t t o n c o n t a i n e r
let shareButtonContainer = UIView ( )
shareButtonContainer . addSubview ( shareButton )
shareButton . pin ( . leading , to : . leading , of : shareButtonContainer , withInset : 80 )
shareButton . pin ( . top , to : . top , of : shareButtonContainer )
shareButtonContainer . pin ( . trailing , to : . trailing , of : shareButton , withInset : 80 )
shareButtonContainer . pin ( . bottom , to : . bottom , of : shareButton )
// S e t u p s t a c k v i e w
2019-12-12 01:10:26 +01:00
let stackView = UIStackView ( arrangedSubviews : [ titleLabel , qrCodeImageViewContainer , explanationLabel , shareButtonContainer , UIView . vStretchingSpacer ( ) ] )
2019-12-05 05:28:32 +01:00
stackView . axis = . vertical
2020-01-17 05:53:56 +01:00
stackView . spacing = isSmallScreen ? Values . mediumSpacing : Values . largeSpacing
2019-12-05 05:28:32 +01:00
stackView . alignment = . fill
2020-01-22 00:10:16 +01:00
stackView . layoutMargins = UIEdgeInsets ( top : Values . largeSpacing , left : Values . largeSpacing , bottom : Values . largeSpacing , right : Values . largeSpacing )
2019-12-05 05:28:32 +01:00
stackView . isLayoutMarginsRelativeArrangement = true
view . addSubview ( stackView )
stackView . pin ( . leading , to : . leading , of : view )
stackView . pin ( . top , to : . top , of : view )
view . pin ( . trailing , to : . trailing , of : stackView )
bottomConstraint = view . pin ( . bottom , to : . bottom , of : stackView )
// S e t u p w i d t h c o n s t r a i n t
view . set ( . width , to : UIScreen . main . bounds . width )
}
// MARK: G e n e r a l
func constrainHeight ( to height : CGFloat ) {
view . set ( . height , to : height )
}
// MARK: I n t e r a c t i o n
@objc private func shareQRCode ( ) {
2019-12-12 01:10:26 +01:00
let qrCode = QRCode . generate ( for : userHexEncodedPublicKey , hasBackground : true )
2019-12-05 05:28:32 +01:00
let shareVC = UIActivityViewController ( activityItems : [ qrCode ] , applicationActivities : nil )
qrCodeVC . navigationController ! . present ( shareVC , animated : true , completion : nil )
}
}
private final class ScanQRCodePlaceholderVC : UIViewController {
weak var qrCodeVC : QRCodeVC !
override func viewDidLoad ( ) {
// R e m o v e b a c k g r o u n d c o l o r
view . backgroundColor = . clear
// S e t u p e x p l a n a t i o n l a b e l
let explanationLabel = UILabel ( )
explanationLabel . textColor = Colors . text
explanationLabel . font = . systemFont ( ofSize : Values . smallFontSize )
2020-01-08 06:10:12 +01:00
explanationLabel . text = NSLocalizedString ( " Session needs camera access to scan QR codes " , comment : " " )
2019-12-05 05:28:32 +01:00
explanationLabel . numberOfLines = 0
explanationLabel . textAlignment = . center
explanationLabel . lineBreakMode = . byWordWrapping
// S e t u p c a l l t o a c t i o n b u t t o n
let callToActionButton = UIButton ( )
callToActionButton . titleLabel ! . font = . boldSystemFont ( ofSize : Values . mediumFontSize )
callToActionButton . setTitleColor ( Colors . accent , for : UIControl . State . normal )
callToActionButton . setTitle ( NSLocalizedString ( " Enable Camera Access " , comment : " " ) , for : UIControl . State . normal )
callToActionButton . addTarget ( self , action : #selector ( requestCameraAccess ) , for : UIControl . Event . touchUpInside )
// S e t u p s t a c k v i e w
let stackView = UIStackView ( arrangedSubviews : [ explanationLabel , callToActionButton ] )
stackView . axis = . vertical
stackView . spacing = Values . mediumSpacing
stackView . alignment = . center
// S e t u p c o n s t r a i n t s
view . set ( . width , to : UIScreen . main . bounds . width )
view . addSubview ( stackView )
stackView . pin ( . leading , to : . leading , of : view , withInset : Values . massiveSpacing )
view . pin ( . trailing , to : . trailing , of : stackView , withInset : Values . massiveSpacing )
let verticalCenteringConstraint = stackView . center ( . vertical , in : view )
verticalCenteringConstraint . constant = - 16 // M a k e s t h i n g s a p p e a r c e n t e r e d v i s u a l l y
}
func constrainHeight ( to height : CGFloat ) {
view . set ( . height , to : height )
}
@objc private func requestCameraAccess ( ) {
ows_ask ( forCameraPermissions : { [ weak self ] hasCameraAccess in
if hasCameraAccess {
self ? . qrCodeVC . handleCameraAccessGranted ( )
} else {
// D o n o t h i n g
}
} )
}
}