396 lines
12 KiB
Swift
396 lines
12 KiB
Swift
//
|
||
// 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)
|
||
}
|
||
}
|