From 3fada1193cf7556189715bb1fc2768125578fc55 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Tue, 20 May 2025 20:34:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor(agent):=20=E9=87=8D=E6=9E=84=E6=97=85?= =?UTF-8?q?=E6=B8=B8=E5=8D=A1=E7=89=87=E7=BB=84=E4=BB=B6=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E4=BB=A3=E7=A0=81=E6=A0=BC=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将旅游卡片相关逻辑从 `message_bubble.dart` 中提取到独立的 `travel_card.dart` 文件中,提高代码可维护性。同时优化了代码格式,包括缩进、空格和换行,提升代码可读性。 --- .../agent/controllers/agent_controller.dart | 38 ++- lib/modules/agent/views/message_bubble.dart | 230 +----------------- lib/modules/agent/views/travel_card.dart | 211 ++++++++++++++++ 3 files changed, 241 insertions(+), 238 deletions(-) create mode 100644 lib/modules/agent/views/travel_card.dart diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 536514dbf..97d17f807 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -180,11 +180,11 @@ class AgentController extends GetxController { void _addWelcomeMessage() { messages.add(Message( isUser: false, - text: '你好!我是Emma,我能帮你:\n\n' - '• 回答问题和提供信息\n' - '• 实时语音翻译\n' - '• 推荐音乐和播客\n' - '• 语音备忘录和提醒\n' + text: '你好!我是**Emma**,我能帮你:\n\n' + '• 答问题和提供信息\n\n' + '• 实时语音翻译\n\n' + '• 推荐音乐和播客\n\n' + '• 语音备忘录和提醒\n\n' '• 控制耳机功能\n\n' '有什么我可以帮你的吗?', )); @@ -395,12 +395,12 @@ class AgentController extends GetxController { Logger.e(TAG, '解析meta数据失败: $e'); } } - if(metaResult.isNotEmpty){ - + if (metaResult.isNotEmpty) { // 判断是否为新的回复或响应ID是否改变 if (_isNewAssistantResponse || _currentResponseId != responseId) { // 添加新消息 - messages.add(Message(isUser: false, text: "", meta: metaResult)); + messages + .add(Message(isUser: false, text: "", meta: metaResult)); _isNewAssistantResponse = false; _currentResponseId = responseId; } else { @@ -535,24 +535,22 @@ class AgentController extends GetxController { try { // 创建待请求权限列表 List permissionsToRequest = []; - + if (Platform.isAndroid) { // Android 需要考虑SDK版本不同的权限要求 permissionsToRequest.add(Permission.microphone); - - } else if (Platform.isIOS) { // iOS 只需要麦克风权限 permissionsToRequest.add(Permission.microphone); } - + // 批量请求所有需要的权限 final statuses = await permissionsToRequest.request(); - + // 检查是否所有权限都已授予 bool allGranted = true; Permission? deniedPermission; - + statuses.forEach((permission, status) { if (!status.isGranted) { allGranted = false; @@ -560,33 +558,33 @@ class AgentController extends GetxController { Logger.e(TAG, '权限 ${permission.toString()} 未被授予: $status'); } }); - + // 单独检查麦克风权限状态,因为这是主要需求 final micStatus = await Permission.microphone.status; - + // 如果所有权限都已授予,或至少麦克风权限已授予,返回true if (allGranted || micStatus.isGranted) { return true; } - + // 处理特定权限被永久拒绝的情况 if (micStatus.isPermanentlyDenied) { _showPermissionDialog(true); return false; } - + // 普通拒绝情况 if (deniedPermission != null) { _showPermissionDialog(false); } - + return false; } catch (e) { Logger.e(TAG, '检查麦克风权限失败: $e'); return false; } } - + // 获取Android SDK版本 Future _getAndroidSdkVersion() async { if (Platform.isAndroid) { diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index 9dd85e9e7..38845bbfc 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -2,6 +2,7 @@ import 'package:common_utils/common_utils.dart'; import 'package:deep_voice/modules/agent/views/message_bubble_navigation.dart'; import 'weather_card.dart'; import 'music_card.dart'; +import 'travel_card.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; @@ -210,13 +211,16 @@ class MessageBubble extends GetView { if (hasImage && imagePath != null) { // 如果消息是默认的[图片]标记,只显示图片 if (message == '[图片]') { - return ClipRRect( - borderRadius: BorderRadius.circular(16), - child: Image.file( - File(imagePath!), - width: 200, - height: 200, - fit: BoxFit.cover, + return Container( + padding: const EdgeInsets.all(8), + child: ClipRRect( + borderRadius: BorderRadius.circular(12), + child: Image.file( + File(imagePath!), + width: 200, + height: 200, + fit: BoxFit.cover, + ), ), ); } @@ -284,7 +288,7 @@ class MessageBubble extends GetView { final travellist = rawList.map((e) => Map.from(e)).toList(); // 旅游 - return _buildTravelContent(context, travellist); + return buildTravelContent(context, travellist); } else if (meta!.keys.contains('card_navigation')) { final rawList = meta!['card_navigation']['items'] as List; final list = rawList.map((e) => Map.from(e)).toList(); @@ -413,216 +417,6 @@ class MessageBubble extends GetView { Widget _buildDot(int milliseconds) { return _AnimatedDot(delayMilliseconds: milliseconds); } - - /// 构建旅游内容卡片 - Widget _buildTravelContent( - BuildContext context, List> travellist) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - '以下是一些推搜索结果:', - style: TextStyle( - color: Colors.black87, - fontSize: 14, - height: 1.4, - fontWeight: FontWeight.w400, - ), - ), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(0.03), - blurRadius: 3, - spreadRadius: 1, - offset: const Offset(0, 1), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // Row( - // children: [ - // Icon( - // Icons.place, - // size: 16, - // color: Color(0xFFE11D48), - // ), - // const SizedBox(width: 6), - // Text( - // '热门景点 · 东京', - // style: TextStyle( - // fontSize: 13, - // fontWeight: FontWeight.w600, - // color: Colors.grey[700], - // ), - // ), - // ], - // ), - // const SizedBox(height: 10), - - // 动态生成音乐条目 - ...travellist.asMap().entries.map((entry) { - final index = entry.key; - final item = entry.value; - final name = item["name"] ?? "未知歌曲"; //名字 - final addr = item["addr"] ?? ""; //地址 - final tag = item["tag"] ?? "未知歌手"; //标签 - final rating = item["rating"] ?? ""; //评分 - final distance = item["distance"] ?? ""; //距离 - final images = item["images"] ?? []; // 图片列表 - return Column( - children: [ - _buildLocationItem( - context, - name, - addr, - ), - if (index != travellist.length - 1) - Divider(height: 1, thickness: 1, color: Colors.grey[100]), - ], - ); - }).toList(), - // // 景点列表 - // _buildLocationItem('东京塔', '标志性建筑,可俯瞰东京全景', context), - // Divider(height: 1, thickness: 1, color: Colors.grey[100]), - // _buildLocationItem('涩谷十字路口', '世界上最繁忙的十字路口之一', context), - // Divider(height: 1, thickness: 1, color: Colors.grey[100]), - // _buildLocationItem('浅草寺', '东京最古老的寺庙', context), - - const SizedBox(height: 8), - Center( - child: TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - minimumSize: Size.zero, - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - ), - child: Text( - '查看更多景点', - style: TextStyle( - fontSize: 12, - color: Theme.of(context).primaryColor, - fontWeight: FontWeight.w500, - ), - ), - ), - ), - ], - ), - ), - const SizedBox(height: 10), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: OutlinedButton.icon( - onPressed: () {}, - icon: Icon(Icons.map, size: 16), - label: Text('查看地图'), - style: OutlinedButton.styleFrom( - foregroundColor: Theme.of(context).primaryColor, - side: BorderSide( - color: Theme.of(context).primaryColor.withOpacity(0.5)), - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - textStyle: TextStyle(fontSize: 12), - ), - ), - ), - SizedBox(width: 8), - Expanded( - child: OutlinedButton.icon( - onPressed: () {}, - icon: Icon(Icons.bookmark_border, size: 16), - label: Text('保存到收藏'), - style: OutlinedButton.styleFrom( - foregroundColor: Colors.grey[700], - side: BorderSide(color: Colors.grey[400]!), - padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - textStyle: TextStyle(fontSize: 12), - ), - ), - ), - ], - ), - ], - ); - } - - /// 构建地点项目 - Widget _buildLocationItem( - BuildContext context, String name, String description) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - Container( - width: 38, - height: 38, - decoration: BoxDecoration( - color: isDarkMode ? Color(0xFF1A365D) : Colors.blue[50], - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.place, - size: 18, - color: Color(0xFFE11D48), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: isDarkMode - ? Colors.white.withOpacity(0.9) - : Colors.grey[800], - ), - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 2), - Text( - description, - style: TextStyle( - fontSize: 12, - color: isDarkMode ? Colors.grey[400] : Colors.grey[600], - ), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - IconButton( - onPressed: () {}, - icon: Icon(Icons.arrow_forward, color: Colors.grey[400]), - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - iconSize: 20, - ), - ], - ), - ); - } } /// 动画点组件 diff --git a/lib/modules/agent/views/travel_card.dart b/lib/modules/agent/views/travel_card.dart new file mode 100644 index 000000000..e5c01a163 --- /dev/null +++ b/lib/modules/agent/views/travel_card.dart @@ -0,0 +1,211 @@ +import 'package:flutter/material.dart'; + +/// 构建旅游内容卡片 +Widget buildTravelContent( + BuildContext context, List> travellist) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '以下是一些推搜索结果:', + style: TextStyle( + color: Colors.black87, + fontSize: 14, + height: 1.4, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.03), + blurRadius: 3, + spreadRadius: 1, + offset: const Offset(0, 1), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Row( + // children: [ + // Icon( + // Icons.place, + // size: 16, + // color: Color(0xFFE11D48), + // ), + // const SizedBox(width: 6), + // Text( + // '热门景点 · 东京', + // style: TextStyle( + // fontSize: 13, + // fontWeight: FontWeight.w600, + // color: Colors.grey[700], + // ), + // ), + // ], + // ), + // const SizedBox(height: 10), + + // 动态生成音乐条目 + ...travellist.asMap().entries.map((entry) { + final index = entry.key; + final item = entry.value; + final name = item["name"] ?? "未知歌曲"; //名字 + final addr = item["addr"] ?? ""; //地址 + final tag = item["tag"] ?? "未知歌手"; //标签 + final rating = item["rating"] ?? ""; //评分 + final distance = item["distance"] ?? ""; //距离 + final images = item["images"] ?? []; // 图片列表 + return Column( + children: [ + buildLocationItem( + context, + name, + addr, + ), + if (index != travellist.length - 1) + Divider(height: 1, thickness: 1, color: Colors.grey[100]), + ], + ); + }).toList(), + // // 景点列表 + // _buildLocationItem('东京塔', '标志性建筑,可俯瞰东京全景', context), + // Divider(height: 1, thickness: 1, color: Colors.grey[100]), + // _buildLocationItem('涩谷十字路口', '世界上最繁忙的十字路口之一', context), + // Divider(height: 1, thickness: 1, color: Colors.grey[100]), + // _buildLocationItem('浅草寺', '东京最古老的寺庙', context), + + const SizedBox(height: 8), + Center( + child: TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + minimumSize: Size.zero, + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 8), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + ), + child: Text( + '查看更多景点', + style: TextStyle( + fontSize: 12, + color: Theme.of(context).primaryColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 10), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: OutlinedButton.icon( + onPressed: () {}, + icon: Icon(Icons.map, size: 16), + label: Text('查看地图'), + style: OutlinedButton.styleFrom( + foregroundColor: Theme.of(context).primaryColor, + side: BorderSide( + color: Theme.of(context).primaryColor.withOpacity(0.5)), + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + textStyle: TextStyle(fontSize: 12), + ), + ), + ), + SizedBox(width: 8), + Expanded( + child: OutlinedButton.icon( + onPressed: () {}, + icon: Icon(Icons.bookmark_border, size: 16), + label: Text('保存到收藏'), + style: OutlinedButton.styleFrom( + foregroundColor: Colors.grey[700], + side: BorderSide(color: Colors.grey[400]!), + padding: EdgeInsets.symmetric(horizontal: 12, vertical: 4), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + textStyle: TextStyle(fontSize: 12), + ), + ), + ), + ], + ), + ], + ); +} + +/// 构建地点项目 +Widget buildLocationItem( + BuildContext context, String name, String description) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( + children: [ + Container( + width: 38, + height: 38, + decoration: BoxDecoration( + color: isDarkMode ? Color(0xFF1A365D) : Colors.blue[50], + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + Icons.place, + size: 18, + color: Color(0xFFE11D48), + ), + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: isDarkMode + ? Colors.white.withOpacity(0.9) + : Colors.grey[800], + ), + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 2), + Text( + description, + style: TextStyle( + fontSize: 12, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + IconButton( + onPressed: () {}, + icon: Icon(Icons.arrow_forward, color: Colors.grey[400]), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + iconSize: 20, + ), + ], + ), + ); +}