<commit_before>/*
 |  _   ____   ____   _
 | | |‾|  ⚈ |-| ⚈  |‾| |
 | | |  ‾‾‾‾| |‾‾‾‾  | |
 |  ‾        ‾        ‾
 */

import UIKit

@IBDesignable open class AvatarLineView: UIView {
    
    // MARK: - Inspectable properties
    
    @IBInspectable open var borderColor: UIColor = UIColor(red: 29 / 255.0, green: 30 / 255.0, blue: 29 / 255.0, alpha: 1.0) {
        didSet {
            updateColors()
        }
    }
    
    @IBInspectable open var innerColor: UIColor = UIColor(red: 232 / 255.0, green: 236 / 255.0, blue: 237 / 255.0, alpha: 1.0) {
        didSet {
            updateColors()
        }
    }
    
    @IBInspectable open var textColor: UIColor = UIColor(red: 29 / 255.0, green: 30 / 255.0, blue: 29 / 255.0, alpha: 1.0) {
        didSet {
            updateColors()
        }
    }
    
    @IBInspectable open var avatarBorderWidth: CGFloat = 0.0 {
        didSet {
            updateBorders()
        }
    }
    
    @IBInspectable open var fontName: String? = nil {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable open var fontSize: CGFloat = 17.0 {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable open var overlap: CGFloat = 10 {
        didSet {
            updateAllConstraints()
        }
    }
    
    @IBInspectable open var maxCircles: Int = 3 {
        didSet {
            layoutIfNeeded()
        }
    }
    
    /// If `true` avatars with initials instead of images will be filtered out if possible
    @IBInspectable open var preferImageAvatars = false {
        didSet {
            layoutIfNeeded()
        }
    }
    
    
    fileprivate var avatars = [AvatarPresentable]()
    fileprivate var avatarViews = [AvatarView]()
    fileprivate var borderViews = [UIView]()
    fileprivate var plusAvatar = AvatarView()
    fileprivate let stackView = UIStackView()
    

    // MARK: - Initialization
    
    override public init(frame: CGRect) {
        super.init(frame: frame)
        setUpViews()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUpViews()
    }

    
    // MARK: - Lifecycle overrides
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        
        borderViews.forEach { borderView in
            borderView.layer.cornerRadius = stackView.frame.size.height / 2
        }
    }
    
}



//MARK: - Public

extension AvatarLineView {
    
    open func update(with avatars: [AvatarPresentable]) {
        self.avatars = avatars
        setUpViews()
    }
    
}


private extension AvatarLineView {
    
    func setUpViews() {
        backgroundColor = .clear
        clipsToBounds = true
        translatesAutoresizingMaskIntoConstraints = false
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.alignment = .fill
        stackView.spacing = -overlap
        addSubview(stackView)
        updateAllConstraints()
        
        self.avatarViews.removeAll()
        stackView.arrangedSubviews.forEach { stackView.removeArrangedSubview($0) }
        stackView.subviews.forEach { $0.removeFromSuperview() }
        
        if avatars.isEmpty {
            addAvatar(for: nil)
        } else {
            for (index, avatar) in avatars.enumerated() {
                if avatars.count > maxCircles && index >= maxCircles - 1 {
                    let remaining = maxCircles - 2 // 1 for 0-based index, 2 for extra avatar to show remaining count
                    addAvatar(for: nil, text: "+\(remaining)")
                    break
                } else {
                    addAvatar(for: avatar)
                }
            }
        }
    }
    <commit_msg>Fix display of additional avatar count<commit_after>/*
 |  _   ____   ____   _
 | | |‾|  ⚈ |-| ⚈  |‾| |
 | | |  ‾‾‾‾| |‾‾‾‾  | |
 |  ‾        ‾        ‾
 */

import UIKit

@IBDesignable open class AvatarLineView: UIView {
    
    // MARK: - Inspectable properties
    
    @IBInspectable open var borderColor: UIColor = UIColor(red: 29 / 255.0, green: 30 / 255.0, blue: 29 / 255.0, alpha: 1.0) {
        didSet {
            updateColors()
        }
    }
    
    @IBInspectable open var innerColor: UIColor = UIColor(red: 232 / 255.0, green: 236 / 255.0, blue: 237 / 255.0, alpha: 1.0) {
        didSet {
            updateColors()
        }
    }
    
    @IBInspectable open var textColor: UIColor = UIColor(red: 29 / 255.0, green: 30 / 255.0, blue: 29 / 255.0, alpha: 1.0) {
        didSet {
            updateColors()
        }
    }
    
    @IBInspectable open var avatarBorderWidth: CGFloat = 0.0 {
        didSet {
            updateBorders()
        }
    }
    
    @IBInspectable open var fontName: String? = nil {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable open var fontSize: CGFloat = 17.0 {
        didSet {
            setNeedsLayout()
        }
    }
    
    @IBInspectable open var overlap: CGFloat = 10 {
        didSet {
            updateAllConstraints()
        }
    }
    
    @IBInspectable open var maxCircles: Int = 3 {
        didSet {
            layoutIfNeeded()
        }
    }
    
    /// If `true` avatars with initials instead of images will be filtered out if possible
    @IBInspectable open var preferImageAvatars = false {
        didSet {
            layoutIfNeeded()
        }
    }
    
    
    fileprivate var avatars = [AvatarPresentable]()
    fileprivate var avatarViews = [AvatarView]()
    fileprivate var borderViews = [UIView]()
    fileprivate var plusAvatar = AvatarView()
    fileprivate let stackView = UIStackView()
    

    // MARK: - Initialization
    
    override public init(frame: CGRect) {
        super.init(frame: frame)
        setUpViews()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setUpViews()
    }

    
    // MARK: - Lifecycle overrides
    
    override open func layoutSubviews() {
        super.layoutSubviews()
        
        borderViews.forEach { borderView in
            borderView.layer.cornerRadius = stackView.frame.size.height / 2
        }
    }
    
}



//MARK: - Public

extension AvatarLineView {
    
    open func update(with avatars: [AvatarPresentable]) {
        self.avatars = avatars
        setUpViews()
    }
    
}


private extension AvatarLineView {
    
    func setUpViews() {
        backgroundColor = .clear
        clipsToBounds = true
        translatesAutoresizingMaskIntoConstraints = false
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .horizontal
        stackView.distribution = .fillEqually
        stackView.alignment = .fill
        stackView.spacing = -overlap
        addSubview(stackView)
        updateAllConstraints()
        
        self.avatarViews.removeAll()
        stackView.arrangedSubviews.forEach { stackView.removeArrangedSubview($0) }
        stackView.subviews.forEach { $0.removeFromSuperview() }
        
        if avatars.isEmpty {
            addAvatar(for: nil)
        } else {
            for (index, avatar) in avatars.enumerated() {
                if avatars.count > maxCircles && index >= maxCircles - 1 {
                    let remaining = avatars.count - index
                    addAvatar(for: nil, text: "+\(remaining)")
                    break
                } else {
                    addAvatar(for: avatar)
                }
            }
        }
    }
    
{'fraction_non_alphanumeric': 0.09572086899275839, 'fraction_numerical': 0.020276497695852536, 'mean_word_length': 1.6643283058575937, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4506222', 'n_tokens_mistral': 2526, 'n_tokens_neox': 2337, 'n_words': 525}