feat: make the carousel looping infinitely

pull/941/head
Ryan Zhao 2 years ago
parent b11ac1f6f8
commit 8164d4400d

@ -113,7 +113,7 @@
7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; }; 7B4C75CB26B37E0F0000AC89 /* UnsendRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */; };
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; }; 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */; };
7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 7B50D64C28AC7CF80086CCEC /* silence.aiff */; }; 7B50D64D28AC7CF80086CCEC /* silence.aiff in Resources */ = {isa = PBXBuildFile; fileRef = 7B50D64C28AC7CF80086CCEC /* silence.aiff */; };
7B5233C42900E90F00F8F375 /* PagedScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C32900E90F00F8F375 /* PagedScrollView.swift */; }; 7B5233C42900E90F00F8F375 /* SessionLabelCarouselView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */; };
7B5233C6290636D700F8F375 /* _011_DisappearingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C5290636D700F8F375 /* _011_DisappearingMessage.swift */; }; 7B5233C6290636D700F8F375 /* _011_DisappearingMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B5233C5290636D700F8F375 /* _011_DisappearingMessage.swift */; };
7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; }; 7B7037432834B81F000DCF35 /* ReactionContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */; };
7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; }; 7B7037452834BCC0000DCF35 /* ReactionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7B7037442834BCC0000DCF35 /* ReactionView.swift */; };
@ -1185,7 +1185,7 @@
7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; }; 7B4C75CA26B37E0F0000AC89 /* UnsendRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsendRequest.swift; sourceTree = "<group>"; };
7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; }; 7B4C75CC26BB92060000AC89 /* DeletedMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeletedMessageView.swift; sourceTree = "<group>"; };
7B50D64C28AC7CF80086CCEC /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = "<group>"; }; 7B50D64C28AC7CF80086CCEC /* silence.aiff */ = {isa = PBXFileReference; lastKnownFileType = audio.aiff; path = silence.aiff; sourceTree = "<group>"; };
7B5233C32900E90F00F8F375 /* PagedScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagedScrollView.swift; sourceTree = "<group>"; }; 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionLabelCarouselView.swift; sourceTree = "<group>"; };
7B5233C5290636D700F8F375 /* _011_DisappearingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_DisappearingMessage.swift; sourceTree = "<group>"; }; 7B5233C5290636D700F8F375 /* _011_DisappearingMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _011_DisappearingMessage.swift; sourceTree = "<group>"; };
7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = "<group>"; }; 7B7037422834B81F000DCF35 /* ReactionContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionContainerView.swift; sourceTree = "<group>"; };
7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; }; 7B7037442834BCC0000DCF35 /* ReactionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReactionView.swift; sourceTree = "<group>"; };
@ -2386,7 +2386,7 @@
C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */, C374EEEA25DA3CA70073A857 /* ConversationTitleView.swift */,
FD4B200D283492210034334B /* InsetLockableTableView.swift */, FD4B200D283492210034334B /* InsetLockableTableView.swift */,
7B9F71C828470667006DFE7B /* ReactionListSheet.swift */, 7B9F71C828470667006DFE7B /* ReactionListSheet.swift */,
7B5233C32900E90F00F8F375 /* PagedScrollView.swift */, 7B5233C32900E90F00F8F375 /* SessionLabelCarouselView.swift */,
); );
path = "Views & Modals"; path = "Views & Modals";
sourceTree = "<group>"; sourceTree = "<group>";
@ -5763,7 +5763,7 @@
B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */, B835249B25C3AB650089A44F /* VisibleMessageCell.swift in Sources */,
B8D0A25025E3678700C1835E /* LinkDeviceVC.swift in Sources */, B8D0A25025E3678700C1835E /* LinkDeviceVC.swift in Sources */,
B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */, B894D0752339EDCF00B4D94D /* NukeDataModal.swift in Sources */,
7B5233C42900E90F00F8F375 /* PagedScrollView.swift in Sources */, 7B5233C42900E90F00F8F375 /* SessionLabelCarouselView.swift in Sources */,
7B93D07727CF1A8A00811CB6 /* MockDataGenerator.swift in Sources */, 7B93D07727CF1A8A00811CB6 /* MockDataGenerator.swift in Sources */,
7B1B52D828580C6D006069F2 /* EmojiPickerSheet.swift in Sources */, 7B1B52D828580C6D006069F2 /* EmojiPickerSheet.swift in Sources */,
7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */, 7B4C75CD26BB92060000AC89 /* DeletedMessageView.swift in Sources */,

