Browse Source

feat(agent): 添加图片选项展开状态和键盘可见性控制

新增图片选择选项的展开/收缩功能,并添加键盘可见性状态管理
当键盘显示时自动收缩图片选项,避免界面重叠
newdev_shunjiawei
lxm 1 year ago
parent
commit
af6e7e7240
  1. 1
      .gitignore
  2. 58
      lib/modules/agent/controllers/agent_controller.dart
  3. 261
      lib/modules/agent/views/agent_view.dart

1
.gitignore

@ -222,3 +222,4 @@ fastlane/Preview.html
fastlane/screenshots/**/*.png fastlane/screenshots/**/*.png
fastlane/test_output fastlane/test_output
fastlane/readme.md fastlane/readme.md
.trae/rules/project_rules.md

58
lib/modules/agent/controllers/agent_controller.dart

@ -75,6 +75,12 @@ class AgentController extends GetxController {
final isFullScreenEditMode = false.obs; // 是否处于全屏编辑模式 final isFullScreenEditMode = false.obs; // 是否处于全屏编辑模式
final shouldShowExpandButton = false.obs; // 是否显示展开按钮 final shouldShowExpandButton = false.obs; // 是否显示展开按钮
// 图片选择选项展开状态
final isImageOptionsExpanded = false.obs; // 是否展开图片选择选项
// 键盘状态
final isKeyboardVisible = false.obs; // 键盘是否可见
// 流事件订阅 // 流事件订阅
StreamSubscription<AgentServiceEvent>? _eventSubscription; StreamSubscription<AgentServiceEvent>? _eventSubscription;
@ -898,6 +904,58 @@ class AgentController extends GetxController {
isImageInputActive.value = false; isImageInputActive.value = false;
} }
// 切换图片选项展开状态
void toggleImageOptions() {
if (isImageOptionsExpanded.value) {
// 如果当前已展开,则收缩
collapseImageOptions();
} else {
// 如果当前未展开,则展开并关闭键盘
if (isKeyboardVisible.value) {
hideKeyboard();
// 延迟展开选项,等待键盘完全关闭
Future.delayed(const Duration(milliseconds: 200), () {
// 再次检查键盘状态,确保键盘已关闭
if (!isKeyboardVisible.value) {
isImageOptionsExpanded.value = true;
} else {
// 如果键盘还没关闭,再等一会儿
Future.delayed(const Duration(milliseconds: 100), () {
if (!isKeyboardVisible.value) {
isImageOptionsExpanded.value = true;
}
});
}
});
} else {
// 键盘未显示,直接展开
isImageOptionsExpanded.value = true;
}
}
}
// 收缩图片选项
void collapseImageOptions() {
isImageOptionsExpanded.value = false;
}
// 更新键盘可见状态
void updateKeyboardVisibility(bool isVisible) {
final wasVisible = isKeyboardVisible.value;
isKeyboardVisible.value = isVisible;
// 只有当键盘从隐藏变为显示时,才收缩图片选项
// 避免键盘关闭时误触发收缩逻辑
if (!wasVisible && isVisible && isImageOptionsExpanded.value) {
collapseImageOptions();
}
}
// 隐藏键盘
void hideKeyboard() {
FocusManager.instance.primaryFocus?.unfocus();
}
// 使用预设提示词处理图片 // 使用预设提示词处理图片
void useImagePrompt(String prompt) { void useImagePrompt(String prompt) {
// 确保selectedImagePath.value不为null // 确保selectedImagePath.value不为null

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

@ -11,12 +11,45 @@ import '../../../core/theme/app_colors.dart';
import 'package:haptic_feedback/haptic_feedback.dart'; import 'package:haptic_feedback/haptic_feedback.dart';
import '../../../data/models/message_model.dart'; import '../../../data/models/message_model.dart';
class AgentView extends StatelessWidget { class AgentView extends StatefulWidget {
const AgentView({Key? key}) : super(key: key); const AgentView({Key? key}) : super(key: key);
@override
State<AgentView> createState() => _AgentViewState();
}
class _AgentViewState extends State<AgentView> with WidgetsBindingObserver {
late AgentController controller;
double _keyboardHeight = 0;
@override
void initState() {
super.initState();
controller = Get.find<AgentController>();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
super.didChangeMetrics();
final bottomInset = View.of(context).viewInsets.bottom;
final newKeyboardHeight = bottomInset / View.of(context).devicePixelRatio;
if (_keyboardHeight != newKeyboardHeight) {
_keyboardHeight = newKeyboardHeight;
// 更新键盘可见状态
controller.updateKeyboardVisibility(_keyboardHeight > 0);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final controller = Get.find<AgentController>();
final theme = Theme.of(context); final theme = Theme.of(context);
final primaryColor = theme.primaryColor; final primaryColor = theme.primaryColor;
final backgroundColor = theme.scaffoldBackgroundColor; final backgroundColor = theme.scaffoldBackgroundColor;
@ -288,6 +321,9 @@ class AgentView extends StatelessWidget {
} }
}), }),
// 输入控件区域 - 包含输入行和展开的选项
Column(
children: [
// 输入控件行 - 使用IntrinsicHeight确保按钮与输入框底部对齐 // 输入控件行 - 使用IntrinsicHeight确保按钮与输入框底部对齐
IntrinsicHeight( IntrinsicHeight(
child: Row( child: Row(
@ -325,7 +361,8 @@ class AgentView extends StatelessWidget {
// 文本输入框或语音波形图 // 文本输入框或语音波形图
Expanded( Expanded(
child: Obx(() { child: Obx(() {
final isTextMode = controller.isTextInputMode.value; final isTextMode =
controller.isTextInputMode.value;
if (isTextMode) { if (isTextMode) {
// 文本输入模式 - 限制最大高度并只向上扩展 // 文本输入模式 - 限制最大高度并只向上扩展
@ -334,8 +371,8 @@ class AgentView extends StatelessWidget {
minHeight: 44, // 最小高度 minHeight: 44, // 最小高度
maxHeight: 120, // 最大高度限制,防止挤压按钮 maxHeight: 120, // 最大高度限制,防止挤压按钮
), ),
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 16), horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.grey[800] ? Colors.grey[800]
@ -356,18 +393,29 @@ class AgentView extends StatelessWidget {
Expanded( Expanded(
child: Obx(() { child: Obx(() {
// 图片模式下使用更高的输入框 // 图片模式下使用更高的输入框
final isImageActive = final isImageActive = controller
controller.isImageInputActive.value; .isImageInputActive.value;
return TextField( return TextField(
controller: controller.textController, controller:
maxLines: controller.textController,
isImageActive ? 3 : 5, // 设置合理的最大行数 maxLines: isImageActive
? 3
: 5, // 设置合理的最大行数
minLines: isImageActive ? 2 : 1, minLines: isImageActive ? 2 : 1,
keyboardType: keyboardType: TextInputType
TextInputType.multiline, // 启用多行键盘 .multiline, // 启用多行键盘
textInputAction: textInputAction: TextInputAction
TextInputAction.newline, // 支持回车换行 .newline, // 支持回车换行
onTap: () {
// 当点击输入框时,如果图片选项已展开,则收缩
if (controller
.isImageOptionsExpanded
.value) {
controller
.collapseImageOptions();
}
},
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 15,
color: isDarkMode color: isDarkMode
@ -392,7 +440,8 @@ class AgentView extends StatelessWidget {
width: 1.0, width: 1.0,
), ),
), ),
contentPadding: EdgeInsets.symmetric( contentPadding:
EdgeInsets.symmetric(
vertical: isImageActive vertical: isImageActive
? 12 ? 12
: 8), // 调整垂直内边距 : 8), // 调整垂直内边距
@ -417,8 +466,8 @@ class AgentView extends StatelessWidget {
}, },
child: Container( child: Container(
height: 50, height: 50,
padding: padding: const EdgeInsets.symmetric(
const EdgeInsets.symmetric(horizontal: 16), horizontal: 16),
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.grey[800] ? Colors.grey[800]
@ -445,8 +494,8 @@ class AgentView extends StatelessWidget {
const Spacer(), const Spacer(),
// "正在聆听"文字 // "正在聆听"文字
Obx(() { Obx(() {
final isListening = final isListening = controller
controller.isListening.value; .isListening.value;
return Row( return Row(
children: [ children: [
Icon( Icon(
@ -461,7 +510,8 @@ class AgentView extends StatelessWidget {
'正在聆听', '正在聆听',
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontWeight: FontWeight.w500, fontWeight:
FontWeight.w500,
color: isListening color: isListening
? primaryColor ? primaryColor
: Colors.grey[500], : Colors.grey[500],
@ -486,7 +536,8 @@ class AgentView extends StatelessWidget {
// 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮 // 动态按钮区域:展开按钮 + 发送按钮 或 图片选择按钮
Obx(() { Obx(() {
final hasText = controller.hasTextInput.value; final hasText = controller.hasTextInput.value;
final hasImage = controller.isImageInputActive.value; final hasImage =
controller.isImageInputActive.value;
final shouldShowSend = hasText || hasImage; final shouldShowSend = hasText || hasImage;
final shouldShowExpand = final shouldShowExpand =
controller.shouldShowExpandButton.value; controller.shouldShowExpandButton.value;
@ -499,7 +550,8 @@ class AgentView extends StatelessWidget {
// 展开按钮(在发送按钮上方) // 展开按钮(在发送按钮上方)
if (shouldShowExpand && hasText) if (shouldShowExpand && hasText)
Padding( Padding(
padding: const EdgeInsets.only(bottom: 40), padding:
const EdgeInsets.only(bottom: 40),
child: GestureDetector( child: GestureDetector(
onTap: () { onTap: () {
// 打开底部弹出编辑模式 // 打开底部弹出编辑模式
@ -522,7 +574,8 @@ class AgentView extends StatelessWidget {
color: isDarkMode color: isDarkMode
? Colors.grey[700] ? Colors.grey[700]
: Colors.grey[300], : Colors.grey[300],
borderRadius: BorderRadius.circular(16), borderRadius:
BorderRadius.circular(16),
), ),
child: Icon( child: Icon(
Icons.expand_less, Icons.expand_less,
@ -554,8 +607,15 @@ class AgentView extends StatelessWidget {
], ],
); );
} else { } else {
// 显示图片选择按钮 // 显示'+'选择按钮
return Container( return Obx(() {
final isExpanded =
controller.isImageOptionsExpanded.value;
return GestureDetector(
onTap: () {
controller.toggleImageOptions();
},
child: Container(
width: 44, width: 44,
height: 44, height: 44,
decoration: BoxDecoration( decoration: BoxDecoration(
@ -564,58 +624,89 @@ class AgentView extends StatelessWidget {
: Colors.grey[200], : Colors.grey[200],
borderRadius: BorderRadius.circular(22), borderRadius: BorderRadius.circular(22),
), ),
child: PopupMenuButton<String>( child: Icon(
icon: Icon( isExpanded ? Icons.close : Icons.add,
Icons.add_photo_alternate_outlined,
color: isDarkMode color: isDarkMode
? Colors.grey[300] ? Colors.grey[300]
: Colors.grey[700], : Colors.grey[700],
size: 22, size: 22,
), ),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
), ),
onSelected: (value) async { );
if (value == 'camera') { });
await controller.takePhoto();
} else if (value == 'gallery') {
await controller.pickImage();
} }
}, }),
itemBuilder: (context) => [
PopupMenuItem<String>(
value: 'camera',
child: Row(
children: [
Icon(Icons.camera_alt,
color: primaryColor, size: 20),
const SizedBox(width: 8),
const Text('拍照',
style: TextStyle(fontSize: 14)),
], ],
), ),
), ),
PopupMenuItem<String>(
value: 'gallery', // 展开的'+'选择选项
Obx(() {
final isExpanded =
controller.isImageOptionsExpanded.value;
return AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: isExpanded ? 100.h : 0, // 调整高度以适应按钮尺寸
child: isExpanded
? Container(
margin: EdgeInsets.only(top: 8.h),
padding: EdgeInsets.symmetric(horizontal:16.w), // 添加左右边距
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 改为均匀分布
children: [ children: [
Icon(Icons.photo_library, // 相机按钮
color: primaryColor, size: 20), _buildPlusOptionButton(
const SizedBox(width: 8), context: context,
const Text('从相册选择', icon: Icons.camera_alt,
style: TextStyle(fontSize: 14)), label: '拍照',
], onTap: () async {
//controller.collapseImageOptions();
await controller.takePhoto();
},
isDarkMode: isDarkMode,
), ),
// 相册按钮
_buildPlusOptionButton(
context: context,
icon: Icons.photo_library,
label: '相册',
onTap: () async {
//controller.collapseImageOptions();
await controller.pickImage();
},
isDarkMode: isDarkMode,
),
// 文件按钮(预留位置)
// _buildPlusOptionButton(
// context: context,
// icon: Icons.folder,
// label: '文件',
// onTap: () {
// //controller.collapseImageOptions();
// // TODO: 实现文件选择功能
// },
// isDarkMode: isDarkMode,
// ),
// 通话按钮(预留位置)
_buildPlusOptionButton(
context: context,
icon: Icons.phone,
label: '打电话',
onTap: () {
//controller.collapseImageOptions();
// TODO: 实现通话功能
},
isDarkMode: isDarkMode,
), ),
], ],
), ),
)
: const SizedBox.shrink(),
); );
}
}), }),
], ],
), ),
),
], ],
), ),
), ),
@ -792,6 +883,62 @@ class AgentView extends StatelessWidget {
)), )),
); );
} }
// 构建"+"号展开选项按钮
Widget _buildPlusOptionButton({
required BuildContext context,
required IconData icon,
required String label,
required VoidCallback onTap,
required bool isDarkMode,
}) {
final primaryColor = Theme.of(context).primaryColor;
return GestureDetector(
onTap: onTap,
child: SizedBox(
// 调整为一行四个按钮的宽度,考虑屏幕宽度和间距
width: (1.sw - 100.w) / 4, // 屏幕宽度减去左右边距,除以4个按钮
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 正方形图标容器 - 增大尺寸
Container(
width: 50.w, // 调整为44.w以适应布局
height: 50.w, // 调整为44.w以适应布局
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[800] : Colors.grey[100],
borderRadius: BorderRadius.circular(12.r), // 调整为10.r
border: Border.all(
color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!,
width: 1,
),
),
child: Icon(
icon,
color: primaryColor,
size: 20.sp, // 调整为18.sp
),
),
SizedBox(height: 4.h), // 调整为3.h
// 文字标签
Text(
label,
style: TextStyle(
fontSize: 10.sp, // 调整为9.sp
color: isDarkMode ? Colors.grey[300] : Colors.grey[700],
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
],
),
),
);
}
} }
class _ReportForm extends StatelessWidget { class _ReportForm extends StatelessWidget {

Loading…
Cancel
Save