Visual_Novel_iOS/crush/Crush/Src/Modules/Role/Voice/RoleVoiceSetController.swift

396 lines
12 KiB
Swift
Raw Normal View History

2025-10-09 10:29:35 +00:00
//
// RoleVoiceSetController.swift
// Crush
//
// Created by Leon on 2025/7/20.
//
import JXPagingView
import JXSegmentedView
import UIKit
import Combine
class RoleVoiceSetController: CLBaseViewController {
weak var viewModel: RoleCreateViewModel!
/// Public ,
var selectVoiceBefore: VoiceModel?
/// Public ,
var sexSelect: Sex?
private let segmentedViewHeight = 40
private lazy var segmentedView = JXSegmentedView(frame: CGRect(x: 0, y: 0, width: UIScreen.width, height: CGFloat(segmentedViewHeight)))
private lazy var pagingView = JXPagingListRefreshView(delegate: self)
private var headerView: RoleVoiceSetHeaderView!
private var headerViewHeight: CGFloat = 288
private var controllers = [JXPagingViewListViewDelegate]()
private let dataSource = JXSegmentedTitleDataSource()
private var maleVoiceListVc: RoleVoiceListController!
private var femaleVoiceListVc: RoleVoiceListController!
var selectVoiceAction: ((_ voice: VoiceModel?) -> Void)?
// Data
@Published private var selectVoice:VoiceModel?
private var cancellables = Set<AnyCancellable>()
// Flag
var tabSelectIndexLast: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupDatas()
setupEvents()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
SpeechManager.shared.stopPlayCurrent()
navigationController?.interactivePopGestureRecognizer?.isEnabled = true
}
private func setupViews() {
navigationView.alpha0Title = "Voice"
navigationView.styleMainButton.text = "Confirm"
navigationView.styleMainButton.addTarget(self, action: #selector(tapNaviComfirm), for: .touchUpInside)
headerView = {
let v = RoleVoiceSetHeaderView()
v.heightChangeBlock = { [weak self] height in
self?.headerViewHeight = height
self?.pagingView.resizeTableHeaderViewHeight()
}
return v
}()
pagingView.mainTableView.backgroundColor = .clear
view.addSubview(pagingView)
pagingView.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview()
make.bottom.equalToSuperview()
make.top.equalTo(navigationView.snp.bottom)
}
dataSource.clNormalStyle()
dataSource.titles = ["Male", "Female"]
segmentedView.listContainer = pagingView.listContainerView
segmentedView.dataSource = dataSource
segmentedView.delegate = self
segmentedView.clNormalStyle()
maleVoiceListVc = {
let vc = RoleVoiceListController()
controllers.append(vc)
return vc
}()
femaleVoiceListVc = {
let vc = RoleVoiceListController()
controllers.append(vc)
return vc
}()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
disabledFullScreenPan()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
enabledFullScreenPan()
}
private func setupDatas() {
guard let timbreDictArray = AppDictManager.shared.aiDict?.timbreDictList else{
Hud.showIndicator()
AppDictManager.shared.loadAIDict {[weak self] ok in
Hud.hideIndicator()
if let array = AppDictManager.shared.aiDict?.timbreDictList, array.count > 0{
self?.setupDatas()
}
}
return
}
//
var maleVoices = [VoiceModel]()
var femaleVoices = [VoiceModel]()
//
var selectVoiceLastTimeId = 0
if let lastTime = selectVoiceBefore{
selectVoiceLastTimeId = lastTime.voiceDict.id!
}
var defaultTabIndex = 0
let sex = sexSelect ?? .noncomfirming
for (_, per) in timbreDictArray.enumerated(){
//
if let type = per.type, type == 1{
let model = VoiceModel(voiceDict: per)
if maleVoices.count <= 0 && sex == .male{
defaultTabIndex = 0
}
if per.id! == selectVoiceLastTimeId{
selectVoice = model
selectVoice?.syncConfigFrom(other: selectVoiceBefore)
defaultTabIndex = 0
}
maleVoices.append(model)
}else{
//
let model = VoiceModel(voiceDict: per)
if femaleVoices.count <= 0 && sex == .female{
defaultTabIndex = 1
}
if per.id! == selectVoiceLastTimeId{
selectVoice = model
selectVoice?.syncConfigFrom(other: selectVoiceBefore)
defaultTabIndex = 1
}
femaleVoices.append(model)
}
}
tabSelectIndexLast = defaultTabIndex
segmentedView.defaultSelectedIndex = defaultTabIndex
maleVoiceListVc.voices = maleVoices
femaleVoiceListVc.voices = femaleVoices
}
private func setupEvents() {
headerView.roleVoiceAdjustView.tapCloseButtonBlock = {[weak self] in
self?.selectVoice = nil
}
maleVoiceListVc.selectVoiceAction = {[weak self] result in
dlog("🎤当前选中的音频:\(String(describing: result))")
self?.selectVoice = result
}
femaleVoiceListVc.selectVoiceAction = {[weak self] result in
dlog("🎤当前选中的音频2\(String(describing: result))")
// self?.headerView.bind(voice: result)
// self?.refreshViewStates()
self?.selectVoice = result
}
$selectVoice.sink {[weak self] result in
guard let `self` = self else {
return
}
SpeechManager.shared.stopPlayCurrent()
self.headerView.bind(voice: result)
self.refreshViewStates()
assert(self.femaleVoiceListVc != nil, "femaleVoiceListVc must be initialized")
if let voice = result{
self.femaleVoiceListVc.select(voiceId: voice.voiceDict.id!)
self.maleVoiceListVc.select(voiceId: voice.voiceDict.id!)
}else{
self.femaleVoiceListVc.select(voiceId: 0)
self.maleVoiceListVc.select(voiceId: 0)
}
}.store(in: &cancellables)
}
// MARK: - Helper
private func refreshViewStates(){
// dlog("voice:\(String(describing: maleVoiceListVc.selectVoice)) and \(String(describing: femaleVoiceListVc.selectVoice))")
// navigationView.styleMainButton.isEnabled = (maleVoiceListVc.selectVoice != nil || femaleVoiceListVc.selectVoice != nil)
dlog("headView's voice \(String(describing: headerView.roleVoiceAdjustView.voice))")
navigationView.styleMainButton.isEnabled = headerView.roleVoiceAdjustView.voice != nil
}
// MARK: - Action
@objc private func tapNaviComfirm(){
guard selectVoice != nil else {return}
let pitch = "\(headerView.roleVoiceAdjustView.speechLoudness)"
let speed = "\(headerView.roleVoiceAdjustView.speechRate)"
selectVoice?.dialoguePitch = pitch
selectVoice?.dialogueSpeechRate = speed
selectVoiceAction?(selectVoice)
navigationController?.popViewController(animated: true)
}
}
extension RoleVoiceSetController: JXPagingViewDelegate, JXSegmentedViewDelegate {
func tableHeaderViewHeight(in _: JXPagingView) -> Int {
return Int(headerViewHeight)
}
func tableHeaderView(in _: JXPagingView) -> UIView {
return headerView
}
func heightForPinSectionHeader(in _: JXPagingView) -> Int {
return segmentedViewHeight
}
func viewForPinSectionHeader(in _: JXPagingView) -> UIView {
return segmentedView
}
func numberOfLists(in _: JXPagingView) -> Int {
return controllers.count
}
func pagingView(_: JXPagingView, initListAtIndex index: Int) -> JXPagingViewListViewDelegate {
let vc = controllers[index]
return vc
}
func segmentedView(_ segmentedView: JXSegmentedView, didSelectedItemAt index: Int) {
if tabSelectIndexLast != index{
tabSelectIndexLast = index;
SpeechManager.shared.stopPlayCurrent()
}
}
func mainTableViewDidScroll(_ scrollView: UIScrollView) {
NaviAlphaHandle.changeNaviTitleAlpha(scrollView: scrollView, titleLabel: navigationView.titleLabel)
}
}
class RoleVoiceSetHeaderView: UIView {
var voiceRecommentTitle: UILabel!
var voiceSelectView: VoiceSelectView!
var roleVoiceAdjustView: RoleVoiceAdjustView!
var heightChangeBlock: ((CGFloat) -> Void)?
override init(frame: CGRect) {
super.init(frame: frame)
setupViews()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupViews() {
let titleView = {
let v = TitleView()
v.optionInnerBottomPadding = 16
v.optionInnerTopPadding = 16
addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview()
make.trailing.equalToSuperview()
make.top.equalToSuperview()
}
return v
}()
titleView.titleLabel.text = "Voice"
let subTitleView = {
let v = UILabel()
v.font = .t.tlm
v.textColor = .c.ctsn
addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(CGFloat.lrs)
make.trailing.equalToSuperview().offset(-CGFloat.lrs)
make.top.equalTo(titleView.snp.bottom).offset(12)
}
v.text = "Select your voice"
return v
}()
// StackViewV has voiceSelectView, RoleVoiceAdjustView
let stackView = {
let v = UIStackView()
v.axis = .vertical
v.spacing = 12
addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(CGFloat.lrs)
make.trailing.equalToSuperview().offset(-CGFloat.lrs)
make.top.equalTo(subTitleView.snp.bottom).offset(12)
}
return v
}()
voiceSelectView = {
let v = VoiceSelectView()
stackView.addArrangedSubview(v)
v.snp.makeConstraints { _ in
// make.height.equalTo(120)
}
return v
}()
roleVoiceAdjustView = {
let v = RoleVoiceAdjustView()
stackView.addArrangedSubview(v)
v.snp.makeConstraints { _ in
// make.height.equalTo(120)
}
return v
}()
voiceSelectView.isHidden = false
roleVoiceAdjustView.isHidden = true
voiceRecommentTitle = {
let v = UILabel()
v.font = .t.ttm
v.textColor = .c.ctpn
addSubview(v)
v.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(CGFloat.lrs)
make.top.equalTo(stackView.snp.bottom).offset(12)
}
v.text = "Voice Recommendation"
return v
}()
}
override func layoutSubviews() {
super.layoutSubviews()
let maxY = voiceRecommentTitle.frame.maxY + 12
heightChangeBlock?(maxY)
}
public func bind(voice: VoiceModel?){
if voice != nil{
voiceSelectView.isHidden = true
roleVoiceAdjustView.isHidden = false
let name = voice?.voiceDict.name ?? "-"
roleVoiceAdjustView.titleLabel.text = name
}else{
voiceSelectView.isHidden = false
roleVoiceAdjustView.isHidden = true
}
roleVoiceAdjustView.bind(voice: voice)
}
}