Browse Source

feat(agent_view): 添加聊天区域点击收起键盘功能并优化焦点控制

- 新增 FocusNode 用于控制 TextField 焦点
- 点击聊天区域时收起键盘并收起扩展选项
- 切换输入模式时自动管理焦点状态
weicu
lxm 12 months ago
parent
commit
dbc1602f96
  1. 200
      lib/modules/agent/views/agent_view.dart

200
lib/modules/agent/views/agent_view.dart

@ -25,6 +25,9 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
late MessageActionController messageActionController; late MessageActionController messageActionController;
double _keyboardHeight = 0; double _keyboardHeight = 0;
// 👇 新增:用于控制 TextField 焦点
final FocusNode _textFocusNode = FocusNode();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -35,6 +38,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
@override @override
void dispose() { void dispose() {
_textFocusNode.dispose();
WidgetsBinding.instance.removeObserver(this); WidgetsBinding.instance.removeObserver(this);
super.dispose(); super.dispose();
} }
@ -286,65 +290,102 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
children: [ children: [
// 聊天内容 // 聊天内容
Expanded( Expanded(
child: Obx(() { child: GestureDetector(
return Align( onTap: () {
alignment: Alignment.topCenter, // 点击聊天区域时,收起键盘并切换工具显示状态
child: ListView.builder( FocusScope.of(context).unfocus();
controller: controller.scrollController, if (controller.isPlusOptionsExpanded.value) {
shrinkWrap: true, controller.collapsePlusOptions();
reverse: true, }
padding: EdgeInsets.symmetric( },
vertical: 16.h, horizontal: 16.w), child: Obx(() {
itemCount: controller.messages.length + return Align(
(controller.hasMoreMessages.value ? 1 : 0), alignment: Alignment.topCenter,
itemBuilder: (context, index) { child: ListView.builder(
// 如果是最后一项且有更多消息,显示加载指示器 controller: controller.scrollController,
if (index == controller.messages.length && shrinkWrap: true,
controller.hasMoreMessages.value) { reverse: true,
return _buildLoadMoreIndicator(); padding: EdgeInsets.symmetric(
} vertical: 16.h, horizontal: 16.w),
itemCount: controller.messages.length +
(controller.hasMoreMessages.value ? 1 : 0),
itemBuilder: (context, index) {
// 如果是最后一项且有更多消息,显示加载指示器
if (index == controller.messages.length &&
controller.hasMoreMessages.value) {
return _buildLoadMoreIndicator();
}
final message = controller final message = controller
.messages[controller.messages.length - 1 - index]; .messages[controller.messages.length - 1 - index];
return index == 0 return index == 0
? IntroStepBuilder( ? IntroStepBuilder(
group: 'ai', group: 'ai',
order: 4, order: 4,
getOverlayPosition: getOverlayPosition:
IntroOverlayBuilder.overlayPosition, IntroOverlayBuilder.overlayPosition,
overlayBuilder: (params) { overlayBuilder: (params) {
return IntroOverlayBuilder.aiBuilder( return IntroOverlayBuilder.aiBuilder(
params, params,
controller.noviceGuideService.aiStepsList[ controller.noviceGuideService.aiStepsList[
controller controller.noviceGuideService
.noviceGuideService.aiStep]['end'], .aiStep]['end'],
onFinish: () async { onFinish: () async {
if (controller.noviceGuideService.aiStep < if (controller
5) { .noviceGuideService.aiStep <
await Future.delayed( 5) {
const Duration(milliseconds: 300)); await Future.delayed(const Duration(
controller.noviceGuideService.aiStep++; milliseconds: 300));
Intro.of(Get.context!) controller
.start(group: 'ai', reset: true); .noviceGuideService.aiStep++;
} Intro.of(Get.context!)
}, .start(group: 'ai', reset: true);
onTap: () async { }
params.onFinish(); },
if (controller.noviceGuideService.aiStep < onTap: () async {
5) { params.onFinish();
await Future.delayed( if (controller
const Duration(milliseconds: 300)); .noviceGuideService.aiStep <
controller.noviceGuideService.aiStep++; 5) {
Intro.of(Get.context!) await Future.delayed(const Duration(
.start(group: 'ai', reset: true); milliseconds: 300));
} controller
}, .noviceGuideService.aiStep++;
); Intro.of(Get.context!)
}, .start(group: 'ai', reset: true);
builder: }
(BuildContext context, GlobalKey key) => },
Padding( );
key: key, },
builder:
(BuildContext context, GlobalKey key) =>
Padding(
key: key,
padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector(
onLongPressStart:
(LongPressStartDetails details) {
messageActionController
.onLongPressMessage(context,
isDarkMode, details, message);
},
child: MessageBubble(
sessionid: message.sessionid,
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
functionCalls: message.functionCalls,
isToolCallsExpanded:
message.isToolCallsExpanded,
),
),
),
)
: Padding(
padding: EdgeInsets.only(bottom: 8.h), padding: EdgeInsets.only(bottom: 8.h),
child: GestureDetector( child: GestureDetector(
onLongPressStart: onLongPressStart:
@ -367,35 +408,12 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
message.isToolCallsExpanded, message.isToolCallsExpanded,
), ),
), ),
), );
) },
: Padding( ),
padding: EdgeInsets.only(bottom: 8.h), );
child: GestureDetector( }),
onLongPressStart: ),
(LongPressStartDetails details) {
messageActionController.onLongPressMessage(
context, isDarkMode, details, message);
},
child: MessageBubble(
sessionid: message.sessionid,
isUser: message.isUser,
message: message.text,
timestamp: message.timestamp,
isRecognizing: message.isRecognizing,
hasImage: message.hasImage,
imagePath: message.imagePath,
meta: message.meta,
functionCalls: message.functionCalls,
isToolCallsExpanded:
message.isToolCallsExpanded,
),
),
);
},
),
);
}),
), ),
// 输入区域 // 输入区域
@ -553,6 +571,13 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
controller.toggleInputMode(); controller.toggleInputMode();
// 切换模式时,确保焦点正确
if (!isTextMode) {
// 切换到语音模式时,请求焦点
Future.delayed(Duration.zero, () {
_textFocusNode.requestFocus();
});
}
}, },
child: Container( child: Container(
width: 44.w, width: 44.w,
@ -652,6 +677,7 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
controller.isImageInputActive.value; controller.isImageInputActive.value;
return TextField( return TextField(
focusNode: _textFocusNode, // 绑定焦点节点
controller: controller.textController, controller: controller.textController,
maxLines: isImageActive maxLines: isImageActive
? 3 ? 3

Loading…
Cancel
Save