chat setting chat mode展开折叠
This commit is contained in:
parent
c4a34fd419
commit
4c4c578d0b
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue