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.
session-ios/Session/Conversations/Views & Modals/SessionLabelCarouselView.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
)
}
}
}