mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Updated the HomeVC, SettingsVC and GlobalSearch UI to use theming Removed the "fade view" gradients from the various screens Added a simple log to the PagedDatabaseObserver to make debugging easier Updated the FullConversationCell to also show the "read" state for messages Updated the read receipt icons to use SFSymbols directly Updated the PlaceholderIcon to use the PrimaryColour's as it's colour options
296 lines
13 KiB
Swift
296 lines
13 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import UIKit
|
|
import Curve25519Kit
|
|
import SessionUIKit
|
|
import SessionUtilitiesKit
|
|
|
|
final class QRCodeVC : BaseVC, 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: - Components
|
|
|
|
private lazy var tabBar: TabBar = {
|
|
let tabs = [
|
|
TabBar.Tab(title: "vc_qr_code_view_my_qr_code_tab_title".localized()) { [weak self] in
|
|
guard let self = self else { return }
|
|
self.pageVC.setViewControllers([ self.pages[0] ], direction: .forward, animated: false, completion: nil)
|
|
},
|
|
TabBar.Tab(title: "vc_qr_code_view_scan_qr_code_tab_title".localized()) { [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 = "vc_qr_code_view_scan_qr_code_explanation".localized()
|
|
let result = ScanQRCodeWrapperVC(message: message)
|
|
result.delegate = self
|
|
|
|
return result
|
|
}()
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
setNavBarTitle("vc_qr_code_title".localized())
|
|
|
|
// Set up page VC
|
|
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)
|
|
|
|
// Set up tab bar
|
|
view.addSubview(tabBar)
|
|
tabBar.pin(.leading, to: .leading, of: view)
|
|
tabBarTopConstraint = tabBar.autoPinEdge(toSuperviewSafeArea: .top)
|
|
view.pin(.trailing, to: .trailing, of: tabBar)
|
|
|
|
// Set up page VC constraints
|
|
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)
|
|
|
|
let height: CGFloat = ((navigationController?.view.bounds.height ?? 0) - (navigationController?.navigationBar.height() ?? 0) - TabBar.snHeight)
|
|
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: - General
|
|
|
|
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: - Updating
|
|
|
|
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: - Interaction
|
|
|
|
@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) {
|
|
let alert = UIAlertController(
|
|
title: "invalid_session_id".localized(),
|
|
message: "INVALID_SESSION_ID_MESSAGE".localized(), preferredStyle: .alert)
|
|
alert.addAction(UIAlertAction(title: "BUTTON_OK".localized(), style: .default, handler: nil))
|
|
presentAlert(alert)
|
|
}
|
|
else {
|
|
let maybeThread: SessionThread? = Storage.shared.write { db in
|
|
try SessionThread.fetchOrCreate(db, id: hexEncodedPublicKey, variant: .contact)
|
|
}
|
|
|
|
guard maybeThread != nil else { return }
|
|
|
|
presentingViewController?.dismiss(animated: true, completion: nil)
|
|
|
|
SessionApp.presentConversation(for: hexEncodedPublicKey, action: .compose, animated: false)
|
|
}
|
|
}
|
|
}
|
|
|
|
private final class ViewMyQRCodeVC : UIViewController {
|
|
weak var qrCodeVC: QRCodeVC!
|
|
private var bottomConstraint: NSLayoutConstraint!
|
|
|
|
// MARK: - Lifecycle
|
|
|
|
override func viewDidLoad() {
|
|
// Remove background color
|
|
view.backgroundColor = .clear
|
|
// Set up title label
|
|
let titleLabel = UILabel()
|
|
titleLabel.textColor = Colors.text
|
|
titleLabel.font = .boldSystemFont(ofSize: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
|
|
titleLabel.text = "Scan Me"
|
|
titleLabel.numberOfLines = 1
|
|
titleLabel.textAlignment = .center
|
|
titleLabel.lineBreakMode = .byWordWrapping
|
|
titleLabel.set(.height, to: isIPhone5OrSmaller ? CGFloat(40) : Values.massiveFontSize)
|
|
// Set up QR code image view
|
|
let qrCodeImageView = UIImageView()
|
|
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
|
|
qrCodeImageView.image = qrCode
|
|
qrCodeImageView.contentMode = .scaleAspectFit
|
|
qrCodeImageView.set(.height, to: isIPhone5OrSmaller ? 180 : 240)
|
|
qrCodeImageView.set(.width, to: isIPhone5OrSmaller ? 180 : 240)
|
|
// Set up QR code image view container
|
|
let qrCodeImageViewContainer = UIView()
|
|
qrCodeImageViewContainer.accessibilityLabel = "Your QR code"
|
|
qrCodeImageViewContainer.isAccessibilityElement = true
|
|
qrCodeImageViewContainer.addSubview(qrCodeImageView)
|
|
qrCodeImageView.center(.horizontal, in: qrCodeImageViewContainer)
|
|
qrCodeImageView.pin(.top, to: .top, of: qrCodeImageViewContainer)
|
|
qrCodeImageView.pin(.bottom, to: .bottom, of: qrCodeImageViewContainer)
|
|
// Set up explanation label
|
|
let explanationLabel = UILabel()
|
|
explanationLabel.textColor = Colors.text
|
|
explanationLabel.font = .systemFont(ofSize: Values.mediumFontSize)
|
|
explanationLabel.text = NSLocalizedString("vc_view_my_qr_code_explanation", comment: "")
|
|
explanationLabel.numberOfLines = 0
|
|
explanationLabel.textAlignment = .center
|
|
explanationLabel.lineBreakMode = .byWordWrapping
|
|
|
|
// Set up share button
|
|
let shareButton = OutlineButton(style: .regular, size: .large)
|
|
shareButton.setTitle("share".localized(), for: .normal)
|
|
shareButton.addTarget(self, action: #selector(shareQRCode), for: .touchUpInside)
|
|
|
|
// Set up share button container
|
|
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, withInset: isIPhone6OrSmaller ? Values.largeSpacing : Values.onboardingButtonBottomOffset)
|
|
let spacing = isIPhone5OrSmaller ? Values.mediumSpacing : Values.largeSpacing
|
|
|
|
// Set up stack view
|
|
let stackView = UIStackView(arrangedSubviews: [ titleLabel, UIView.spacer(withHeight: spacing), qrCodeImageViewContainer, UIView.spacer(withHeight: spacing),
|
|
explanationLabel, UIView.vStretchingSpacer(), shareButtonContainer ])
|
|
stackView.axis = .vertical
|
|
stackView.alignment = .fill
|
|
stackView.layoutMargins = UIEdgeInsets(top: Values.largeSpacing, left: Values.largeSpacing, bottom: 0, right: Values.largeSpacing)
|
|
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)
|
|
// Set up width constraint
|
|
view.set(.width, to: UIScreen.main.bounds.width)
|
|
}
|
|
|
|
// MARK: General
|
|
func constrainHeight(to height: CGFloat) {
|
|
view.set(.height, to: height)
|
|
}
|
|
|
|
// MARK: Interaction
|
|
@objc private func shareQRCode() {
|
|
let qrCode = QRCode.generate(for: getUserHexEncodedPublicKey(), hasBackground: true)
|
|
let shareVC = UIActivityViewController(activityItems: [ qrCode ], applicationActivities: nil)
|
|
if UIDevice.current.isIPad {
|
|
shareVC.excludedActivityTypes = []
|
|
shareVC.popoverPresentationController?.permittedArrowDirections = []
|
|
shareVC.popoverPresentationController?.sourceView = self.view
|
|
shareVC.popoverPresentationController?.sourceRect = self.view.bounds
|
|
}
|
|
qrCodeVC.navigationController!.present(shareVC, animated: true, completion: nil)
|
|
}
|
|
}
|
|
|
|
private final class ScanQRCodePlaceholderVC : UIViewController {
|
|
weak var qrCodeVC: QRCodeVC!
|
|
|
|
override func viewDidLoad() {
|
|
// Remove background color
|
|
view.backgroundColor = .clear
|
|
// Set up explanation label
|
|
let explanationLabel = UILabel()
|
|
explanationLabel.textColor = Colors.text
|
|
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
|
|
explanationLabel.text = NSLocalizedString("vc_scan_qr_code_camera_access_explanation", comment: "")
|
|
explanationLabel.numberOfLines = 0
|
|
explanationLabel.textAlignment = .center
|
|
explanationLabel.lineBreakMode = .byWordWrapping
|
|
// Set up call to action button
|
|
let callToActionButton = UIButton()
|
|
callToActionButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
|
callToActionButton.setTitleColor(Colors.accent, for: UIControl.State.normal)
|
|
callToActionButton.setTitle(NSLocalizedString("vc_scan_qr_code_grant_camera_access_button_title", comment: ""), for: UIControl.State.normal)
|
|
callToActionButton.addTarget(self, action: #selector(requestCameraAccess), for: UIControl.Event.touchUpInside)
|
|
// Set up stack view
|
|
let stackView = UIStackView(arrangedSubviews: [ explanationLabel, callToActionButton ])
|
|
stackView.axis = .vertical
|
|
stackView.spacing = Values.mediumSpacing
|
|
stackView.alignment = .center
|
|
// Set up constraints
|
|
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 // Makes things appear centered visually
|
|
}
|
|
|
|
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 {
|
|
// Do nothing
|
|
}
|
|
})
|
|
}
|
|
}
|