Path: blob/main/Signal/ConversationView/ConversationScrollButton.swift
1 views
//
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//
import SignalServiceKit
import SignalUI
public import UIKit
public class ConversationScrollButton: UIButton {
let iconName: String
var unreadCount: UInt = 0 {
didSet {
unreadCountLabel.text = String.localizedStringWithFormat("%u", unreadCount)
unreadBadge.isHidden = unreadCount == 0
}
}
var badgeTintColor: UIColor! {
get { unreadBadge.backgroundColor ?? UIColor.Signal.accent }
set { unreadBadge.backgroundColor = newValue ?? UIColor.Signal.accent }
}
init(iconName: String) {
self.iconName = iconName
super.init(frame: .zero)
var configuration: UIButton.Configuration?
if #available(iOS 26, *) {
configuration = .glass()
}
if configuration == nil {
configuration = .gray()
configuration?.baseForegroundColor = UIColor(
light: .ows_gray75,
dark: .ows_gray15,
)
configuration?.baseBackgroundColor = UIColor(
light: .ows_gray02,
dark: .ows_gray65,
)
if #available(iOS 18, *) {
configuration?.background.shadowProperties.offset = CGSize(width: 0, height: 4)
configuration?.background.shadowProperties.color = .black
configuration?.background.shadowProperties.radius = 12
configuration?.background.shadowProperties.opacity = 0.3
}
}
configuration?.cornerStyle = .capsule
configuration?.image = UIImage(named: iconName)
self.configuration = configuration
addUnreadLabel()
}
override public func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
if previousTraitCollection?.userInterfaceStyle != traitCollection.userInterfaceStyle {
applyUnreadBadgeStroke()
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public var intrinsicContentSize: CGSize {
.square(ConversationScrollButton.circleDiameter)
}
private class var circleDiameter: CGFloat { 40 }
private lazy var unreadCountLabel: UILabel = {
let label = UILabel()
label.font = .dynamicTypeFootnoteClamped.semibold()
label.textColor = .white
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
private lazy var unreadBadge: UIView = {
let view = PillView()
view.isUserInteractionEnabled = false
view.clipsToBounds = true
view.backgroundColor = .Signal.accent
view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(unreadCountLabel)
NSLayoutConstraint.activate([
unreadCountLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 6),
unreadCountLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
unreadCountLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 2),
unreadCountLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor),
view.widthAnchor.constraint(greaterThanOrEqualTo: view.heightAnchor, multiplier: 1),
])
return view
}()
private func addUnreadLabel() {
let pillViewOverlap: CGFloat = 8 // how much unread badge pill overlaps circle
addSubview(unreadBadge)
NSLayoutConstraint.activate([
unreadBadge.centerXAnchor.constraint(equalTo: centerXAnchor),
unreadBadge.bottomAnchor.constraint(equalTo: topAnchor, constant: pillViewOverlap),
])
applyUnreadBadgeStroke()
}
private func applyUnreadBadgeStroke() {
let strokeColor = switch traitCollection.userInterfaceStyle {
case .dark: UIColor(white: 1, alpha: 0.1)
default: UIColor(white: 0, alpha: 0.1)
}
unreadBadge.layer.borderWidth = 1
unreadBadge.layer.borderColor = strokeColor.cgColor
}
}