@ -15,7 +15,7 @@ final class ConversationTitleView: UIView {
return UIView.layoutFittingExpandedSize return UIView.layoutFittingExpandedSize
} }
private lazy var pagedScrollViewWidth = pagedScrollView.set(.width, to: 200) private lazy var labelCarouselViewWidth = labelCarouselView.set(.width, to: 200)
// MARK: - UI Components // MARK: - UI Components
@ -31,49 +31,13 @@ final class ConversationTitleView: UIView {
return result return result
}() }()
private lazy var pagedScrollView: PagedScrollView = { private lazy var labelCarouselView: SessionLabelCarouselView = {
let result = PagedScrollView() let result = SessionLabelCarouselView()
return result
}()
private lazy var subtitleLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.themeTextColor = .textPrimary
result.lineBreakMode = .byTruncatingTail
return result
}()
private lazy var userCountLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.themeTextColor = .textPrimary
result.lineBreakMode = .byTruncatingTail
return result
}()
private lazy var notificationSettingsLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.themeTextColor = .textPrimary
result.lineBreakMode = .byTruncatingTail
return result
}()
private lazy var disappearingMessageSettingLabel: UILabel = {
let result: UILabel = UILabel()
result.font = .systemFont(ofSize: Values.verySmallFontSize)
result.themeTextColor = .textPrimary
result.lineBreakMode = .byTruncatingTail
return result return result
}() }()
private lazy var stackView: UIStackView = { private lazy var stackView: UIStackView = {
let result = UIStackView(arrangedSubviews: [ titleLabel, pagedScrollView ]) let result = UIStackView(arrangedSubviews: [ titleLabel, labelCarouselView ])
result.axis = .vertical result.axis = .vertical
result.alignment = .center result.alignment = .center
@ -169,13 +133,13 @@ final class ConversationTitleView: UIView {
) )
) )
ThemeManager.onThemeChange(observer: self.subtitleLabel) { [weak self] theme, _ in ThemeManager.onThemeChange(observer: self.labelCarouselView) { [weak self] theme, _ in
guard let textPrimary: UIColor = theme.color(for: .textPrimary) else { return } guard let textPrimary: UIColor = theme.color(for: .textPrimary) else { return }
var slides: [UIView?] = [] var labelStrings: [NSAttributedString] = []
if Date().timeIntervalSince1970 <= (mutedUntilTimestamp ?? 0) { if Date().timeIntervalSince1970 <= (mutedUntilTimestamp ?? 0) {
self?.notificationSettingsLabel.attributedText = NSAttributedString( let notificationSettingsLabelString = NSAttributedString(
string: "\u{e067} ", string: "\u{e067} ",
attributes: [ attributes: [
.font: UIFont.ows_elegantIconsFont(10), .font: UIFont.ows_elegantIconsFont(10),
@ -183,8 +147,8 @@ final class ConversationTitleView: UIView {
] ]
) )
.appending(string: "Muted") .appending(string: "Muted")
self?.notificationSettingsLabel.isHidden = false
slides.append(self?.notificationSettingsLabel) labelStrings.append(notificationSettingsLabelString)
} else if onlyNotifyForMentions{ } else if onlyNotifyForMentions{
let imageAttachment = NSTextAttachment() let imageAttachment = NSTextAttachment()
imageAttachment.image = UIImage(named: "NotifyMentions.png")?.withTint(textPrimary) imageAttachment.image = UIImage(named: "NotifyMentions.png")?.withTint(textPrimary)
@ -195,28 +159,31 @@ final class ConversationTitleView: UIView {
height: Values.verySmallFontSize height: Values.verySmallFontSize
) )
self?.notificationSettingsLabel.attributedText = NSAttributedString(attachment: imageAttachment) let notificationSettingsLabelString = NSAttributedString(attachment: imageAttachment)
.appending(string: " ") .appending(string: " ")
.appending(string: "view_conversation_title_notify_for_mentions_only".localized()) .appending(string: "view_conversation_title_notify_for_mentions_only".localized())
self?.notificationSettingsLabel.isHidden = false
slides.append(self?.notificationSettingsLabel) labelStrings.append(notificationSettingsLabelString)
} }
if let userCount: Int = userCount { if let userCount: Int = userCount {
switch threadVariant { let userCountLabelString: NSAttributedString = {
case .contact: break switch threadVariant {
case .contact: return NSAttributedString(string: "") // Should not happen
case .closedGroup:
self?.userCountLabel.attributedText = NSAttributedString( case .closedGroup:
string: "\(userCount) member\(userCount == 1 ? "" : "s")" return NSAttributedString(
) string: "\(userCount) member\(userCount == 1 ? "" : "s")"
)
case .openGroup:
self?.userCountLabel.attributedText = NSAttributedString( case .openGroup:
string: "\(userCount) active member\(userCount == 1 ? "" : "s")" return NSAttributedString(
) string: "\(userCount) active member\(userCount == 1 ? "" : "s")"
} )
slides.append(self?.userCountLabel) }
}()
labelStrings.append(userCountLabelString)
} }
if let config = disappearingMessagesConfig, config.isEnabled == true { if let config = disappearingMessagesConfig, config.isEnabled == true {
@ -229,25 +196,25 @@ final class ConversationTitleView: UIView {
height: Values.verySmallFontSize height: Values.verySmallFontSize
) )
self?.disappearingMessageSettingLabel.attributedText = NSAttributedString(attachment: imageAttachment) let disappearingMessageSettingLabelString = NSAttributedString(attachment: imageAttachment)
.appending(string: " ") .appending(string: " ")
.appending(string: config.type == .disappearAfterRead ? "DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE".localized() : "DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE".localized()) .appending(string: config.type == .disappearAfterRead ? "DISAPPERING_MESSAGES_TYPE_AFTER_READ_TITLE".localized() : "DISAPPERING_MESSAGES_TYPE_AFTER_SEND_TITLE".localized())
.appending(string: " - ") .appending(string: " - ")
.appending(string: floor(config.durationSeconds).formatted(format: .short)) .appending(string: floor(config.durationSeconds).formatted(format: .short))
self?.disappearingMessageSettingLabel.isHidden = false
slides.append(self?.disappearingMessageSettingLabel) labelStrings.append(disappearingMessageSettingLabelString)
} }
self?.pagedScrollView.update( self?.labelCarouselView.update(
with: slides.compactMap{ $0 }, with: labelStrings,
slideSize: CGSize( labelSize: CGSize(
width: self?.pagedScrollViewWidth.constant ?? 0, width: self?.labelCarouselViewWidth.constant ?? 0,
height: 20 height: 20
), ),
shouldAutoScroll: false shouldAutoScroll: false
) )
self?.pagedScrollView.isHidden = (slides.count == 0) self?.labelCarouselView.isHidden = (labelStrings.count == 0)
} }
// Contact threads also have the call button to compensate for // Contact threads also have the call button to compensate for

