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