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.
		
		
		
		
		
			
		
			
				
	
	
		
			240 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Swift
		
	
			
		
		
	
	
			240 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Swift
		
	
| // Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
 | |
| 
 | |
| import UIKit
 | |
| import SessionUIKit
 | |
| 
 | |
| final class SessionLabelCarouselView: UIView, UIScrollViewDelegate {
 | |
|     private static let autoScrollingTimeInterval: TimeInterval = 10
 | |
|     private var labelInfos: [LabelInfo] = []
 | |
|     private var labelSize: CGSize = .zero
 | |
|     private var shouldAutoScroll: Bool = false
 | |
|     private var timer: Timer?
 | |
|     
 | |
|     private lazy var contentWidth = stackView.set(.width, to: 0)
 | |
|     private lazy var contentHeight = stackView.set(.height, to: 0)
 | |
|     
 | |
|     private var shouldScroll: Bool = false {
 | |
|         didSet {
 | |
|             arrowLeft.isHidden = !shouldScroll
 | |
|             arrowRight.isHidden = !shouldScroll
 | |
|             pageControl.isHidden = !shouldScroll
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     public struct LabelInfo {
 | |
|         let attributedText: NSAttributedString
 | |
|         let accessibility: Accessibility?
 | |
|         let type: LabelType
 | |
|     }
 | |
|     
 | |
|     // MARK: - Types
 | |
|     
 | |
|     public enum LabelType {
 | |
|         case notificationSettings
 | |
|         case userCount
 | |
|         case disappearingMessageSetting
 | |
|     }
 | |
|     
 | |
|     public var currentLabelType: LabelType? {
 | |
|         let index = pageControl.currentPage + (shouldScroll ? 1 : 0)
 | |
|         return self.labelInfos[safe: index]?.type
 | |
|     }
 | |
|     
 | |
|     // MARK: - UI Components
 | |
|     
 | |
|     public lazy var scrollView: UIScrollView = {
 | |
|         let result = UIScrollView(frame: .zero)
 | |
|         result.isPagingEnabled = true
 | |
|         result.showsVerticalScrollIndicator = false
 | |
|         result.showsHorizontalScrollIndicator = false
 | |
|         result.delegate = self
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var stackView: UIStackView = {
 | |
|         let result = UIStackView()
 | |
|         result.axis = .horizontal
 | |
|         
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var pageControl: UIPageControl = {
 | |
|         let result = UIPageControl(frame: .zero)
 | |
|         result.themeCurrentPageIndicatorTintColor = .textPrimary
 | |
|         result.themePageIndicatorTintColor = .textSecondary
 | |
|         result.themeTintColor = .textPrimary
 | |
|         result.currentPage = 0
 | |
|         result.set(.height, to: 5)
 | |
|         result.transform = CGAffineTransform(scaleX: 0.5, y: 0.5)
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var arrowLeft: UIImageView = {
 | |
|         let result = UIImageView(image: UIImage(systemName: "chevron.left")?.withRenderingMode(.alwaysTemplate))
 | |
|         result.themeTintColor = .textPrimary
 | |
|         result.set(.height, to: 10)
 | |
|         result.set(.width, to: 5)
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     private lazy var arrowRight: UIImageView = {
 | |
|         let result = UIImageView(image: UIImage(systemName: "chevron.right")?.withRenderingMode(.alwaysTemplate))
 | |
|         result.themeTintColor = .textPrimary
 | |
|         result.set(.height, to: 10)
 | |
|         result.set(.width, to: 5)
 | |
|         return result
 | |
|     }()
 | |
|     
 | |
|     // MARK: - Initialization
 | |
|     
 | |
|     init(labelInfos: [LabelInfo] = [], labelSize: CGSize = .zero, shouldAutoScroll: Bool = false) {
 | |
|         super.init(frame: .zero)
 | |
|         setUpViewHierarchy()
 | |
|         self.update(with: labelInfos, labelSize: labelSize, shouldAutoScroll: shouldAutoScroll)
 | |
|     }
 | |
|     
 | |
|     required init?(coder: NSCoder) {
 | |
|         fatalError("init(coder:) has not been implemented")
 | |
|     }
 | |
|     
 | |
|     // MARK: - Content
 | |
|     
 | |
|     public func update(with labelInfos: [LabelInfo], labelSize: CGSize = .zero, shouldAutoScroll: Bool = false) {
 | |
|         self.labelInfos = labelInfos
 | |
|         self.labelSize = labelSize
 | |
|         self.shouldAutoScroll = shouldAutoScroll
 | |
|         self.shouldScroll = labelInfos.count > 1
 | |
|         
 | |
|         if self.shouldScroll {
 | |
|             let first: LabelInfo = labelInfos.first!
 | |
|             let last: LabelInfo = labelInfos.last!
 | |
|             self.labelInfos.append(first)
 | |
|             self.labelInfos.insert(last, at: 0)
 | |
|         }
 | |
|         
 | |
|         pageControl.numberOfPages = labelInfos.count
 | |
|         pageControl.currentPage = 0
 | |
|         
 | |
|         let contentSize = CGSize(width: labelSize.width * CGFloat(self.labelInfos.count), height: labelSize.height)
 | |
|         scrollView.contentSize = contentSize
 | |
|         contentWidth.constant = contentSize.width
 | |
|         contentHeight.constant = contentSize.height
 | |
|         self.scrollView.setContentOffset(
 | |
|             CGPoint(
 | |
|                 x: Int(self.labelSize.width) * (self.shouldScroll ? 1 : 0),
 | |
|                 y: 0
 | |
|             ),
 | |
|             animated: false
 | |
|         )
 | |
|         
 | |
|         stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
 | |
|         
 | |
|         self.labelInfos.forEach {
 | |
|             let wrapper: UIView = UIView()
 | |
|             wrapper.set(.width, to: labelSize.width)
 | |
|             wrapper.set(.height, to: labelSize.height)
 | |
|             let label: UILabel = UILabel()
 | |
|             label.font = .systemFont(ofSize: Values.miniFontSize)
 | |
|             label.themeTextColor = .textPrimary
 | |
|             label.lineBreakMode = .byTruncatingTail
 | |
|             label.attributedText = $0.attributedText
 | |
|             label.accessibilityIdentifier = $0.accessibility?.identifier
 | |
|             label.accessibilityLabel = $0.accessibility?.label
 | |
|             label.isAccessibilityElement = true
 | |
|             wrapper.addSubview(label)
 | |
|             label.center(in: wrapper)
 | |
|             stackView.addArrangedSubview(wrapper)
 | |
|         }
 | |
|         
 | |
|         if self.shouldAutoScroll {
 | |
|             startScrolling()
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private func setUpViewHierarchy() {
 | |
|         addSubview(scrollView)
 | |
|         scrollView.pin(to: self)
 | |
|         
 | |
|         addSubview(arrowLeft)
 | |
|         arrowLeft.pin(.left, to: .left, of: self)
 | |
|         arrowLeft.center(.vertical, in: self, withInset: -4)
 | |
|         
 | |
|         addSubview(arrowRight)
 | |
|         arrowRight.pin(.right, to: .right, of: self)
 | |
|         arrowRight.center(.vertical, in: self, withInset: -4)
 | |
|         
 | |
|         addSubview(pageControl)
 | |
|         pageControl.center(.horizontal, in: self)
 | |
|         pageControl.pin(.bottom, to: .bottom, of: self)
 | |
|         
 | |
|         scrollView.addSubview(stackView)
 | |
|     }
 | |
|     
 | |
|     // MARK: - Interaction
 | |
|     
 | |
|     private func startScrolling() {
 | |
|         timer?.invalidate()
 | |
|         timer = Timer.scheduledTimerOnMainThread(withTimeInterval: Self.autoScrollingTimeInterval, repeats: true) { _ in
 | |
|             guard self.labelInfos.count != 0 else { return }
 | |
|             let targetPage = (self.pageControl.currentPage + 1) % self.labelInfos.count
 | |
|             self.scrollView.scrollRectToVisible(
 | |
|                 CGRect(
 | |
|                     origin: CGPoint(
 | |
|                         x: Int(self.labelSize.width) * targetPage,
 | |
|                         y: 0
 | |
|                     ),
 | |
|                     size: self.labelSize
 | |
|                 ),
 | |
|                 animated: true
 | |
|             )
 | |
|         }
 | |
|     }
 | |
|     
 | |
|     private func stopScrolling() {
 | |
|         timer?.invalidate()
 | |
|         timer = nil
 | |
|     }
 | |
|     
 | |
|     func scrollViewDidScroll(_ scrollView: UIScrollView) {
 | |
|         let pageIndex: Int = {
 | |
|             let maybeCurrentPageIndex: Int = Int(round(scrollView.contentOffset.x/labelSize.width))
 | |
|             if self.shouldScroll {
 | |
|                 if maybeCurrentPageIndex == 0 {
 | |
|                     return pageControl.numberOfPages - 1
 | |
|                 }
 | |
|                 if maybeCurrentPageIndex == self.labelInfos.count - 1 {
 | |
|                     return 0
 | |
|                 }
 | |
|                 return maybeCurrentPageIndex - 1
 | |
|             }
 | |
|             return maybeCurrentPageIndex
 | |
|         }()
 | |
|         
 | |
|         pageControl.currentPage = pageIndex
 | |
|     }
 | |
|     
 | |
|     func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
 | |
|         if pageControl.currentPage == 0 {
 | |
|             scrollView.setContentOffset(
 | |
|                 CGPoint(
 | |
|                     x: Int(self.labelSize.width) * 1,
 | |
|                     y: 0
 | |
|                 ),
 | |
|                 animated: false
 | |
|             )
 | |
|         }
 | |
|         
 | |
|         if pageControl.currentPage == pageControl.numberOfPages - 1 {
 | |
|             let realLastIndex: Int = self.labelInfos.count - 2
 | |
|             scrollView.setContentOffset(
 | |
|                 CGPoint(
 | |
|                     x: Int(self.labelSize.width) * realLastIndex,
 | |
|                     y: 0
 | |
|                 ),
 | |
|                 animated: false
 | |
|             )
 | |
|         }
 | |
|     }
 | |
| }
 |