|
|
|
@ -4,6 +4,7 @@ import 'dart:math'; |
|
|
|
import 'package:flutter/material.dart'; |
|
|
|
import 'package:flutter_intro/flutter_intro.dart'; |
|
|
|
import 'package:get/get.dart'; |
|
|
|
import '../../../core/widgets/intro_overlay_builder.dart'; |
|
|
|
import '../controllers/agent_controller.dart'; |
|
|
|
import '../controllers/message_action_controller.dart'; |
|
|
|
import 'message_bubble.dart'; |
|
|
|
@ -197,47 +198,79 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver { |
|
|
|
children: [ |
|
|
|
// 聊天内容 |
|
|
|
Expanded( |
|
|
|
child: Obx( |
|
|
|
() => Align( |
|
|
|
alignment: Alignment.topCenter, |
|
|
|
child: ListView.builder( |
|
|
|
controller: controller.scrollController, |
|
|
|
shrinkWrap: true, |
|
|
|
reverse: true, |
|
|
|
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 |
|
|
|
.messages[controller.messages.length - 1 - index]; |
|
|
|
return Padding( |
|
|
|
padding: EdgeInsets.only(bottom: 8.h), |
|
|
|
child: GestureDetector( |
|
|
|
onLongPressStart: |
|
|
|
(LongPressStartDetails details) { |
|
|
|
messageActionController.onLongPressMessage( |
|
|
|
context, isDarkMode, details, message); |
|
|
|
}, |
|
|
|
child: MessageBubble( |
|
|
|
isUser: message.isUser, |
|
|
|
message: message.text, |
|
|
|
timestamp: message.timestamp, |
|
|
|
isRecognizing: message.isRecognizing, |
|
|
|
hasImage: message.hasImage, |
|
|
|
imagePath: message.imagePath, |
|
|
|
meta: message.meta, |
|
|
|
), |
|
|
|
)); |
|
|
|
child: Stack( |
|
|
|
children: [ |
|
|
|
IntroStepBuilder( |
|
|
|
group: 'ai', |
|
|
|
order: 3, |
|
|
|
overlayBuilder: (params) { |
|
|
|
return IntroOverlayBuilder.aiBuilder( |
|
|
|
params, |
|
|
|
controller.noviceGuideService.aiStepsList[ |
|
|
|
controller.noviceGuideService.aiStep]['end'], |
|
|
|
onFinish: () async { |
|
|
|
if (controller.noviceGuideService.aiStep < 5) { |
|
|
|
await Future.delayed( |
|
|
|
const Duration(milliseconds: 300)); |
|
|
|
controller.noviceGuideService.aiStep++; |
|
|
|
Intro.of(Get.context!) |
|
|
|
.start(group: 'ai', reset: true); |
|
|
|
} |
|
|
|
}, |
|
|
|
); |
|
|
|
}, |
|
|
|
builder: (BuildContext context, GlobalKey key) => Align( |
|
|
|
alignment: Alignment.bottomCenter, |
|
|
|
child: SizedBox( |
|
|
|
key: key, |
|
|
|
width: double.infinity, |
|
|
|
height: 430.w, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
Obx( |
|
|
|
() => Align( |
|
|
|
alignment: Alignment.topCenter, |
|
|
|
child: ListView.builder( |
|
|
|
controller: controller.scrollController, |
|
|
|
shrinkWrap: true, |
|
|
|
reverse: true, |
|
|
|
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.messages[ |
|
|
|
controller.messages.length - 1 - index]; |
|
|
|
return Padding( |
|
|
|
padding: EdgeInsets.only(bottom: 8.h), |
|
|
|
child: GestureDetector( |
|
|
|
onLongPressStart: |
|
|
|
(LongPressStartDetails details) { |
|
|
|
messageActionController.onLongPressMessage( |
|
|
|
context, isDarkMode, details, message); |
|
|
|
}, |
|
|
|
child: MessageBubble( |
|
|
|
isUser: message.isUser, |
|
|
|
message: message.text, |
|
|
|
timestamp: message.timestamp, |
|
|
|
isRecognizing: message.isRecognizing, |
|
|
|
hasImage: message.hasImage, |
|
|
|
imagePath: message.imagePath, |
|
|
|
meta: message.meta, |
|
|
|
), |
|
|
|
)); |
|
|
|
}, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
|
|
|
|
@ -392,7 +425,23 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver { |
|
|
|
return IntroStepBuilder( |
|
|
|
group: 'ai', |
|
|
|
order: 1, |
|
|
|
text: '对小言说“今天天气怎么样”', |
|
|
|
overlayBuilder: (params) { |
|
|
|
return IntroOverlayBuilder.aiBuilder( |
|
|
|
params, |
|
|
|
controller.noviceGuideService |
|
|
|
.aiStepsList[ |
|
|
|
controller.noviceGuideService |
|
|
|
.aiStep]['start'], |
|
|
|
onNext: () { |
|
|
|
controller.textController.text = |
|
|
|
controller.noviceGuideService |
|
|
|
.aiStepsList[ |
|
|
|
controller |
|
|
|
.noviceGuideService |
|
|
|
.aiStep]['input']; |
|
|
|
}, |
|
|
|
); |
|
|
|
}, |
|
|
|
onWidgetLoad: () { |
|
|
|
controller.noviceGuideService |
|
|
|
.startAIGuide(); |
|
|
|
@ -630,111 +679,143 @@ class _AgentViewState extends State<AgentView> with WidgetsBindingObserver { |
|
|
|
SizedBox(width: 8.w), |
|
|
|
|
|
|
|
// 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮 |
|
|
|
Obx(() { |
|
|
|
final hasText = controller.hasTextInput.value; |
|
|
|
final hasImage = |
|
|
|
controller.isImageInputActive.value; |
|
|
|
final shouldShowSend = hasText || hasImage; |
|
|
|
final shouldShowExpand = |
|
|
|
controller.shouldShowExpandButton.value; |
|
|
|
Builder(builder: (context) { |
|
|
|
StepWidgetParams? widgetParams; |
|
|
|
return IntroStepBuilder( |
|
|
|
group: 'ai', |
|
|
|
order: 2, |
|
|
|
onHighlightWidgetTap: () { |
|
|
|
widgetParams!.onNext!(); |
|
|
|
controller.sendMessage(); |
|
|
|
}, |
|
|
|
overlayBuilder: (params) { |
|
|
|
widgetParams = params; |
|
|
|
return IntroOverlayBuilder.aiBuilder( |
|
|
|
params, |
|
|
|
'点击发送,${controller.noviceGuideService.aiStepsList[controller.noviceGuideService.aiStep]['send']}', |
|
|
|
alignment: false, |
|
|
|
); |
|
|
|
}, |
|
|
|
builder: |
|
|
|
(BuildContext context, GlobalKey key) => |
|
|
|
Obx( |
|
|
|
key: key, |
|
|
|
() { |
|
|
|
final hasText = |
|
|
|
controller.hasTextInput.value; |
|
|
|
final hasImage = |
|
|
|
controller.isImageInputActive.value; |
|
|
|
final shouldShowSend = |
|
|
|
hasText || hasImage; |
|
|
|
final shouldShowExpand = controller |
|
|
|
.shouldShowExpandButton.value; |
|
|
|
|
|
|
|
if (shouldShowSend) { |
|
|
|
// 显示发送按钮(可能还有展开按钮) |
|
|
|
return Column( |
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
// 展开按钮(在发送按钮上方) |
|
|
|
if (shouldShowExpand && hasText) |
|
|
|
Padding( |
|
|
|
padding: |
|
|
|
EdgeInsets.only(bottom: 40.h), |
|
|
|
child: GestureDetector( |
|
|
|
if (shouldShowSend) { |
|
|
|
// 显示发送按钮(可能还有展开按钮) |
|
|
|
return Column( |
|
|
|
mainAxisSize: MainAxisSize.min, |
|
|
|
children: [ |
|
|
|
// 展开按钮(在发送按钮上方) |
|
|
|
if (shouldShowExpand && hasText) |
|
|
|
Padding( |
|
|
|
padding: EdgeInsets.only( |
|
|
|
bottom: 40.h), |
|
|
|
child: GestureDetector( |
|
|
|
onTap: () { |
|
|
|
// 打开底部弹出编辑模式 |
|
|
|
controller |
|
|
|
.openFullScreenEdit(); |
|
|
|
showModalBottomSheet( |
|
|
|
context: context, |
|
|
|
isScrollControlled: true, |
|
|
|
backgroundColor: |
|
|
|
Colors.transparent, |
|
|
|
builder: (context) => |
|
|
|
const FullScreenEditor(), |
|
|
|
).then((_) { |
|
|
|
// 弹出页关闭时的回调 |
|
|
|
controller |
|
|
|
.closeFullScreenEdit(); |
|
|
|
}); |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
width: 32.w, |
|
|
|
height: 32.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode |
|
|
|
? Colors.grey[700] |
|
|
|
: Colors.grey[300], |
|
|
|
borderRadius: |
|
|
|
BorderRadius.circular( |
|
|
|
16.w), |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
Icons.expand_less, |
|
|
|
color: isDarkMode |
|
|
|
? Colors.grey[300] |
|
|
|
: Colors.grey[700], |
|
|
|
size: 20.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
// 发送按钮 |
|
|
|
GestureDetector( |
|
|
|
onTap: () => |
|
|
|
controller.sendMessage(), |
|
|
|
child: Container( |
|
|
|
width: 44.w, |
|
|
|
height: 44.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: primaryColor, |
|
|
|
borderRadius: |
|
|
|
BorderRadius.circular( |
|
|
|
22.w), |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
Icons.send, |
|
|
|
color: Colors.white, |
|
|
|
size: 22.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
} else { |
|
|
|
// 显示'+'选择按钮 |
|
|
|
return Obx(() { |
|
|
|
final isExpanded = controller |
|
|
|
.isPlusOptionsExpanded.value; |
|
|
|
return GestureDetector( |
|
|
|
onTap: () { |
|
|
|
// 打开底部弹出编辑模式 |
|
|
|
controller.openFullScreenEdit(); |
|
|
|
showModalBottomSheet( |
|
|
|
context: context, |
|
|
|
isScrollControlled: true, |
|
|
|
backgroundColor: |
|
|
|
Colors.transparent, |
|
|
|
builder: (context) => |
|
|
|
const FullScreenEditor(), |
|
|
|
).then((_) { |
|
|
|
// 弹出页关闭时的回调 |
|
|
|
controller |
|
|
|
.closeFullScreenEdit(); |
|
|
|
}); |
|
|
|
controller.togglePlusOptions(); |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
width: 32.w, |
|
|
|
height: 32.w, |
|
|
|
width: 44.w, |
|
|
|
height: 44.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode |
|
|
|
? Colors.grey[700] |
|
|
|
: Colors.grey[300], |
|
|
|
? Colors.grey[800] |
|
|
|
: Colors.grey[200], |
|
|
|
borderRadius: |
|
|
|
BorderRadius.circular(16.w), |
|
|
|
BorderRadius.circular(22.w), |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
Icons.expand_less, |
|
|
|
isExpanded |
|
|
|
? Icons.close |
|
|
|
: Icons.add, |
|
|
|
color: isDarkMode |
|
|
|
? Colors.grey[300] |
|
|
|
: Colors.grey[700], |
|
|
|
size: 20.sp, |
|
|
|
size: 22.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
// 发送按钮 |
|
|
|
GestureDetector( |
|
|
|
onTap: () => controller.sendMessage(), |
|
|
|
child: Container( |
|
|
|
width: 44.w, |
|
|
|
height: 44.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: primaryColor, |
|
|
|
borderRadius: |
|
|
|
BorderRadius.circular(22.w), |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
Icons.send, |
|
|
|
color: Colors.white, |
|
|
|
size: 22.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
); |
|
|
|
} else { |
|
|
|
// 显示'+'选择按钮 |
|
|
|
return Obx(() { |
|
|
|
final isExpanded = |
|
|
|
controller.isPlusOptionsExpanded.value; |
|
|
|
return GestureDetector( |
|
|
|
onTap: () { |
|
|
|
controller.togglePlusOptions(); |
|
|
|
}, |
|
|
|
child: Container( |
|
|
|
width: 44.w, |
|
|
|
height: 44.w, |
|
|
|
decoration: BoxDecoration( |
|
|
|
color: isDarkMode |
|
|
|
? Colors.grey[800] |
|
|
|
: Colors.grey[200], |
|
|
|
borderRadius: |
|
|
|
BorderRadius.circular(22.w), |
|
|
|
), |
|
|
|
child: Icon( |
|
|
|
isExpanded ? Icons.close : Icons.add, |
|
|
|
color: isDarkMode |
|
|
|
? Colors.grey[300] |
|
|
|
: Colors.grey[700], |
|
|
|
size: 22.sp, |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
); |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
), |
|
|
|
); |
|
|
|
}), |
|
|
|
], |
|
|
|
), |
|
|
|
|