chat setting chat mode展开折叠

This commit is contained in:
mh 2025-10-31 15:29:20 +08:00
parent c4a34fd419
commit 4c4c578d0b
3 changed files with 310 additions and 2 deletions

View File

@ -0,0 +1,250 @@
//
// ChatModeContainerCell.swift
// Visual_Novel_iOS
//
// Created by mh on 2025/01/27.
//
import UIKit
import SnapKit
// MARK: -
struct ChatModeItem {
let id: String
let title: String
let subtitle: String
let isVip: Bool
var isSelected: Bool
}
// MARK: - RowModel
struct ChatModeRow: RowModel {
let items: [ChatModeItem]
var cellReuseID: String { "ChatModeContainerCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat {
let rowHeight: CGFloat = 58
return CGFloat(items.count) * rowHeight
}
}
// MARK: - ChatModeContainerCell
class ChatModeContainerCell: UITableViewCell, CellConfigurable {
private var items: [ChatModeItem] = []
private var selectedItemId: String?
private var tableViewHeightConstraint: Constraint?
private lazy var tableView: UITableView = {
let tableView = UITableView(frame: .zero, style: .plain)
tableView.separatorStyle = .none
tableView.delegate = self
tableView.dataSource = self
tableView.backgroundColor = UIColor(hex: "#F5F5FF")
tableView.showsVerticalScrollIndicator = false
tableView.isScrollEnabled = false
tableView.register(ChatModeItemCell.self, forCellReuseIdentifier: "ChatModeItemCell")
tableView.estimatedRowHeight = 58
tableView.rowHeight = 58
tableView.contentInset = .zero
tableView.scrollIndicatorInsets = .zero
tableView.bounces = false
tableView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]
tableView.layer.cornerRadius = 15
return tableView
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(tableView)
tableView.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(20)
make.top.bottom.equalToSuperview().inset(2.5)
tableViewHeightConstraint = make.height.equalTo(0).constraint
}
}
func configure(with row: RowModel) {
guard let modeRow = row as? ChatModeRow else { return }
items = modeRow.items
// item
if let selected = items.first(where: { $0.isSelected }) {
selectedItemId = selected.id
}
tableView.reloadData()
updateTableViewHeight()
}
private func updateTableViewHeight() {
let rowHeight: CGFloat = 58
let totalHeight = CGFloat(items.count) * rowHeight
tableViewHeightConstraint?.update(offset: totalHeight)
layoutIfNeeded()
}
}
// MARK: - UITableViewDataSource & UITableViewDelegate
extension ChatModeContainerCell: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatModeItemCell", for: indexPath) as! ChatModeItemCell
let item = items[indexPath.row]
let isSelected = item.id == selectedItemId
cell.configure(with: item, isSelected: isSelected)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let item = items[indexPath.row]
//
selectedItemId = item.id
// cell
tableView.reloadData()
}
}
// MARK: - ChatModeItemCell
class ChatModeItemCell: UITableViewCell {
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hex: "#F5F5FF")
return view
}()
private lazy var titleLabel: UILabel = {
let lab = UILabel()
lab.textColor = UIColor(hex: "#333333")
lab.font = UIFont.systemFont(ofSize: 13)
return lab
}()
private lazy var vipBadge: UILabel = {
let lab = UILabel()
lab.text = "VIP"
lab.textColor = .black
lab.font = UIFont.systemFont(ofSize: 10, weight: .bold)
lab.backgroundColor = UIColor(hex: "#FFD700")
lab.textAlignment = .center
lab.layer.cornerRadius = 6
lab.clipsToBounds = true
lab.isHidden = true
return lab
}()
private lazy var subtitleLabel: UILabel = {
let lab = UILabel()
lab.textColor = UIColor(hex: "#9494C3")
lab.font = UIFont.systemFont(ofSize: 11)
return lab
}()
private lazy var selectionIndicator: UIView = {
let view = UIView()
view.layer.cornerRadius = 10
view.backgroundColor = UIColor(hex: "#333333")
return view
}()
private lazy var selectedDot: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hex: "#00CC88")
view.layer.cornerRadius = 5
view.isHidden = true
return view
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupViews() {
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(containerView)
containerView.addSubview(titleLabel)
containerView.addSubview(vipBadge)
containerView.addSubview(subtitleLabel)
containerView.addSubview(selectionIndicator)
selectionIndicator.addSubview(selectedDot)
containerView.snp.makeConstraints { make in
make.left.right.equalToSuperview().inset(10)
make.top.bottom.equalToSuperview().inset(2.5)
}
titleLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(10)
make.top.equalToSuperview().offset(8)
make.right.lessThanOrEqualTo(vipBadge.snp.left).offset(-8)
}
vipBadge.snp.makeConstraints { make in
make.left.equalTo(titleLabel.snp.right).offset(8)
make.centerY.equalTo(titleLabel)
make.width.equalTo(24)
make.height.equalTo(14)
}
subtitleLabel.snp.makeConstraints { make in
make.left.equalTo(titleLabel)
make.top.equalTo(titleLabel.snp.bottom).offset(2)
make.right.lessThanOrEqualTo(selectionIndicator.snp.left).offset(-10)
}
selectionIndicator.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.right.equalToSuperview().inset(10)
make.width.height.equalTo(20)
}
selectedDot.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.height.equalTo(10)
}
}
func configure(with item: ChatModeItem, isSelected: Bool) {
titleLabel.text = item.title
subtitleLabel.text = item.subtitle
vipBadge.isHidden = !item.isVip
//
if isSelected {
selectionIndicator.backgroundColor = UIColor(hex: "#00CC88")
selectedDot.isHidden = false
} else {
selectionIndicator.backgroundColor = UIColor(hex: "#333333")
selectedDot.isHidden = true
}
}
}

