// 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 labelStrings: [NSAttributedString] = [] private var labelTypes: [LabelType] = [.empty] 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 } } // MARK: - Types public enum LabelType { case notificationSettings case userCount case disappearingMessageSetting case empty } public var currentLabelType: LabelType { return self.labelTypes[pageControl.currentPage] } // MARK: - UI Components private 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(labelStrings: [NSAttributedString] = [], labelTypes: [LabelType] = [.empty], labelSize: CGSize = .zero, shouldAutoScroll: Bool = false) { super.init(frame: .zero) setUpViewHierarchy() self.update(with: labelStrings, labelTypes: labelTypes, labelSize: labelSize, shouldAutoScroll: shouldAutoScroll) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Content public func update(with labelStrings: [NSAttributedString], labelTypes: [LabelType], labelSize: CGSize = .zero, shouldAutoScroll: Bool = false) { self.labelStrings = labelStrings self.labelTypes = labelTypes.isEmpty ? [.empty] : labelTypes self.labelSize = labelSize self.shouldAutoScroll = shouldAutoScroll self.shouldScroll = labelStrings.count > 1 if self.shouldScroll { let first: NSAttributedString = labelStrings.first! let last: NSAttributedString = labelStrings.last! self.labelStrings.append(first) self.labelStrings.insert(last, at: 0) } pageControl.numberOfPages = labelStrings.count pageControl.currentPage = 0 let contentSize = CGSize(width: labelSize.width * CGFloat(self.labelStrings.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.labelStrings.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.verySmallFontSize) label.themeTextColor = .textPrimary label.lineBreakMode = .byTruncatingTail label.attributedText = $0 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: -2) addSubview(arrowRight) arrowRight.pin(.right, to: .right, of: self) arrowRight.center(.vertical, in: self, withInset: -2) addSubview(pageControl) pageControl.center(.horizontal, in: self) pageControl.pin(.bottom, to: .bottom, of: self, withInset: 1) scrollView.addSubview(stackView) } private func startScrolling() { timer?.invalidate() timer = Timer.scheduledTimerOnMainThread(withTimeInterval: Self.autoScrollingTimeInterval, repeats: true) { _ in guard self.labelStrings.count != 0 else { return } let targetPage = (self.pageControl.currentPage + 1) % self.labelStrings.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.labelStrings.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.labelStrings.count - 2 scrollView.setContentOffset( CGPoint( x: Int(self.labelSize.width) * realLastIndex, y: 0 ), animated: false ) } } }