Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Classes/Cells/CytrusMultplayerRoomCell.swift
2 views
//
//  CytrusMultplayerRoomCell.swift
//  Folium-iOS
//
//  Created by Jarrod Norwell on 21/10/2025.
//

import Cytrus
import Foundation
import UIKit

class CytrusMultplayerRoomCell : UICollectionViewCell {
    var textLabel: UILabel? = nil, // ip
        secondaryTextLabel: UILabel? = nil, // name
        tertiaryTextLabel: UILabel? = nil // details
    
    var playersLabel: UILabel? = nil,
        maxPlayersTextLabel: UILabel? = nil
    
    var optionsButton: UIButton? = nil
    
    var imageView: UIImageView? = nil
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = .secondarySystemBackground
        
        textLabel = .init()
        guard let textLabel else {
            return
        }
        textLabel.translatesAutoresizingMaskIntoConstraints = false
        textLabel.font = .preferredFont(forTextStyle: .headline)
        textLabel.textAlignment = .left
        textLabel.textColor = .tertiaryLabel
        addSubview(textLabel)
        
        secondaryTextLabel = .init()
        guard let secondaryTextLabel else {
            return
        }
        secondaryTextLabel.translatesAutoresizingMaskIntoConstraints = false
        secondaryTextLabel.font = .bold(.title2)
        secondaryTextLabel.numberOfLines = 3
        secondaryTextLabel.textAlignment = .left
        secondaryTextLabel.textColor = .label
        addSubview(secondaryTextLabel)
        
        tertiaryTextLabel = .init()
        guard let tertiaryTextLabel else {
            return
        }
        tertiaryTextLabel.translatesAutoresizingMaskIntoConstraints = false
        tertiaryTextLabel.font = .preferredFont(forTextStyle: .body)
        tertiaryTextLabel.numberOfLines = 3
        tertiaryTextLabel.textAlignment = .left
        tertiaryTextLabel.textColor = .secondaryLabel
        addSubview(tertiaryTextLabel)
        
        maxPlayersTextLabel = .init()
        guard let maxPlayersTextLabel else {
            return
        }
        maxPlayersTextLabel.translatesAutoresizingMaskIntoConstraints = false
        maxPlayersTextLabel.font = .bold(.body)
        maxPlayersTextLabel.textAlignment = .left
        maxPlayersTextLabel.textColor = .tertiaryLabel
        addSubview(maxPlayersTextLabel)
        
        playersLabel = .init()
        guard let playersLabel else {
            return
        }
        playersLabel.translatesAutoresizingMaskIntoConstraints = false
        playersLabel.font = .bold(.body)
        playersLabel.textAlignment = .left
        playersLabel.textColor = .tertiaryLabel
        addSubview(playersLabel)
        
        var configuration: UIButton.Configuration = if #available(iOS 26, *) {
            .glass()
        } else {
            .filled()
        }
        configuration.buttonSize = .small
        configuration.cornerStyle = .capsule
        configuration.image = .init(systemName: "ellipsis")
        
        optionsButton = .init(configuration: configuration)
        guard let optionsButton else {
            return
        }
        optionsButton.translatesAutoresizingMaskIntoConstraints = false
        optionsButton.showsMenuAsPrimaryAction = true
        if #unavailable(iOS 26) {
            optionsButton.layer.shadowColor = UIColor.black.cgColor
            optionsButton.layer.shadowOpacity = 1 / 4
            optionsButton.layer.shadowRadius = 20
            optionsButton.layer.shadowOffset = .init(width: 0, height: 10)
        }
        optionsButton.alpha = 0
        addSubview(optionsButton)
        
        imageView = .init()
        guard let imageView else {
            return
        }
        imageView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imageView)
        
        addConstraints([
            textLabel.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            textLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            textLabel.trailingAnchor.constraint(lessThanOrEqualTo: imageView.safeAreaLayoutGuide.leadingAnchor, constant: -20),
            
            secondaryTextLabel.topAnchor.constraint(equalTo: textLabel.safeAreaLayoutGuide.bottomAnchor, constant: 8),
            secondaryTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            secondaryTextLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -20),
            
            tertiaryTextLabel.topAnchor.constraint(equalTo: secondaryTextLabel.safeAreaLayoutGuide.bottomAnchor, constant: 8),
            tertiaryTextLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20),
            tertiaryTextLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -20),
            
            playersLabel.topAnchor.constraint(equalTo: tertiaryTextLabel.safeAreaLayoutGuide.bottomAnchor, constant: 8),
            playersLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
            
            maxPlayersTextLabel.topAnchor.constraint(equalTo: tertiaryTextLabel.safeAreaLayoutGuide.bottomAnchor, constant: 8),
            maxPlayersTextLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 20),
            maxPlayersTextLabel.trailingAnchor.constraint(equalTo: playersLabel.safeAreaLayoutGuide.leadingAnchor, constant: -20),
            
            maxPlayersTextLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -20),
            
            optionsButton.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            optionsButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20),
            
            imageView.topAnchor.constraint(equalTo: topAnchor, constant: 20),
            imageView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20)
        ])
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        guard let optionsButton, let configuration = optionsButton.configuration else {
            return
        }
        
        layer.cornerRadius = configuration.background.cornerRadius + 20
    }
    
    private func attributed(_ font: UIFont, _ string: String, _ image: UIImage? = nil) -> AttributedString {
        var secondaryAttributedText: AttributedString = .init()
        
        let attachment: NSTextAttachment = .init()
        attachment.image = image?.withTintColor(.tertiaryLabel)
            .applyingSymbolConfiguration(.init(scale: .small))
        
        let attributed: NSAttributedString = .init(attachment: attachment)
        if let converted: AttributedString = try? .init(attributed, including: \.uiKit) {
            secondaryAttributedText.append(converted)
        }
        
        secondaryAttributedText.append(AttributedString(string))
        return secondaryAttributedText
    }
    
    func set(_ room: CytrusRoom, isCurrent: Bool = false) {
        guard let textLabel, let secondaryTextLabel, let tertiaryTextLabel,
              let maxPlayersTextLabel, let playersLabel, let imageView else {
            return
        }
        
        textLabel.text = room.ip
        secondaryTextLabel.text = room.name
        tertiaryTextLabel.text = room.details.trimmingCharacters(in: .whitespaces).isEmpty ? "No Details" : room.details
        
        maxPlayersTextLabel.attributedText = .init(attributed(.bold(.body),
                                                              " \(room.maximumPlayers) maximum",
                                                              .init(systemName: "number.circle.fill")))
        playersLabel.attributedText = .init(attributed(.bold(.body),
                                                       " \(room.numberOfPlayers) current",
                                                      .init(systemName: "person.crop.circle.fill")))
        
        imageView.image = .init(systemName: isCurrent ? "checkmark" : room.passwordLocked ? "lock.fill" : "lock.open.fill")?
            .applyingSymbolConfiguration(.init(scale: .medium))
    }
}