聊天详情页面
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "back_black@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "back_black@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/back_black.imageset/back_black@2x.png
vendored
Normal file
After Width: | Height: | Size: 368 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/back_black.imageset/back_black@3x.png
vendored
Normal file
After Width: | Height: | Size: 735 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_back_white.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_back_white@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_back_white@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_back_white.imageset/role_back_white@2x.png
vendored
Normal file
After Width: | Height: | Size: 377 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_back_white.imageset/role_back_white@3x.png
vendored
Normal file
After Width: | Height: | Size: 768 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_close.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_change_close@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_change_close@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_close.imageset/role_chat_change_close@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_close.imageset/role_chat_change_close@3x.png
vendored
Normal file
After Width: | Height: | Size: 6.1 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_normal.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_change_normal@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_change_normal@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.5 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_open.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_change_open@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_change_open@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_open.imageset/role_chat_change_open@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_change_open.imageset/role_chat_change_open@3x.png
vendored
Normal file
After Width: | Height: | Size: 4.8 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_close.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_expand_close@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_expand_close@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_close.imageset/role_chat_expand_close@2x.png
vendored
Normal file
After Width: | Height: | Size: 355 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_close.imageset/role_chat_expand_close@3x.png
vendored
Normal file
After Width: | Height: | Size: 764 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_open.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_expand_open@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_expand_open@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_open.imageset/role_chat_expand_open@2x.png
vendored
Normal file
After Width: | Height: | Size: 336 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_open.imageset/role_chat_expand_open@3x.png
vendored
Normal file
After Width: | Height: | Size: 731 B |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_phone.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_expand_phone@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_expand_phone@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_phone.imageset/role_chat_expand_phone@2x.png
vendored
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_expand_phone.imageset/role_chat_expand_phone@3x.png
vendored
Normal file
After Width: | Height: | Size: 5.0 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_keyboard.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_keyboard@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_keyboard@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_keyboard.imageset/role_chat_keyboard@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_keyboard.imageset/role_chat_keyboard@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.9 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_phone_vip.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_phone_vip@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_phone_vip@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_phone_vip.imageset/role_chat_phone_vip@2x.png
vendored
Normal file
After Width: | Height: | Size: 1017 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_phone_vip.imageset/role_chat_phone_vip@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.1 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_remind_voice.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_remind_voice@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_remind_voice@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_remind_voice.imageset/role_chat_remind_voice@2x.png
vendored
Normal file
After Width: | Height: | Size: 599 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_remind_voice.imageset/role_chat_remind_voice@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_send@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_send@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_send.imageset/role_chat_send@2x.png
vendored
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_send.imageset/role_chat_send@3x.png
vendored
Normal file
After Width: | Height: | Size: 26 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_setting.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_setting@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_setting@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_setting.imageset/role_chat_setting@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_setting.imageset/role_chat_setting@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.8 KiB |
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_vip@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_vip@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_vip.imageset/role_chat_vip@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_vip.imageset/role_chat_vip@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.4 KiB |
22
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_voice.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_voice@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "role_chat_voice@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_voice.imageset/role_chat_voice@2x.png
vendored
Normal file
After Width: | Height: | Size: 994 B |
BIN
Visual_Novel_iOS/Assets.xcassets/Role/role_chat_voice.imageset/role_chat_voice@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.0 KiB |
|
@ -49,6 +49,20 @@ import UIKit
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@objc lazy var centerStackH : UIStackView = {
|
||||||
|
let v = UIStackView()
|
||||||
|
v.spacing = 5
|
||||||
|
v.alignment = .center
|
||||||
|
addSubview(v)
|
||||||
|
v.snp.makeConstraints { make in
|
||||||
|
make.centerY.equalTo(backButton)
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.leading.greaterThanOrEqualTo(leftStackH.snp.trailing)
|
||||||
|
make.trailing.lessThanOrEqualTo(rightStackH.snp.leading)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}()
|
||||||
|
|
||||||
@objc lazy var backButton: UIButton = {
|
@objc lazy var backButton: UIButton = {
|
||||||
let backButton = UIButton(type: .custom)
|
let backButton = UIButton(type: .custom)
|
||||||
leftStackH.addArrangedSubview(backButton)
|
leftStackH.addArrangedSubview(backButton)
|
||||||
|
@ -69,17 +83,31 @@ import UIKit
|
||||||
titleLabel.preferredMaxLayoutWidth = 120
|
titleLabel.preferredMaxLayoutWidth = 120
|
||||||
titleLabel.textAlignment = .center
|
titleLabel.textAlignment = .center
|
||||||
titleLabel.font = .t.ttm//.fredokaOne(size: 18)
|
titleLabel.font = .t.ttm//.fredokaOne(size: 18)
|
||||||
addSubview(titleLabel)
|
// addSubview(titleLabel)
|
||||||
|
centerStackH.addArrangedSubview(titleLabel)
|
||||||
|
|
||||||
titleLabel.snp.makeConstraints { make in
|
// titleLabel.snp.makeConstraints { make in
|
||||||
make.centerY.equalTo(backButton)
|
// make.centerY.equalTo(backButton)
|
||||||
make.centerX.equalToSuperview()
|
// make.centerX.equalToSuperview()
|
||||||
make.leading.greaterThanOrEqualTo(leftStackH.snp.trailing)
|
// make.leading.greaterThanOrEqualTo(leftStackH.snp.trailing)
|
||||||
make.trailing.lessThanOrEqualTo(rightStackH.snp.leading)
|
// make.trailing.lessThanOrEqualTo(rightStackH.snp.leading)
|
||||||
}
|
// }
|
||||||
return titleLabel
|
return titleLabel
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@objc lazy var iconImgView: UIImageView = {
|
||||||
|
let icon = UIImageView()
|
||||||
|
icon.isHidden = true
|
||||||
|
icon.cornerRadius = 10.0
|
||||||
|
icon.snp.makeConstraints { make in
|
||||||
|
make.width.height.equalTo(20)
|
||||||
|
}
|
||||||
|
centerStackH.addArrangedSubview(icon)
|
||||||
|
|
||||||
|
return icon
|
||||||
|
}()
|
||||||
|
|
||||||
|
|
||||||
/// btn 44x44 is ok
|
/// btn 44x44 is ok
|
||||||
@objc lazy var rightStackH: UIStackView = {
|
@objc lazy var rightStackH: UIStackView = {
|
||||||
let rightStackH = UIStackView()
|
let rightStackH = UIStackView()
|
||||||
|
@ -131,6 +159,8 @@ import UIKit
|
||||||
|
|
||||||
//backgroundColor = .c.cbd
|
//backgroundColor = .c.cbd
|
||||||
backgroundColor = .clear
|
backgroundColor = .clear
|
||||||
|
|
||||||
|
iconImgView.backgroundColor = .blue
|
||||||
}
|
}
|
||||||
|
|
||||||
@available(*, unavailable)
|
@available(*, unavailable)
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// InputSafeView.swift
|
||||||
|
// Visual_Novel_iOS
|
||||||
|
//
|
||||||
|
// Created by mh on 2025/10/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class InputSafeView: UIView {
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
// 1. 自身不可见/无交互 → 剪枝
|
||||||
|
if alpha <= 0.01 || isHidden || !isUserInteractionEnabled { return nil }
|
||||||
|
|
||||||
|
// 2. 自身命中(含子视图)→ 正常返回
|
||||||
|
let hit = super.hitTest(point, with: event)
|
||||||
|
if hit != nil { return hit }
|
||||||
|
|
||||||
|
// 3. 自身没命中,继续测**所有子视图(含超框)**
|
||||||
|
for sub in subviews.reversed() {
|
||||||
|
let converted = sub.convert(point, from: self)
|
||||||
|
if sub.bounds.contains(converted) {
|
||||||
|
return sub.hitTest(converted, with: event)
|
||||||
|
} else {
|
||||||
|
if sub is SessionPhoneExpandView {
|
||||||
|
return sub.hitTest(converted, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// InputStackView.swift
|
||||||
|
// Visual_Novel_iOS
|
||||||
|
//
|
||||||
|
// Created by mh on 2025/10/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class InputStackView: UIStackView {
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
// 1. 自身不可见/无交互 → 剪枝
|
||||||
|
if alpha <= 0.01 || isHidden || !isUserInteractionEnabled { return nil }
|
||||||
|
|
||||||
|
// 2. 自身命中(含子视图)→ 正常返回
|
||||||
|
let hit = super.hitTest(point, with: event)
|
||||||
|
if hit != nil { return hit }
|
||||||
|
|
||||||
|
// 3. 自身没命中,继续测**所有子视图(含超框)**
|
||||||
|
for sub in subviews.reversed() {
|
||||||
|
if sub is SessionInputOperateView {
|
||||||
|
let converted = sub.convert(point, from: self)
|
||||||
|
return sub.hitTest(converted, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
//
|
||||||
|
// SessionCoverView.swift
|
||||||
|
// Visual_Novel_iOS
|
||||||
|
//
|
||||||
|
// Created by mh on 2025/10/21.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SessionCoverView: UIView {
|
||||||
|
|
||||||
|
var aiAnswerBlock: (()->Void)? = nil
|
||||||
|
|
||||||
|
lazy var expandBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.setImage(UIImage(named: "role_chat_expand_open"), for: .normal)
|
||||||
|
btn.setImage(UIImage(named: "role_chat_expand_close"), for: .selected)
|
||||||
|
btn.backgroundColor = .clear
|
||||||
|
btn.isUserInteractionEnabled = false
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var lineView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
// view.backgroundColor = UIColor.init(white: 1.0, alpha: 0.2)
|
||||||
|
view.backgroundColor = .red
|
||||||
|
view.cornerRadius = 0.5
|
||||||
|
view.alpha = 0
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var phoneBtn: UIButton = {
|
||||||
|
let btn = UIButton(type: .custom)
|
||||||
|
btn.setImage(UIImage(named: "role_chat_expand_phone"), for: .normal)
|
||||||
|
btn.addTarget(self, action: #selector(phoneBtnClicked), for: .touchDown)
|
||||||
|
btn.alpha = 0
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var emailBtn: 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.alpha = 0
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
lazy var bgview: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .clear
|
||||||
|
view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(bgviewTap)))
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func bgviewTap() {
|
||||||
|
print("bgviewTap-bgviewTap")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func phoneBtnClicked() {
|
||||||
|
self.aiAnswerBlock?()
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func emailBtnClicked() {
|
||||||
|
self.aiAnswerBlock?()
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandStatusChanged(_ expand: Bool) {
|
||||||
|
expandBtn.isSelected = expand
|
||||||
|
self.backgroundColor = expand ? .orange : .blue
|
||||||
|
UIView.animate(withDuration: 0.25) {
|
||||||
|
self.phoneBtn.alpha = expand ? 1 : 0
|
||||||
|
self.emailBtn.alpha = expand ? 1 : 0
|
||||||
|
self.lineView.alpha = expand ? 1 : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupUI() {
|
||||||
|
|
||||||
|
addSubview(expandBtn)
|
||||||
|
addSubview(lineView)
|
||||||
|
addSubview(bgview)
|
||||||
|
addSubview(phoneBtn)
|
||||||
|
addSubview(emailBtn)
|
||||||
|
|
||||||
|
expandBtn.snp.makeConstraints { make in
|
||||||
|
make.bottom.left.right.equalToSuperview()
|
||||||
|
make.height.equalTo(44)
|
||||||
|
}
|
||||||
|
|
||||||
|
lineView.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.height.equalTo(1.0)
|
||||||
|
make.width.equalTo(10)
|
||||||
|
make.bottom.equalTo(expandBtn.snp.top)
|
||||||
|
}
|
||||||
|
|
||||||
|
phoneBtn.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalTo(lineView.snp.top).offset(-17)
|
||||||
|
make.width.height.equalTo(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
emailBtn.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.bottom.equalTo(phoneBtn.snp.top).offset(-20)
|
||||||
|
make.width.height.equalTo(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
bgview.snp.makeConstraints { make in
|
||||||
|
make.left.right.top.equalToSuperview()
|
||||||
|
make.bottom.equalTo(lineView.snp.top)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
//
|
//
|
||||||
|
//
|
||||||
// SessionInputOperateView.swift
|
// SessionInputOperateView.swift
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -23,19 +24,20 @@ enum InputOperateState {
|
||||||
class SessionInputOperateView: UIView {
|
class SessionInputOperateView: UIView {
|
||||||
weak var delegate: SessionInputOperateViewDelegate?
|
weak var delegate: SessionInputOperateViewDelegate?
|
||||||
|
|
||||||
var safeView: UIView!
|
var safeView: InputSafeView!
|
||||||
var giftButton: EPIconPrimaryButton!
|
|
||||||
|
var expandView: SessionPhoneExpandView!
|
||||||
|
|
||||||
var block: UIView!
|
var block: UIView!
|
||||||
var effectViewOnBlock: UIVisualEffectView!
|
var effectViewOnBlock: UIVisualEffectView!
|
||||||
var modeButton: UIButton!
|
var modeButton: UIButton!
|
||||||
var rightStackH: UIStackView!
|
|
||||||
var helpButton: UIButton!
|
|
||||||
var moreButton: UIButton!
|
|
||||||
|
|
||||||
var fakeTextfield: UITextField!
|
|
||||||
var voiceHoldView: UIView!
|
var voiceHoldView: UIView!
|
||||||
|
|
||||||
|
var inputTextView: CLTextView!
|
||||||
|
let minTextViewHeight = 40.0//56.0
|
||||||
|
var sendBtn: UIButton!
|
||||||
|
|
||||||
//var tapInputFieldAction: (() -> Void)?
|
//var tapInputFieldAction: (() -> Void)?
|
||||||
// var voiceHoldAction: ((_ onVoice: Bool) -> Void)?
|
// var voiceHoldAction: ((_ onVoice: Bool) -> Void)?
|
||||||
|
|
||||||
|
@ -45,6 +47,9 @@ class SessionInputOperateView: UIView {
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
self.clipsToBounds = false // 允许子视图绘制到框外
|
||||||
|
self.isUserInteractionEnabled = true
|
||||||
|
|
||||||
setupViews()
|
setupViews()
|
||||||
setupData()
|
setupData()
|
||||||
setupEvent()
|
setupEvent()
|
||||||
|
@ -54,28 +59,47 @@ class SessionInputOperateView: UIView {
|
||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
// 1. 自身不可见/无交互 → 剪枝
|
||||||
|
if alpha <= 0.01 || isHidden || !isUserInteractionEnabled { return nil }
|
||||||
|
|
||||||
|
// 2. 自身命中(含子视图)→ 正常返回
|
||||||
|
let hit = super.hitTest(point, with: event)
|
||||||
|
if hit != nil { return hit }
|
||||||
|
|
||||||
|
// 3. 自身没命中,继续测**所有子视图(含超框)**
|
||||||
|
for sub in subviews.reversed() {
|
||||||
|
let converted = sub.convert(point, from: self)
|
||||||
|
return sub.hitTest(converted, with: event)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
private func setupViews() {
|
private func setupViews() {
|
||||||
safeView = {
|
safeView = {
|
||||||
let v = UIView()
|
let v = InputSafeView()
|
||||||
v.backgroundColor = .clear
|
// v.backgroundColor = .orange
|
||||||
addSubview(v)
|
addSubview(v)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.leading.top.trailing.equalToSuperview()
|
make.left.top.right.equalToSuperview()
|
||||||
// make.bottom.equalToSuperview().offset(-UIWindow.safeAreaBottom*0.5-16)
|
// make.bottom.equalToSuperview().offset(-UIWindow.safeAreaBottom*0.5-16)
|
||||||
make.bottom.equalToSuperview()
|
make.bottom.equalToSuperview()
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
giftButton = {
|
expandView = {
|
||||||
let v = EPIconPrimaryButton(radius: .round, iconSize: .large, iconCode: .giftBorder)
|
let v = SessionPhoneExpandView()
|
||||||
v.addTarget(self, action: #selector(tapGiftButton), for: .touchUpInside)
|
safeView.clipsToBounds = false
|
||||||
|
safeView.isUserInteractionEnabled = true
|
||||||
|
|
||||||
safeView.addSubview(v)
|
safeView.addSubview(v)
|
||||||
|
// v.backgroundColor = .blue
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.leading.equalToSuperview().offset(24)
|
make.left.equalToSuperview().offset(24)
|
||||||
make.top.equalToSuperview().offset(16)
|
make.top.equalToSuperview().inset(16).priority(999)
|
||||||
make.bottom.equalToSuperview().offset(-16)
|
make.bottom.equalToSuperview().inset(16)
|
||||||
make.size.equalTo(v.bgImageSize())
|
make.width.height.equalTo(44)
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
@ -83,13 +107,13 @@ class SessionInputOperateView: UIView {
|
||||||
block = {
|
block = {
|
||||||
let v = UIView()
|
let v = UIView()
|
||||||
v.backgroundColor = .c.cseln
|
v.backgroundColor = .c.cseln
|
||||||
v.layer.cornerRadius = 24
|
v.cornerRadius = 24
|
||||||
v.layer.masksToBounds = true
|
|
||||||
safeView.addSubview(v)
|
safeView.addSubview(v)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.leading.equalTo(giftButton.snp.trailing).offset(12)
|
make.left.equalTo(expandView.snp.right).offset(12)
|
||||||
make.trailing.equalToSuperview().offset(-24)
|
make.right.equalToSuperview().offset(-24)
|
||||||
make.height.equalTo(48)
|
// make.height.equalTo(48)
|
||||||
|
make.top.equalToSuperview().inset(16)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
@ -108,75 +132,51 @@ class SessionInputOperateView: UIView {
|
||||||
modeButton = {
|
modeButton = {
|
||||||
let v = UIButton()
|
let v = UIButton()
|
||||||
block.addSubview(v)
|
block.addSubview(v)
|
||||||
let size = CGSize(width: 20, height: 20)
|
let size = CGSize(width: 26, height: 26)
|
||||||
let image = MWIconFont.image(fromIcon: .voiceMsg, size: size, color: .white)
|
// let image = MWIconFont.image(fromIcon: .voiceMsg, size: size, color: .white)
|
||||||
|
let image = UIImage(named: "role_chat_voice")
|
||||||
v.setImage(image, for: .normal)
|
v.setImage(image, for: .normal)
|
||||||
v.touchAreaInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
v.touchAreaInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
||||||
v.addTarget(self, action: #selector(tapModeButton), for: .touchUpInside)
|
v.addTarget(self, action: #selector(tapModeButton), for: .touchUpInside)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.size.equalTo(size)
|
make.size.equalTo(size)
|
||||||
make.leading.equalToSuperview().offset(16)
|
make.left.equalToSuperview().inset(12)
|
||||||
make.centerY.equalToSuperview()
|
make.bottom.equalToSuperview().inset(11)
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
rightStackH = {
|
sendBtn = {
|
||||||
let v = UIStackView()
|
let btn = UIButton()
|
||||||
v.spacing = 20
|
block.addSubview(btn)
|
||||||
v.alignment = .center
|
btn.setImage(UIImage(named: "role_chat_send"), for: .normal)
|
||||||
|
btn.addTarget(self, action: #selector(tapSendButton), for: .touchUpInside)
|
||||||
|
btn.snp.makeConstraints { make in
|
||||||
|
make.bottom.equalToSuperview().inset(2)
|
||||||
|
make.right.equalToSuperview().inset(3)
|
||||||
|
make.width.equalTo(60)
|
||||||
|
make.height.equalTo(44)
|
||||||
|
}
|
||||||
|
return btn
|
||||||
|
}()
|
||||||
|
|
||||||
|
inputTextView = {
|
||||||
|
let v = CLTextView()
|
||||||
block.addSubview(v)
|
block.addSubview(v)
|
||||||
v.snp.makeConstraints { make in
|
v.placeholder = "Type a message..."
|
||||||
make.trailing.equalToSuperview().offset(-16)
|
v.placeholderTextColor = UIColor.init(white: 1, alpha: 0.4)
|
||||||
make.centerY.equalToSuperview()
|
v.backgroundColor = .clear
|
||||||
}
|
v.limit.maxCharacterNumber = 500
|
||||||
return v
|
|
||||||
}()
|
|
||||||
|
|
||||||
helpButton = {
|
|
||||||
let v = UIButton()
|
|
||||||
rightStackH.addArrangedSubview(v)
|
|
||||||
let size = CGSize(width: 20, height: 20)
|
|
||||||
let image = MWIconFont.image(fromIcon: .prompt, size: size, color: .white)
|
|
||||||
v.setImage(image, for: .normal)
|
|
||||||
v.touchAreaInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
|
||||||
v.addTarget(self, action: #selector(tapHelpButton), for: .touchUpInside)
|
|
||||||
v.snp.makeConstraints { make in
|
|
||||||
make.size.equalTo(size)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}()
|
|
||||||
|
|
||||||
moreButton = {
|
|
||||||
let v = UIButton()
|
|
||||||
rightStackH.addArrangedSubview(v)
|
|
||||||
let size = CGSize(width: 20, height: 20)
|
|
||||||
let image = MWIconFont.image(fromIcon: .add, size: size, color: .white)
|
|
||||||
v.setImage(image, for: .normal)
|
|
||||||
v.touchAreaInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
|
||||||
v.addTarget(self, action: #selector(tapMoreButton), for: .touchUpInside)
|
|
||||||
v.snp.makeConstraints { make in
|
|
||||||
make.size.equalTo(size)
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}()
|
|
||||||
|
|
||||||
fakeTextfield = {
|
|
||||||
let v = UITextField()
|
|
||||||
v.font = .t.tll
|
v.font = .t.tll
|
||||||
v.textColor = .c.ctpn
|
v.textColor = UIColor.white
|
||||||
block.addSubview(v)
|
v.font = UIFont.systemFont(ofSize: 14)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
|
make.left.equalTo(modeButton.snp.right).offset(10)
|
||||||
|
make.right.equalTo(sendBtn.snp.left).offset(-10)
|
||||||
|
make.height.equalTo(minTextViewHeight)
|
||||||
make.centerY.equalToSuperview()
|
make.centerY.equalToSuperview()
|
||||||
make.leading.equalTo(modeButton.snp.trailing).offset(16)
|
make.top.equalToSuperview().inset(4)
|
||||||
make.trailing.equalTo(rightStackH.snp.leading).offset(-16)
|
|
||||||
}
|
}
|
||||||
// v.text = "Chat"
|
|
||||||
v.attributedPlaceholder = "Chat".withAttributes([
|
|
||||||
.font(.t.tll),
|
|
||||||
.textColor(UIColor.c.ctsn),
|
|
||||||
])
|
|
||||||
v.delegate = self
|
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -185,19 +185,26 @@ class SessionInputOperateView: UIView {
|
||||||
block.addSubview(v)
|
block.addSubview(v)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.top.bottom.equalToSuperview()
|
make.top.bottom.equalToSuperview()
|
||||||
make.leading.trailing.equalTo(fakeTextfield)
|
make.left.right.equalTo(inputTextView)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let voiceImgView = UIImageView(image: UIImage(named: "role_chat_remind_voice"))
|
||||||
|
|
||||||
let label = UILabel()
|
let label = UILabel()
|
||||||
label.font = .t.tll
|
label.font = .t.tll
|
||||||
label.textColor = .white
|
label.textColor = .white
|
||||||
label.textAlignment = .center
|
label.textAlignment = .center
|
||||||
label.text = "Hold to Talk"
|
label.text = "Hold to Talk"
|
||||||
v.addSubview(label)
|
|
||||||
label.snp.makeConstraints { make in
|
let stackView = UIStackView(arrangedSubviews: [voiceImgView, label])
|
||||||
make.leading.trailing.equalToSuperview()
|
stackView.spacing = 3
|
||||||
make.centerY.equalToSuperview()
|
stackView.alignment = .center
|
||||||
|
stackView.distribution = .fill
|
||||||
|
v.addSubview(stackView)
|
||||||
|
stackView.snp.makeConstraints { make in
|
||||||
|
make.centerX.centerY.equalToSuperview()
|
||||||
}
|
}
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
@ -207,6 +214,11 @@ class SessionInputOperateView: UIView {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func setupEvent() {
|
private func setupEvent() {
|
||||||
|
|
||||||
|
inputTextView.textPublisher.sink {[weak self] str in
|
||||||
|
self?.fixTextViewHeight()
|
||||||
|
}.store(in: &cancellables)
|
||||||
|
|
||||||
// 添加长按手势
|
// 添加长按手势
|
||||||
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
|
||||||
longPress.minimumPressDuration = 0.5 // 长按时间,默认 0.5 秒
|
longPress.minimumPressDuration = 0.5 // 长按时间,默认 0.5 秒
|
||||||
|
@ -222,22 +234,37 @@ class SessionInputOperateView: UIView {
|
||||||
// MARK: - Helper
|
// MARK: - Helper
|
||||||
|
|
||||||
private func refreshByState(stateOf: InputOperateState) {
|
private func refreshByState(stateOf: InputOperateState) {
|
||||||
let size = CGSize(width: 20, height: 20)
|
// let size = CGSize(width: 26, height: 26)
|
||||||
switch stateOf {
|
switch stateOf {
|
||||||
case .text:
|
case .text:
|
||||||
fakeTextfield.isHidden = false
|
// fakeTextfield.isHidden = false
|
||||||
|
inputTextView.isHidden = false
|
||||||
voiceHoldView.isHidden = true
|
voiceHoldView.isHidden = true
|
||||||
let image = MWIconFont.image(fromIcon: .voiceMsg, size: size, color: .white)
|
// let image = MWIconFont.image(fromIcon:4 .voiceMsg, size: size, color: .white)
|
||||||
|
let image = UIImage(named: "role_chat_voice")
|
||||||
modeButton.setImage(image, for: .normal)
|
modeButton.setImage(image, for: .normal)
|
||||||
case .voice:
|
case .voice:
|
||||||
fakeTextfield.isHidden = true
|
// fakeTextfield.isHidden = true
|
||||||
|
inputTextView.isHidden = true
|
||||||
voiceHoldView.isHidden = false
|
voiceHoldView.isHidden = false
|
||||||
let image = MWIconFont.image(fromIcon: .iconKeyboard, size: size, color: .white)
|
let image = UIImage(named: "role_chat_keyboard")
|
||||||
modeButton.setImage(image, for: .normal)
|
modeButton.setImage(image, for: .normal)
|
||||||
AudioRecordTool.audioAuth()
|
AudioRecordTool.audioAuth()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func fixTextViewHeight(){
|
||||||
|
if inputTextView.contentSize.height > minTextViewHeight {
|
||||||
|
inputTextView.snp.updateConstraints { make in
|
||||||
|
make.height.equalTo(max(inputTextView.contentSize.height, 48)) // 73
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
inputTextView.snp.updateConstraints { make in
|
||||||
|
make.height.equalTo(inputTextView.contentSize.height) //minTextViewHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Action
|
// MARK: - Action
|
||||||
|
|
||||||
@objc private func tapGiftButton() {
|
@objc private func tapGiftButton() {
|
||||||
|
@ -253,6 +280,10 @@ class SessionInputOperateView: UIView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func tapSendButton() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@objc private func tapMoreButton() {
|
@objc private func tapMoreButton() {
|
||||||
delegate?.operateTapMoreAction()
|
delegate?.operateTapMoreAction()
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,101 @@
|
||||||
|
//
|
||||||
|
// SessionPhoneExpandView.swift
|
||||||
|
// Visual_Novel_iOS
|
||||||
|
//
|
||||||
|
// Created by mh on 2025/10/20.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class SessionPhoneExpandView: UIView {
|
||||||
|
|
||||||
|
lazy var expandView: SessionCoverView = {
|
||||||
|
let view = SessionCoverView()
|
||||||
|
// view.backgroundColor = UIColor.init(white: 0, alpha: 0.4)
|
||||||
|
view.backgroundColor = .blue
|
||||||
|
view.cornerRadius = 22.0
|
||||||
|
view.aiAnswerBlock = { [weak self] in
|
||||||
|
self?.tapClicked()
|
||||||
|
}
|
||||||
|
let tap = UITapGestureRecognizer(target: self, action: #selector(tapClicked))
|
||||||
|
view.addGestureRecognizer(tap)
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
var expand: Bool = false
|
||||||
|
|
||||||
|
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||||
|
// 1. 自身不可见/无交互 → 剪枝
|
||||||
|
if alpha <= 0.01 || isHidden || !isUserInteractionEnabled { return nil }
|
||||||
|
|
||||||
|
// 2. 自身命中(含子视图)→ 正常返回
|
||||||
|
let hit = super.hitTest(point, with: event)
|
||||||
|
if hit != nil { return hit }
|
||||||
|
|
||||||
|
// 3. 自身没命中,继续测**所有子视图(含超框)**
|
||||||
|
for sub in subviews.reversed() {
|
||||||
|
let converted = sub.convert(point, from: self)
|
||||||
|
if sub.bounds.contains(converted) {
|
||||||
|
return sub.hitTest(converted, with: event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
self.clipsToBounds = false // 允许子视图绘制到框外
|
||||||
|
setupViews()
|
||||||
|
setupData()
|
||||||
|
setupEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc func tapClicked() {
|
||||||
|
print("tapClicked 被调用 - expand: \(expand)")
|
||||||
|
self.expandView.expandStatusChanged(!self.expand)
|
||||||
|
UIView.animate(withDuration: 0.25) {
|
||||||
|
// 同时调整自身和expandView的高度
|
||||||
|
self.expandView.snp.updateConstraints { make in
|
||||||
|
make.height.equalTo(self.expand ? 44 : 152)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.layoutIfNeeded()
|
||||||
|
} completion: { finish in
|
||||||
|
if finish {
|
||||||
|
self.expand = !self.expand
|
||||||
|
|
||||||
|
print("动画完成 - 新的 expand 状态: \(self.expand)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupViews() {
|
||||||
|
|
||||||
|
addSubview(expandView)
|
||||||
|
|
||||||
|
expandView.snp.makeConstraints { make in
|
||||||
|
make.height.width.equalTo(44.0)
|
||||||
|
make.bottom.equalToSuperview()
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置自身的高度约束,允许动态调整
|
||||||
|
self.snp.makeConstraints { make in
|
||||||
|
make.height.equalTo(44.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupData() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func setupEvent() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,8 +14,9 @@ extension SessionController {
|
||||||
func setupInputView() {
|
func setupInputView() {
|
||||||
|
|
||||||
bottomViewsStackV = {
|
bottomViewsStackV = {
|
||||||
let v = UIStackView()
|
let v = InputStackView()
|
||||||
v.axis = .vertical
|
v.axis = .vertical
|
||||||
|
v.clipsToBounds = false // 允许子视图绘制到框外,支持超框点击
|
||||||
view.addSubview(v)
|
view.addSubview(v)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.leading.trailing.equalToSuperview()
|
make.leading.trailing.equalToSuperview()
|
||||||
|
@ -97,7 +98,7 @@ extension SessionController {
|
||||||
}
|
}
|
||||||
|
|
||||||
inputBar.textView.textPublisher.sink {[weak self] string in
|
inputBar.textView.textPublisher.sink {[weak self] string in
|
||||||
self?.inputEntrance.fakeTextfield.text = string
|
self?.inputEntrance.inputTextView.text = string
|
||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ class SessionController: CLBaseViewController {
|
||||||
// var headView: SessionAIHeadView!
|
// var headView: SessionAIHeadView!
|
||||||
|
|
||||||
// MARK: BottomViews
|
// MARK: BottomViews
|
||||||
var bottomViewsStackV : UIStackView!
|
var bottomViewsStackV : InputStackView!
|
||||||
var inputEntrance: SessionInputOperateView!
|
var inputEntrance: SessionInputOperateView!
|
||||||
var inputBar: SessionInputView!
|
var inputBar: SessionInputView!
|
||||||
var moreView: IMMoreItemView!
|
var moreView: IMMoreItemView!
|
||||||
|
@ -23,6 +23,7 @@ class SessionController: CLBaseViewController {
|
||||||
var voiceHoldView: IMVoiceHoldView!
|
var voiceHoldView: IMVoiceHoldView!
|
||||||
var pureBgOperateView:SessionPureBgOperateView!
|
var pureBgOperateView:SessionPureBgOperateView!
|
||||||
|
|
||||||
|
var titleStr: String? = nil
|
||||||
|
|
||||||
// 长按菜单响应的cell
|
// 长按菜单响应的cell
|
||||||
var menuCell: SessionCell?
|
var menuCell: SessionCell?
|
||||||
|
@ -78,9 +79,10 @@ class SessionController: CLBaseViewController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
convenience init(conversationId: String) {
|
convenience init(conversationId: String, title: String? = "") {
|
||||||
self.init()
|
self.init()
|
||||||
self.conversationId = conversationId
|
self.conversationId = conversationId
|
||||||
|
self.titleStr = title
|
||||||
conversation = V2NIMConversation()
|
conversation = V2NIMConversation()
|
||||||
|
|
||||||
let stings = conversationId.components(separatedBy: "|")
|
let stings = conversationId.components(separatedBy: "|")
|
||||||
|
@ -149,11 +151,14 @@ class SessionController: CLBaseViewController {
|
||||||
|
|
||||||
extension SessionController {
|
extension SessionController {
|
||||||
func setupUI() {
|
func setupUI() {
|
||||||
view.clipsToBounds = true
|
view.clipsToBounds = false // 允许子视图绘制到框外,支持超框点击
|
||||||
navigationView.backgroundColor = .clear
|
navigationView.backgroundColor = .clear
|
||||||
|
navigationView.backButton.setImage(nil, for: .normal)
|
||||||
|
|
||||||
sessionNavigationView = {
|
sessionNavigationView = {
|
||||||
let v = SessionNavigationView()
|
let v = SessionNavigationView()
|
||||||
|
v.navigationView.title = self.titleStr ?? ""
|
||||||
|
v.navigationView.iconImgView.isHidden = false
|
||||||
view.addSubview(v)
|
view.addSubview(v)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.top.leading.trailing.equalToSuperview()
|
make.top.leading.trailing.equalToSuperview()
|
||||||
|
@ -368,9 +373,9 @@ extension SessionController {
|
||||||
}
|
}
|
||||||
|
|
||||||
func doKeyboardShowActions(){
|
func doKeyboardShowActions(){
|
||||||
self.inputBar.isHidden = false
|
self.inputBar.isHidden = true
|
||||||
|
|
||||||
self.inputEntrance.isHidden = true
|
self.inputEntrance.isHidden = false
|
||||||
// self.moreView.isHidden = true
|
// self.moreView.isHidden = true
|
||||||
//showMoreItems(show: false)
|
//showMoreItems(show: false)
|
||||||
}
|
}
|
||||||
|
|
|
@ -180,11 +180,13 @@ class SessionNavigationView: UIView {
|
||||||
private func setupViews() {
|
private func setupViews() {
|
||||||
addNavigationView()
|
addNavigationView()
|
||||||
navigationView.bgView.alpha = 0
|
navigationView.bgView.alpha = 0
|
||||||
|
navigationView.backButton.setImage(UIImage(named: "role_back_white"), for: .normal)
|
||||||
|
|
||||||
navigationView.paddingRightForRightStack = 2
|
navigationView.paddingRightForRightStack = 2
|
||||||
likeView = {
|
likeView = {
|
||||||
let v = HeartLikeCountView(viewSize: .xl)
|
let v = HeartLikeCountView(viewSize: .xl)
|
||||||
v.purIconStyle()
|
v.purIconStyle()
|
||||||
|
v.isHidden = true
|
||||||
navigationView.rightStackH.addArrangedSubview(v)
|
navigationView.rightStackH.addArrangedSubview(v)
|
||||||
return v
|
return v
|
||||||
}()
|
}()
|
||||||
|
@ -192,7 +194,10 @@ class SessionNavigationView: UIView {
|
||||||
naviMoreButton = {
|
naviMoreButton = {
|
||||||
let v = EPIconGhostButton(radius: .none, iconSize: .medium, iconCode: .chatroomMore)
|
let v = EPIconGhostButton(radius: .none, iconSize: .medium, iconCode: .chatroomMore)
|
||||||
navigationView.rightStackH.addArrangedSubview(v)
|
navigationView.rightStackH.addArrangedSubview(v)
|
||||||
|
v.setBackgroundImage(nil, for: .highlighted)
|
||||||
|
v.setImage(UIImage(named: "role_chat_setting"), for: .normal)
|
||||||
|
v.setImage(UIImage(named: "role_chat_setting"), for: .selected)
|
||||||
|
v.setImage(UIImage(named: "role_chat_setting"), for: .highlighted)
|
||||||
v.snp.makeConstraints { make in
|
v.snp.makeConstraints { make in
|
||||||
make.size.equalTo(CGSize(width: 52, height: 44))
|
make.size.equalTo(CGSize(width: 52, height: 44))
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,7 +122,8 @@ extension RolesRootPageView: UICollectionViewDelegate, UICollectionViewDataSourc
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
let sessionId = "439217670979585@r@t"
|
||||||
|
AppRouter.goChatVC(conversationId: sessionId, title: "Character · 18", complete: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectionView(_ collectionView: UICollectionView,
|
func collectionView(_ collectionView: UICollectionView,
|
||||||
|
|
|
@ -35,11 +35,11 @@ extension AppRouter{
|
||||||
complete?()
|
complete?()
|
||||||
}
|
}
|
||||||
|
|
||||||
static func goChatVC(conversationId: String?, complete: (() -> Void)? = nil) {
|
static func goChatVC(conversationId: String?, title: String? = nil, complete: (() -> Void)? = nil) {
|
||||||
guard UserCore.shared.checkUserLoginIfNotPushUserToLogin() else{return}
|
guard UserCore.shared.checkUserLoginIfNotPushUserToLogin() else{return}
|
||||||
|
|
||||||
guard let sessionId = conversationId else{return}
|
guard let sessionId = conversationId else{return}
|
||||||
let vc = SessionController(conversationId: sessionId)
|
let vc = SessionController(conversationId: sessionId, title: title)
|
||||||
let nvc = UIWindow.getTopViewController(base: UIWindow.applicationKey?.rootViewController)?.navigationController
|
let nvc = UIWindow.getTopViewController(base: UIWindow.applicationKey?.rootViewController)?.navigationController
|
||||||
nvc?.pushViewController(vc, animated: true)
|
nvc?.pushViewController(vc, animated: true)
|
||||||
complete?()
|
complete?()
|
||||||
|
|