Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
folium-app
GitHub Repository: folium-app/Folium
Path: blob/a-new-beginning/Folium-iOS/Controllers/CytrusMultiplayerChatController.swift
2 views
//
//  CytrusMultiplayerChatController.swift
//  Folium-iOS
//
//  Created by Jarrod Norwell on 7/11/2025.
//

import Cytrus
import Foundation
import UIKit

class CytrusMultiplayerChatController : UICollectionViewController {
    var dataSource: UICollectionViewDiffableDataSource<CytrusChatEntry, CytrusChatEntry>? = nil
    var snapshot: NSDiffableDataSourceSnapshot<CytrusChatEntry, CytrusChatEntry>? = nil
    
    var applicationModel: ApplicationModel
    
    var room: CytrusRoom? = nil
    init(applicationModel: ApplicationModel, room: CytrusRoom? = nil, collectionViewLayout: UICollectionViewCompositionalLayout) {
        self.applicationModel = applicationModel
        self.room = room
        super.init(collectionViewLayout: collectionViewLayout)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        if let navigationController {
            navigationController.navigationBar.prefersLargeTitles = true
        }
        if #available(iOS 17, *) {
            navigationItem.largeTitleDisplayMode = .inline
        }
        navigationItem.style = .browser
        if #available(iOS 26, *) {
            navigationItem.largeTitle = "Chat"
            navigationItem.title = navigationItem.largeTitle
            navigationItem.largeSubtitle = "\(room?.members.count ?? 0) member\(room?.members.count ?? 0 == 1 ? "" : "s")"
            navigationItem.subtitle = navigationItem.largeSubtitle
        } else {
            navigationItem.title = "Chat"
        }
        navigationItem.rightBarButtonItem = .init(systemItem: .add, primaryAction: .init { action in
            let alertController: UIAlertController = .init(title: "Send Chat", message: "Enter text below to send to this room",
                                                           preferredStyle: .alert)
            alertController.addAction(.init(title: .localized(for: .dismiss), style: .cancel))
            alertController.addAction(.init(title: "Send", style: .default) { _ in
                guard let textFields = alertController.textFields, let textField = textFields.first, let string = textField.text else {
                    return
                }
                
                self.applicationModel.cytrus.multiplayer.sendChatMessage(string)
            })
            alertController.addTextField { textField in
                textField.placeholder = "Enter text here..."
            }
            alertController.preferredAction = alertController.actions.last
            self.present(alertController, animated: true)
        })
        view.backgroundColor = .systemBackground
        
        let supplementaryCellRegistration: UICollectionView.SupplementaryRegistration<UICollectionViewListCell> = .init(elementKind: UICollectionView.elementKindSectionHeader) { supplementaryView, elementKind, indexPath in
            var contentConfiguration = UIListContentConfiguration.extraProminentInsetGroupedHeader()
            if let dataSource = self.dataSource, let sectionIdentifier = dataSource.sectionIdentifier(for: indexPath.section) {
                contentConfiguration.text = sectionIdentifier.member.nickname
            }
            supplementaryView.contentConfiguration = contentConfiguration
        }
        
        let cellRegistration: UICollectionView.CellRegistration<UICollectionViewListCell, CytrusChatEntry> = .init { cell, indexPath, itemIdentifier in
            var contentConfiguration = UIListContentConfiguration.subtitleCell()
            contentConfiguration.text = itemIdentifier.entry.message
            
            let formatter: DateFormatter = .init()
            formatter.dateStyle = .medium
            formatter.timeStyle = .short
            contentConfiguration.secondaryText = formatter.string(from: itemIdentifier.entry.date)
            
            cell.contentConfiguration = contentConfiguration
        }
        
        dataSource = .init(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in
            collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier)
        }
        guard let dataSource else {
            return
        }
        
        dataSource.supplementaryViewProvider = { collectionView, elementKind, indexPath in
            switch elementKind {
            case UICollectionView.elementKindSectionHeader:
                collectionView.dequeueConfiguredReusableSupplementary(using: supplementaryCellRegistration, for: indexPath)
            default: nil
            }
        }
        
        snapshot = .init()
        guard var snapshot else {
            return
        }
        
        if let room {
            let entries: [CytrusNetworkChatEntry] = applicationModel.cytrus.multiplayer.entries
            for entry in entries {
                if let member = room.members.first(where: { $0.nickname == entry.nickname }) {
                    let chatEntry: CytrusChatEntry = .init(member: member, entry: entry)
                    
                    snapshot.appendSections([chatEntry])
                    snapshot.appendItems([chatEntry], toSection: chatEntry)
                    Task {
                        await dataSource.apply(snapshot)
                    }
                }
            }
        }
    }
    
    func receivedChatEntry(_ entry: CytrusNetworkChatEntry) {
        guard let room, let member = room.members.first(where: { $0.nickname == entry.nickname }) else {
            return
        }
        
        let chatEntry: CytrusChatEntry = .init(member: member, entry: entry)
        guard let dataSource else {
            return
        }
        
        var snapshot = dataSource.snapshot()
        snapshot.appendSections([chatEntry])
        snapshot.appendItems([chatEntry], toSection: chatEntry)
        Task {
            await dataSource.apply(snapshot)
        }
    }
}