聊天输入框细节

This commit is contained in:
mh 2025-10-24 10:43:40 +08:00
parent f75c4ecba0
commit 15550525fe
25 changed files with 250 additions and 68 deletions

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "launch_bg@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "launch_bg@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "chat_scr_bottom@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "chat_scr_bottom@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "role_chat_ai@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "role_chat_ai@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "role_chat_up_cancel@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "role_chat_up_cancel@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 349 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "role_chat_voice_cancel@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "role_chat_voice_cancel@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "role_chat_voice_normal@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "role_chat_voice_normal@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

View File

@ -14,6 +14,8 @@ class IMVoiceHoldView: UIView {
var voiceWaveContainer: GradientView!
var wave : LottieAnimationView!
var swipeStackView: UIStackView!
var tipLabel: UILabel!
var voiceIconDecorationView: UIImageView!
@ -57,39 +59,32 @@ class IMVoiceHoldView: UIView {
return v
}()
// gradientBg = {
// let colors = [
// CLGlobalToken.color(token: .glo_color_black)!.withAlphaComponent(0),
// CLGlobalToken.color(token: .glo_color_black)!.withAlphaComponent(0.65),
// CLGlobalToken.color(token: .glo_color_black)!,
// ]
// let v = GradientView(colors: colors, gradientType: .topToBottom)
// addSubview(v)
// v.snp.makeConstraints { make in
// make.edges.equalToSuperview()
// }
// return v
// }()
voiceIconDecorationView = {
let v = UIImageView()
v.image = UIImage(named: "role_chat_voice_normal")
addSubview(v)
v.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(v.snp.width).multipliedBy(171/375.0)
}
return v
}()
voiceWaveContainer = {
let gradient = CLSystemToken.gradient(token: .cpgn)
let v = GradientView(colors: gradient.colors(), gradientType: .leftToRight)
// let gradient = CLSystemToken.gradient(token: .cpgn)
// let v = GradientView(colors: gradient.colors(), gradientType: .leftToRight)
let v = GradientView(colors: [UIColor.clear], gradientType: .leftToRight)
v.cornerRadius = 24
addSubview(v)
v.snp.makeConstraints { make in
make.top.equalToSuperview().offset(64)
make.leading.equalToSuperview().offset(48)
make.trailing.equalToSuperview().offset(-48)
make.top.equalTo(voiceIconDecorationView.snp.top).offset(10)
make.centerX.equalToSuperview()
make.width.equalTo(120)
make.height.equalTo(48)
}
// let temp = UIImageView()
// temp.image = UIImage(named: "temp_voice_wave")
// v.addSubview(temp)
// temp.snp.makeConstraints { make in
// make.edges.equalToSuperview()
// }
return v
}()
@ -114,6 +109,15 @@ class IMVoiceHoldView: UIView {
return voiceAnimation
}()
swipeStackView = {
let stackView = UIStackView(arrangedSubviews: [])
stackView.spacing = 5.0
stackView.distribution = .fill
stackView.alignment = .fill
return stackView
}()
tipLabel = {
let v = UILabel()
v.font = .t.tlm
@ -121,30 +125,7 @@ class IMVoiceHoldView: UIView {
addSubview(v)
v.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(voiceWaveContainer.snp.bottom).offset(24)
}
return v
}()
voiceIconDecorationView = {
let v = UIImageView()
v.image = UIImage(named: "voice_record_decoration")
addSubview(v)
v.snp.makeConstraints { make in
make.leading.trailing.bottom.equalToSuperview()
make.height.equalTo(v.snp.width).multipliedBy(120/393.0)
}
return v
}()
voiceIcon = {
let v = UIImageView()
let image = MWIconFont.image(fromIcon: .voiceMsg, size: CGSize(width: 32, height: 32), color: CLGlobalToken.color(token: .glo_color_grey_80))
v.image = image
addSubview(v)
v.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(voiceIconDecorationView).offset(36)
make.top.equalTo(voiceWaveContainer.snp.bottom).offset(5)
}
return v
}()

View File

