Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
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
    }
}