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

396 lines
12 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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