mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			208 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			208 lines
		
	
	
		
			7.1 KiB
		
	
	
	
		
			Swift
		
	
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
 | 
						|
 | 
						|
import UIKit
 | 
						|
import SessionUtilitiesKit
 | 
						|
 | 
						|
public class TopBannerController: UIViewController {
 | 
						|
    public enum Warning: String, Codable {
 | 
						|
        case outdatedUserConfig
 | 
						|
        
 | 
						|
        var text: String {
 | 
						|
            switch self {
 | 
						|
                case .outdatedUserConfig: return "USER_CONFIG_OUTDATED_WARNING".localized()
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private static var lastInstance: TopBannerController?
 | 
						|
    private let child: UIViewController
 | 
						|
    private var initialCachedWarning: Warning?
 | 
						|
    
 | 
						|
    // MARK: - UI
 | 
						|
    
 | 
						|
    private lazy var bottomConstraint: NSLayoutConstraint = bannerLabel
 | 
						|
        .pin(.bottom, to: .bottom, of: bannerContainer, withInset: -Values.verySmallSpacing)
 | 
						|
    
 | 
						|
    private let contentStackView: UIStackView = {
 | 
						|
        let result: UIStackView = UIStackView()
 | 
						|
        result.translatesAutoresizingMaskIntoConstraints = false
 | 
						|
        result.axis = .vertical
 | 
						|
        result.distribution = .fill
 | 
						|
        result.alignment = .fill
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
    
 | 
						|
    private let bannerContainer: UIView = {
 | 
						|
        let result: UIView = UIView()
 | 
						|
        result.translatesAutoresizingMaskIntoConstraints = false
 | 
						|
        result.themeBackgroundColor = .primary
 | 
						|
        result.isHidden = true
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
    
 | 
						|
    private let bannerLabel: UILabel = {
 | 
						|
        let result: UILabel = UILabel()
 | 
						|
        result.translatesAutoresizingMaskIntoConstraints = false
 | 
						|
        result.setContentHuggingPriority(.required, for: .vertical)
 | 
						|
        result.font = .systemFont(ofSize: Values.verySmallFontSize)
 | 
						|
        result.textAlignment = .center
 | 
						|
        result.themeTextColor = .black
 | 
						|
        result.numberOfLines = 0
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
    
 | 
						|
    private lazy var closeButton: UIButton = {
 | 
						|
        let result: UIButton = UIButton()
 | 
						|
        result.translatesAutoresizingMaskIntoConstraints = false
 | 
						|
        result.setImage(
 | 
						|
            UIImage(systemName: "xmark", withConfiguration: UIImage.SymbolConfiguration(pointSize: 12, weight: .bold))?
 | 
						|
                .withRenderingMode(.alwaysTemplate),
 | 
						|
            for: .normal
 | 
						|
        )
 | 
						|
        result.contentMode = .center
 | 
						|
        result.themeTintColor = .black
 | 
						|
        result.addTarget(self, action: #selector(dismissBanner), for: .touchUpInside)
 | 
						|
        
 | 
						|
        return result
 | 
						|
    }()
 | 
						|
    
 | 
						|
    // MARK: - Initialization
 | 
						|
    
 | 
						|
    public init(
 | 
						|
        child: UIViewController,
 | 
						|
        cachedWarning: Warning? = nil
 | 
						|
    ) {
 | 
						|
        self.child = child
 | 
						|
        self.initialCachedWarning = cachedWarning
 | 
						|
        
 | 
						|
        super.init(nibName: nil, bundle: nil)
 | 
						|
        
 | 
						|
        TopBannerController.lastInstance = self
 | 
						|
    }
 | 
						|
    
 | 
						|
    required init?(coder: NSCoder) {
 | 
						|
        fatalError("init(coder:) has not been implemented")
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: - Lifecycle
 | 
						|
    
 | 
						|
    public override func loadView() {
 | 
						|
        super.loadView()
 | 
						|
        
 | 
						|
        view.addSubview(contentStackView)
 | 
						|
        
 | 
						|
        contentStackView.addArrangedSubview(bannerContainer)
 | 
						|
        attachChild()
 | 
						|
        
 | 
						|
        bannerContainer.addSubview(bannerLabel)
 | 
						|
        bannerContainer.addSubview(closeButton)
 | 
						|
        
 | 
						|
        setupLayout()
 | 
						|
        
 | 
						|
        // If we had an initial warning then show it
 | 
						|
        if let warning: Warning = self.initialCachedWarning {
 | 
						|
            UIView.performWithoutAnimation {
 | 
						|
                TopBannerController.show(warning: warning)
 | 
						|
            }
 | 
						|
            
 | 
						|
            self.initialCachedWarning = nil
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    private func setupLayout() {
 | 
						|
        contentStackView.pin(.top, to: .top, of: view.safeAreaLayoutGuide)
 | 
						|
        contentStackView.pin(.leading, to: .leading, of: view)
 | 
						|
        contentStackView.pin(.trailing, to: .trailing, of: view)
 | 
						|
        contentStackView.pin(.bottom, to: .bottom, of: view)
 | 
						|
        
 | 
						|
        bannerLabel.pin(.top, to: .top, of: view.safeAreaLayoutGuide, withInset: Values.verySmallSpacing)
 | 
						|
        bannerLabel.pin(.leading, to: .leading, of: bannerContainer, withInset: Values.veryLargeSpacing)
 | 
						|
        bannerLabel.pin(.trailing, to: .trailing, of: bannerContainer, withInset: -Values.veryLargeSpacing)
 | 
						|
        bottomConstraint.isActive = false
 | 
						|
        
 | 
						|
        let buttonSize: CGFloat = (12 + (Values.smallSpacing * 2))
 | 
						|
        closeButton.center(.vertical, in: bannerLabel)
 | 
						|
        closeButton.pin(.trailing, to: .trailing, of: bannerContainer, withInset: -Values.smallSpacing)
 | 
						|
        closeButton.set(.width, to: buttonSize)
 | 
						|
        closeButton.set(.height, to: buttonSize)
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: - Actions
 | 
						|
    
 | 
						|
    @objc private func dismissBanner() {
 | 
						|
        // Remove the cached warning
 | 
						|
        UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = nil
 | 
						|
        
 | 
						|
        UIView.animate(
 | 
						|
            withDuration: 0.3,
 | 
						|
            animations: { [weak self] in
 | 
						|
                self?.bottomConstraint.isActive = false
 | 
						|
                self?.contentStackView.setNeedsLayout()
 | 
						|
                self?.contentStackView.layoutIfNeeded()
 | 
						|
            },
 | 
						|
            completion: { [weak self] _ in
 | 
						|
                self?.bannerContainer.isHidden = true
 | 
						|
            }
 | 
						|
        )
 | 
						|
    }
 | 
						|
    
 | 
						|
    // MARK: - Functions
 | 
						|
    
 | 
						|
    public func attachChild() {
 | 
						|
        child.willMove(toParent: self)
 | 
						|
        addChild(child)
 | 
						|
        contentStackView.addArrangedSubview(child.view)
 | 
						|
        child.didMove(toParent: self)
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static func show(warning: Warning, inWindowFor view: UIView? = nil) {
 | 
						|
        guard Thread.isMainThread else {
 | 
						|
            DispatchQueue.main.async {
 | 
						|
                TopBannerController.show(warning: warning, inWindowFor: view)
 | 
						|
            }
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Not an ideal approach but should allow us to have a single banner
 | 
						|
        guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Cache the banner to show (so we can show it on re-launch)
 | 
						|
        UserDefaults.sharedLokiProject?[.topBannerWarningToShow] = warning.rawValue
 | 
						|
        
 | 
						|
        UIView.performWithoutAnimation {
 | 
						|
            instance.bannerLabel.text = warning.text
 | 
						|
            instance.bannerLabel.setNeedsLayout()
 | 
						|
            instance.bannerLabel.layoutIfNeeded()
 | 
						|
            instance.bottomConstraint.isActive = false
 | 
						|
            instance.bannerContainer.isHidden = false
 | 
						|
        }
 | 
						|
        
 | 
						|
        UIView.animate(withDuration: 0.3) { [weak instance] in
 | 
						|
            instance?.bottomConstraint.isActive = true
 | 
						|
            instance?.contentStackView.setNeedsLayout()
 | 
						|
            instance?.contentStackView.layoutIfNeeded()
 | 
						|
        }
 | 
						|
    }
 | 
						|
    
 | 
						|
    public static func hide(inWindowFor view: UIView? = nil) {
 | 
						|
        guard Thread.isMainThread else {
 | 
						|
            DispatchQueue.main.async {
 | 
						|
                TopBannerController.hide(inWindowFor: view)
 | 
						|
            }
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        // Not an ideal approach but should allow us to have a single banner
 | 
						|
        guard let instance: TopBannerController = ((view?.window?.rootViewController as? TopBannerController) ?? TopBannerController.lastInstance) else {
 | 
						|
            return
 | 
						|
        }
 | 
						|
        
 | 
						|
        UIView.performWithoutAnimation { instance.dismissBanner() }
 | 
						|
    }
 | 
						|
}
 |