View File

@ -17,12 +17,13 @@ struct ImageRow: RowModel {
let showSwitch: Bool
let subItems: [ImageRow]? //
let buttleItems: [ChatButtleRow]?
let chatModeItems: [ChatModeItem]?
var isExpanded: Bool = false //
var cellReuseID: String { "ChatSwipeCell" }
func cellHeight(tableWidth: CGFloat) -> CGFloat { 50 }
init(icon: String, title: String, showAvatar: Bool = false, showArrow: Bool = false, showSwitch: Bool = false, subItems: [ImageRow]? = nil, buttleItems: [ChatButtleRow]? = nil) {
init(icon: String, title: String, showAvatar: Bool = false, showArrow: Bool = false, showSwitch: Bool = false, subItems: [ImageRow]? = nil, buttleItems: [ChatButtleRow]? = nil, chatModeItems: [ChatModeItem]? = nil) {
self.icon = icon
self.title = title
self.showAvatar = showAvatar
@ -30,6 +31,7 @@ struct ImageRow: RowModel {
self.showSwitch = showSwitch
self.subItems = subItems
self.buttleItems = buttleItems
self.chatModeItems = chatModeItems
}
}

View File

@ -57,7 +57,7 @@ class ChatSettingSwipeView: CLContainer {
[modelRow, ImageRow(icon: "role_text_mode", title: "Short Text Mode", showAvatar: false, showArrow: false, showSwitch: true)],
[ImageRow(icon: "role_voice", title: "Voice actor", showAvatar: true, showArrow: true, showSwitch: false), ImageRow(icon: "role_talk", title: "Play dialogue only", showAvatar: false, showArrow: false, showSwitch: true)],
[TokenRow(count: "2500")],
[FontRow(count: "20", icon: "role_font", title: "Font size"), ImageRow(icon: "role_chat_mode", title: "Chat Mode", showAvatar: false, showArrow: true, showSwitch: false), buttleRow],
[FontRow(count: "20", icon: "role_font", title: "Font size"), ImageRow(icon: "role_chat_mode", title: "Chat Mode", showAvatar: false, showArrow: true, showSwitch: false, chatModeItems: createChatModeItems()), buttleRow],
[BackgroundRow(count: 50)],
[HistoryRow(time: "", icon: "", title: "", itemCount: 30)]
]
@ -111,6 +111,7 @@ class ChatSettingSwipeView: CLContainer {
tableView.register(ChatHistoryCell.self, forCellReuseIdentifier: "ChatHistoryCell")
tableView.register(SubItemsContainerCell.self, forCellReuseIdentifier: "SubItemsContainerCell")
tableView.register(ChatButtleCollectionCell.self, forCellReuseIdentifier: "ChatButtleCollectionCell")
tableView.register(ChatModeContainerCell.self, forCellReuseIdentifier: "ChatModeContainerCell")
tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
return tableView
}()
@ -192,6 +193,45 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let imageRow = rows[indexPath.section][indexPath.row] as? ImageRow,
imageRow.showArrow else { return }
tableView.deselectRow(at: indexPath, animated: true)
// Chat Mode
if imageRow.title == "Chat Mode", let chatModeItems = imageRow.chatModeItems, !chatModeItems.isEmpty {
let isExpanded = expandedStates[indexPath.section]?[indexPath.row] ?? false
expandedStates[indexPath.section, default: [:]][indexPath.row] = !isExpanded
if !isExpanded {
//
//
let modeRow = ChatModeRow(items: chatModeItems)
rows[indexPath.section].insert(modeRow, at: indexPath.row + 1)
let insertIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.performBatchUpdates({
tableView.insertRows(at: [insertIndexPath], with: .fade)
tableView.reloadRows(at: [indexPath], with: .none)
}, completion: nil)
} else {
//
//
if indexPath.row + 1 < rows[indexPath.section].count,
rows[indexPath.section][indexPath.row + 1] is ChatModeRow {
rows[indexPath.section].remove(at: indexPath.row + 1)
let deleteIndexPath = IndexPath(row: indexPath.row + 1, section: indexPath.section)
tableView.performBatchUpdates({
tableView.deleteRows(at: [deleteIndexPath], with: .fade)
tableView.reloadRows(at: [indexPath], with: .none)
}, completion: nil)
}
}
return
}
// Chat buttle使
if imageRow.title == "Chat buttle", let subItems = imageRow.buttleItems, !subItems.isEmpty {
tableView.deselectRow(at: indexPath, animated: true)
@ -270,6 +310,14 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = rows[indexPath.section][indexPath.row]
// ChatModeRow
if let modeRow = row as? ChatModeRow {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatModeContainerCell", for: indexPath) as! ChatModeContainerCell
cell.selectionStyle = .none
cell.configure(with: modeRow)
return cell
}
// ChatButtleRow
if let buttleRow = row as? SubButtleRow {
let cell = tableView.dequeueReusableCell(withIdentifier: "ChatButtleCollectionCell", for: indexPath) as! ChatButtleCollectionCell
@ -326,4 +374,12 @@ extension ChatSettingSwipeView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFLOAT_MIN
}
// MARK: - ChatMode
private func createChatModeItems() -> [ChatModeItem] {
return [
ChatModeItem(id: "1", title: "Dialogue Mode", subtitle: "Previous-generation large model", isVip: false, isSelected: true),
ChatModeItem(id: "2", title: "Immersive Mode", subtitle: "Previous-generation large model", isVip: true, isSelected: false)
]
}
}