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)
}
}
}