@ -3,20 +3,21 @@
import UIKit import UIKit
import SessionUIKit import SessionUIKit
final class PagedScrollView: UIView, UIScrollViewDelegate { final class SessionLabelCarouselView: UIView, UIScrollViewDelegate {
private static let autoScrollingTimeInterval: TimeInterval = 10 private static let autoScrollingTimeInterval: TimeInterval = 10
private var slides: [UIView] = [] private var labelStrings: [NSAttributedString] = []
private var slideSize: CGSize = .zero private var labelSize: CGSize = .zero
private var shouldAutoScroll: Bool = false private var shouldAutoScroll: Bool = false
private var timer: Timer? private var timer: Timer?
private lazy var contentWidth = stackView.set(.width, to: 0) private lazy var contentWidth = stackView.set(.width, to: 0)
private lazy var contentHeight = stackView.set(.height, to: 0) private lazy var contentHeight = stackView.set(.height, to: 0)
private var shouldArrowsShow: Bool = false { private var shouldScroll: Bool = false {
didSet { didSet {
arrowLeft.isHidden = !shouldArrowsShow arrowLeft.isHidden = !shouldScroll
arrowRight.isHidden = !shouldArrowsShow arrowRight.isHidden = !shouldScroll
pageControl.isHidden = !shouldScroll
} }
} }
@ -67,10 +68,10 @@ final class PagedScrollView: UIView, UIScrollViewDelegate {
// MARK: - Initialization // MARK: - Initialization
init(slides: [UIView] = [], slideSize: CGSize = .zero, shouldAutoScroll: Bool = false) { init(labelStrings: [NSAttributedString] = [], labelSize: CGSize = .zero, shouldAutoScroll: Bool = false) {
super.init(frame: .zero) super.init(frame: .zero)
setUpViewHierarchy() setUpViewHierarchy()
self.update(with: slides, slideSize: slideSize, shouldAutoScroll: shouldAutoScroll) self.update(with: labelStrings, labelSize: labelSize, shouldAutoScroll: shouldAutoScroll)
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -79,29 +80,47 @@ final class PagedScrollView: UIView, UIScrollViewDelegate {
// MARK: - Content // MARK: - Content
public func update(with slides: [UIView] = [], slideSize: CGSize = .zero, shouldAutoScroll: Bool = false) { public func update(with labelStrings: [NSAttributedString] = [], labelSize: CGSize = .zero, shouldAutoScroll: Bool = false) {
self.slides = slides self.labelStrings = labelStrings
self.slideSize = slideSize self.labelSize = labelSize
self.shouldAutoScroll = shouldAutoScroll self.shouldAutoScroll = shouldAutoScroll
self.shouldArrowsShow = slides.count > 1 self.shouldScroll = labelStrings.count > 1
pageControl.numberOfPages = slides.count 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 pageControl.currentPage = 0
pageControl.isHidden = (slides.count == 1)
let contentSize = CGSize(width: slideSize.width * CGFloat(slides.count), height: slideSize.height) let contentSize = CGSize(width: labelSize.width * CGFloat(self.labelStrings.count), height: labelSize.height)
scrollView.contentSize = contentSize scrollView.contentSize = contentSize
contentWidth.constant = contentSize.width contentWidth.constant = contentSize.width
contentHeight.constant = contentSize.height 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() } stackView.arrangedSubviews.forEach { $0.removeFromSuperview() }
self.slides.forEach { self.labelStrings.forEach {
let wrapper: UIView = UIView() let wrapper: UIView = UIView()
wrapper.set(.width, to: slideSize.width) wrapper.set(.width, to: labelSize.width)
wrapper.set(.height, to: slideSize.height) wrapper.set(.height, to: labelSize.height)
wrapper.addSubview($0) let label: UILabel = UILabel()
$0.center(in: wrapper) 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) stackView.addArrangedSubview(wrapper)
} }
@ -132,15 +151,15 @@ final class PagedScrollView: UIView, UIScrollViewDelegate {
private func startScrolling() { private func startScrolling() {
timer?.invalidate() timer?.invalidate()
timer = Timer.scheduledTimerOnMainThread(withTimeInterval: Self.autoScrollingTimeInterval, repeats: true) { _ in timer = Timer.scheduledTimerOnMainThread(withTimeInterval: Self.autoScrollingTimeInterval, repeats: true) { _ in
guard self.slides.count != 0 else { return } guard self.labelStrings.count != 0 else { return }
let targetPage = (self.pageControl.currentPage + 1) % self.slides.count let targetPage = (self.pageControl.currentPage + 1) % self.labelStrings.count
self.scrollView.scrollRectToVisible( self.scrollView.scrollRectToVisible(
CGRect( CGRect(
origin: CGPoint( origin: CGPoint(
x: Int(self.slideSize.width) * targetPage, x: Int(self.labelSize.width) * targetPage,
y: 0 y: 0
), ),
size: self.slideSize size: self.labelSize
), ),
animated: true animated: true
) )
@ -153,7 +172,43 @@ final class PagedScrollView: UIView, UIScrollViewDelegate {
} }
func scrollViewDidScroll(_ scrollView: UIScrollView) { func scrollViewDidScroll(_ scrollView: UIScrollView) {
let pageIndex = round(scrollView.contentOffset.x/slideSize.width) let pageIndex: Int = {
pageControl.currentPage = Int(pageIndex) 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
)
}
} }
} }
Loading…
Cancel
Save