@ -37,10 +37,15 @@ class SessionCoverView: UIView {
return btn
}()
lazy var emailBtn: UIButton = {
let vipImgView: UIImageView = {
let imgView = UIImageView(image: UIImage(named: "role_chat_phone_vip"))
return imgView
}()
lazy var aiBtn: UIButton = {
let btn = UIButton(type: .custom)
btn.setImage(UIImage(named: "role_chat_expand_phone"), for: .normal)
btn.addTarget(self, action: #selector(emailBtnClicked), for: .touchDown)
btn.setImage(UIImage(named: "role_chat_ai"), for: .normal)
btn.addTarget(self, action: #selector(aiBtnClicked), for: .touchDown)
btn.alpha = 0
return btn
}()
@ -70,7 +75,7 @@ class SessionCoverView: UIView {
self.aiAnswerBlock?()
}
@objc func emailBtnClicked() {
@objc func aiBtnClicked() {
self.aiAnswerBlock?()
}
@ -79,7 +84,7 @@ class SessionCoverView: UIView {
self.backgroundColor = expand ? .orange : .blue
UIView.animate(withDuration: 0.25) {
self.phoneBtn.alpha = expand ? 1 : 0
self.emailBtn.alpha = expand ? 1 : 0
self.aiBtn.alpha = expand ? 1 : 0
self.lineView.alpha = expand ? 1 : 0
}
}
@ -90,7 +95,9 @@ class SessionCoverView: UIView {
addSubview(lineView)
addSubview(bgview)
addSubview(phoneBtn)
addSubview(emailBtn)
addSubview(vipImgView)
addSubview(aiBtn)
expandBtn.snp.makeConstraints { make in
make.bottom.left.right.equalToSuperview()
@ -110,7 +117,12 @@ class SessionCoverView: UIView {
make.width.height.equalTo(30)
}
emailBtn.snp.makeConstraints { make in
vipImgView.snp.makeConstraints { make in
make.right.equalTo(phoneBtn.snp.right)
make.bottom.equalTo(phoneBtn.snp.bottom)
}
aiBtn.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.bottom.equalTo(phoneBtn.snp.top).offset(-20)
make.width.height.equalTo(30)

View File

@ -37,6 +37,7 @@ class SessionInputOperateView: UIView {
var inputTextView: CLTextView!
let minTextViewHeight = 40.0//56.0
var sendBtn: UIButton!
var isFirstIn: Bool = true
//var tapInputFieldAction: (() -> Void)?
// var voiceHoldAction: ((_ onVoice: Bool) -> Void)?
@ -111,7 +112,7 @@ class SessionInputOperateView: UIView {
safeView.addSubview(v)
v.snp.makeConstraints { make in
make.left.equalTo(expandView.snp.right).offset(12)
make.right.equalToSuperview().offset(-24)
make.right.equalToSuperview().offset(-20)
// make.height.equalTo(48)
make.top.equalToSuperview().inset(16)
make.centerY.equalToSuperview()
@ -164,6 +165,7 @@ class SessionInputOperateView: UIView {
let v = CLTextView()
block.addSubview(v)
v.placeholder = "Type a message..."
v.textContainerInset = UIEdgeInsets(top: 10, left: 5, bottom: 10, right: 0)
v.placeholderTextColor = UIColor.init(white: 1, alpha: 0.4)
v.backgroundColor = .clear
v.limit.maxCharacterNumber = 500
@ -182,10 +184,10 @@ class SessionInputOperateView: UIView {
voiceHoldView = {
let v = UIView()
block.addSubview(v)
block.insertSubview(v, belowSubview: modeButton)
v.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.left.right.equalTo(inputTextView)
make.left.right.top.bottom.equalToSuperview()
// make.left.right.equalTo(inputTextView)
}
let voiceImgView = UIImageView(image: UIImage(named: "role_chat_remind_voice"))
@ -221,7 +223,7 @@ class SessionInputOperateView: UIView {
//
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
longPress.minimumPressDuration = 0.5 // 0.5
longPress.minimumPressDuration = 0.2 // 0.5
voiceHoldView.addGestureRecognizer(longPress)
$state.sink { [weak self] state in
@ -237,30 +239,42 @@ class SessionInputOperateView: UIView {
// let size = CGSize(width: 26, height: 26)
switch stateOf {
case .text:
// fakeTextfield.isHidden = false
inputTextView.isHidden = false
voiceHoldView.isHidden = true
// let image = MWIconFont.image(fromIcon:4 .voiceMsg, size: size, color: .white)
sendBtn.isHidden = false
let image = UIImage(named: "role_chat_voice")
modeButton.setImage(image, for: .normal)
if !isFirstIn {
self.fixTextViewHeight()
self.inputTextView.becomeFirstResponder()
}
case .voice:
// fakeTextfield.isHidden = true
inputTextView.isHidden = true
voiceHoldView.isHidden = false
sendBtn.isHidden = true
let image = UIImage(named: "role_chat_keyboard")
modeButton.setImage(image, for: .normal)
self.inputTextView.snp.updateConstraints { make in
make.height.equalTo(40)
}
self.inputTextView.resignFirstResponder()
AudioRecordTool.audioAuth()
}
if isFirstIn {
isFirstIn = false
}
}
private func fixTextViewHeight(){
if inputTextView.contentSize.height > minTextViewHeight {
inputTextView.snp.updateConstraints { make in
make.height.equalTo(max(inputTextView.contentSize.height, 48)) // 73
let maxH = inputTextView.contentSize.height
make.height.equalTo(max(maxH >= 173 ? 173 : maxH, 48)) // 73
}
}else{
inputTextView.snp.updateConstraints { make in
make.height.equalTo(inputTextView.contentSize.height) //minTextViewHeight
make.height.equalTo(minTextViewHeight) //minTextViewHeight
}
}
}

View File

@ -393,6 +393,8 @@ extension SessionController {
@objc func tapNaviMore(sender: UIButton) {
// sessionNavigationView.upDownNoticeView.showUnlocked(string: "XY")
inputEntrance.inputTextView.resignFirstResponder()
let vc = ChatSettingListController()
vc.aiId = aiId
navigationController?.pushViewController(vc, animated: true)

View File

@ -33,6 +33,44 @@ extension SessionController {
return v
}()
toolView = {
let view = UIView()
bottomViewsStackV.addArrangedSubview(view)
let exchangeBtn = UIButton(type: .custom)
exchangeBtn.setImage(UIImage(named: "role_chat_change_open"), for: .normal)
// exchangeBtn.addTarget(, action: <#T##Selector#>, for: <#T##UIControl.Event#>)
// view.addSubview(exchangeBtn)
// exchangeBtn.snp.makeConstraints { make in
// make.right.equalToSuperview().inset(20)
// make.bottom.equalToSuperview().inset(2)
// make.top.equalToSuperview().inset(5)
// }
let scrToBottomBtn = UIButton(type: .custom)
scrToBottomBtn.setImage(UIImage(named: "chat_scr_bottom"), for: .normal)
let stackView = UIStackView(arrangedSubviews: [scrToBottomBtn, exchangeBtn])
stackView.spacing = 10.0
stackView.alignment = .fill
stackView.alignment = .fill
view.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.right.equalToSuperview().inset(20)
make.bottom.equalToSuperview().inset(2)
make.top.equalToSuperview().inset(5)
}
let vipImgView = UIImageView(image: UIImage(named: "role_chat_vip"))
vipImgView.isUserInteractionEnabled = false
view.addSubview(vipImgView)
vipImgView.snp.makeConstraints { make in
make.bottom.equalToSuperview().inset(0)
make.right.equalToSuperview().inset(18)
}
return view
}()
inputEntrance = {
let v = SessionInputOperateView()

View File

@ -14,6 +14,7 @@ class SessionController: CLBaseViewController {
// MARK: BottomViews
var bottomViewsStackV : InputStackView!
var toolView: UIView!
var inputEntrance: SessionInputOperateView!
var inputBar: SessionInputView!
var moreView: IMMoreItemView!
@ -395,7 +396,7 @@ extension SessionController {
// self.bottomViewsStackV.setNeedsDisplay()
// self.bottomViewsStackV.layoutIfNeeded()
// showMoreItems(show: false)
hideAllBottomViews(except: [inputEntrance])
hideAllBottomViews(except: [toolView, inputEntrance])
updateInputEntrance(offsetY: 0, duration: 0, curve: UIView.AnimationCurve.easeOut.rawValue) // 0.3
}

View File

@ -155,7 +155,8 @@ class SessionNavigationView: UIView {
func refreshMoreButtonBadge(){
let config = AppCache.fetchCache(key: CacheKey.chatRedBadgeConfig.rawValue, type: ChatSettingCacheConfig.self) ?? ChatSettingCacheConfig()
moreBadge.isHidden = config.chatBackgroundTapped && config.chatBubbleTapped
// moreBadge.isHidden = config.chatBackgroundTapped && config.chatBubbleTapped
moreBadge.isHidden = true
}
@objc private func notiMoreButtonBadgeChanged(){
@ -207,6 +208,7 @@ class SessionNavigationView: UIView {
moreBadge = {
let v = BadgeView()
v.onlyShowPoint = true
v.isHidden = true
navigationView.addSubview(v)
v.snp.makeConstraints { make in
// make.top.equalTo(naviMoreButton)