From a643735dd780e8ab9a63389f7b1b274aff9d92ff Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Tue, 20 May 2025 17:43:05 +0800 Subject: [PATCH] =?UTF-8?q?refactor(agent):=20=E5=B0=86=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E5=92=8C=E5=A4=A9=E6=B0=94=E5=8D=A1=E7=89=87=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E6=8F=90=E5=8F=96=E5=88=B0=E7=8B=AC=E7=AB=8B=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E4=BB=A5=E6=8F=90=E5=8D=87=E4=BB=A3=E7=A0=81=E5=8F=AF=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/views/message_bubble.dart | 629 +------------------- lib/modules/agent/views/music_card.dart | 261 ++++++++ lib/modules/agent/views/weather_card.dart | 313 ++++++++++ pubspec.yaml | 2 +- 4 files changed, 579 insertions(+), 626 deletions(-) create mode 100644 lib/modules/agent/views/music_card.dart create mode 100644 lib/modules/agent/views/weather_card.dart diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index 7c8df4796..9dd85e9e7 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -1,6 +1,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 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:intl/intl.dart'; @@ -257,14 +258,14 @@ class MessageBubble extends GetView { final musicSinger = meta!['card_music']['sgener'] as String; final musicImage = meta!['card_music']['image'] as String; //音乐卡片 - return _buildMusicItem( + return buildMusicItem( context, musicId, musicName, musicUrl, musicSinger, musicImage); } else if (meta!.keys.contains('card_musiclist')) { final rawList = meta!['card_musiclist']['musics'] as List; final playlist = rawList.map((e) => Map.from(e)).toList(); //音乐播放列表 - return _buildMusicContent(context, playlist); + return buildMusicContent(context, playlist); } else if (meta!.keys.contains('card_weather')) { final city = meta!['card_weather']['city'] as String; final rawList = meta!['card_weather']['dayweather'] as List; @@ -272,7 +273,7 @@ class MessageBubble extends GetView { rawList.map((e) => Map.from(e)).toList(); final now = meta!['card_weather']['day'] as Map; //天气 - return _buildWeatherContent( + return buildWeatherContent( context, city, now, @@ -413,153 +414,6 @@ class MessageBubble extends GetView { return _AnimatedDot(delayMilliseconds: milliseconds); } - /// 构建音乐内容卡片 - Widget _buildMusicContent( - BuildContext context, List> playlist) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '以下是一些推荐的音乐:', - style: TextStyle( - color: isDarkMode ? Colors.white.withOpacity(0.9) : 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.music_note, - size: 16, - color: Theme.of(context).primaryColor.withOpacity(0.7), - ), - const SizedBox(width: 6), - Text( - '播放列表'.tr, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.grey[300] - : Colors.grey[700], - ), - ), - ], - ), - const SizedBox(height: 10), - - // 动态生成音乐条目 - ...playlist.asMap().entries.take(4).map((entry) { - final index = entry.key; - final item = entry.value; - final id = item["id"]?.toString() ?? ""; - final name = item["name"] ?? "未知歌曲"; - final url = item["url"] ?? ""; - final singer = item["sgener"] ?? "未知歌手"; - final image = item["image"] ?? ""; - return Column( - children: [ - _buildMusicItem(context, id, name, url, singer, image), - if (index != playlist.length - 1) - Divider( - height: 1, - thickness: 1, - color: Theme.of(context).brightness == Brightness.dark - ? Colors.grey[800] - : Colors.grey[100], - ), - ], - ); - }).toList(), - 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.play_arrow, 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.shuffle, 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 _buildTravelContent( BuildContext context, List> travellist) { @@ -708,120 +562,6 @@ class MessageBubble extends GetView { ); } - /// 构建音乐项目 - Widget _buildMusicItem(BuildContext context, String id, String name, - String url, String singer, String image) { - final controller = Get.find(); // 获取控制器 - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - Map song = { - 'id': id, - 'url': url, - 'title': name, - 'artist': singer, - 'coverUrl': image, - }; - controller.musicStatus(); //刷新状态 - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: isDarkMode ? Color(0xFF1E1E1E) : Colors.white, - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(isDarkMode ? 0.2 : 0.03), - blurRadius: 3, - spreadRadius: 1, - offset: const Offset(0, 1), - ), - ], - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - Container( - width: 50, - height: 50, - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - image: image.isNotEmpty - ? DecorationImage( - image: NetworkImage(image), - fit: BoxFit.cover, - ) - : null, - ), - child: image.isEmpty - ? Icon( - Icons.music_note, - size: 18, - color: Theme.of(context).primaryColor, - ) - : null, - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.grey[800], - ), - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 2), - Text( - singer, - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], - ), - overflow: TextOverflow.ellipsis, - ), - ], - ), - ), - Obx(() { - final isThisPlaying = controller.isPlaying.value && - controller.currentUrl.value == url; - - return IconButton( - onPressed: () { - if (controller.isPlaying.value) { - if (controller.currentUrl.value == url) { - controller.musicPause(); - } else { - controller.musicPlay(song); - } - } else { - if (controller.currentUrl.value == url) { - controller.musicResume(); - } else { - controller.musicPlay(song); - } - } - }, - icon: Icon( - isThisPlaying ? Icons.pause_circle : Icons.play_circle, - color: Theme.of(context).primaryColor, - ), - padding: EdgeInsets.zero, - constraints: BoxConstraints(), - iconSize: 36, - ); - }), - ], - ), - ), - ); - } - /// 构建地点项目 Widget _buildLocationItem( BuildContext context, String name, String description) { @@ -883,367 +623,6 @@ class MessageBubble extends GetView { ), ); } - - /// 构建天气内容卡片 - Widget _buildWeatherContent(BuildContext context, String city, - Map now, List> days) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '查询天气情况:', - style: TextStyle( - color: isDarkMode ? Colors.white.withOpacity(0.9) : Colors.black87, - fontSize: 14, - height: 1.4, - fontWeight: FontWeight.w400, - ), - ), - const SizedBox(height: 12), - Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - gradient: isDarkMode - ? LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)], - ) - : LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)], - ), - borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black.withOpacity(isDarkMode ? 0.3 : 0.1), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${city} ${now["date"]}", - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - SizedBox(height: 4), - Text( - now["weatherCondition"], - style: TextStyle( - fontSize: 14, - color: Colors.white.withOpacity(0.9), - ), - ), - SizedBox(height: 12), - Row( - children: [ - Text( - "${now["temperature"]}°", - style: const TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - SizedBox(width: 8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '最高: ${now["maxTemperature"]}°', - style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.9), - ), - ), - Text( - '最低: ${now["minTemperature"]}°', - style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.9), - ), - ), - ], - ), - ], - ), - ], - ), - const Icon( - Icons.wb_sunny, - size: 48, - color: Colors.white, - ), - ], - ), - SizedBox(height: 16), - Container( - height: 1, - color: Colors.white.withOpacity(isDarkMode ? 0.15 : 0.2), - ), - SizedBox(height: 16), - const Text( - '未来预报', - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: Colors.white, - ), - ), - SizedBox(height: 8), - // Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // _buildForecastDay('周一', Icons.wb_sunny, '28°'), - // _buildForecastDay('周二', Icons.cloud, '25°'), - // _buildForecastDay('周三', Icons.wb_cloudy, '26°'), - // _buildForecastDay('周四', Icons.grain, '24°'), - // _buildForecastDay('周五', Icons.wb_sunny, '27°'), - // ], - // ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate( - days.length, - (index) { - String dateStr = days[index]['date']; - DateTime? date = DateUtil.getDateTime(dateStr); - IconData weatherIcon = - getWeatherIcon(days[index]['weatherCondition']); - String weekday = DateUtil.getWeekday(date, - languageCode: "zh", short: true); - String temp = "${days[index]['temperature']}°"; - return _buildForecastDay( - weekday, - weatherIcon, - temp, - ); - }, - ), - ), - SizedBox(height: 16), - Container( - padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), - decoration: BoxDecoration( - color: isDarkMode - ? Colors.white.withOpacity(0.15) - : Colors.white.withOpacity(0.2), - borderRadius: BorderRadius.circular(8), - ), - child: const Row( - children: [ - Icon( - Icons.info_outline, - size: 16, - color: Colors.white, - ), - SizedBox(width: 6), - Flexible( - child: Text( - '今日紫外线指数较高,外出请做好防晒', - style: TextStyle( - fontSize: 12, - color: Colors.white, - ), - ), - ), - ], - ), - ), - ], - ), - ), - // const SizedBox(height: 10), - // Row( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Expanded( - // child: OutlinedButton.icon( - // onPressed: () {}, - // icon: Icon(Icons.location_on, 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.calendar_today, size: 16), - // label: Text('15天预报'), - // 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 _buildForecastDay(String day, IconData icon, String temp) { - return Column( - children: [ - Text( - day, - style: TextStyle( - fontSize: 12, - color: Colors.white.withOpacity(0.9), - ), - ), - SizedBox(height: 4), - Icon( - icon, - size: 20, - color: Colors.white, - ), - SizedBox(height: 4), - Text( - temp, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - ), - ], - ); - } - - /// 获取天气图标 - IconData getWeatherIcon(String description) { - description = description.trim(); - - if (["晴", "少云", "晴间多云"].contains(description)) { - return Icons.wb_sunny; - } - - if (["多云", "阴"].contains(description)) { - return Icons.cloud; - } - - if (description.contains("雾") || - ["雾", "浓雾", "强浓雾", "轻雾", "大雾", "特强浓雾", "霾", "中度霾", "重度霾", "严重霾"] - .contains(description)) { - return Icons.blur_on; - } - - if (description.contains("风") || - [ - "有风", - "平静", - "微风", - "和风", - "清风", - "强风/劲风", - "疾风", - "大风", - "烈风", - "风暴", - "狂爆风", - "飓风", - "热带风暴" - ].contains(description)) { - return Icons.air; - } - - if (description.contains("雨") || - [ - "阵雨", - "雷阵雨", - "雷阵雨并伴有冰雹", - "小雨", - "中雨", - "大雨", - "暴雨", - "大暴雨", - "特大暴雨", - "强阵雨", - "强雷阵雨", - "极端降雨", - "毛毛雨/细雨", - "雨", - "小雨-中雨", - "中雨-大雨", - "大雨-暴雨", - "暴雨-大暴雨", - "大暴雨-特大暴雨" - ].contains(description)) { - return Icons.grain; // 或使用 community icon 包:weather_icons 中的 rain - } - - if (description.contains("雪") || - [ - "雨雪天气", - "雨夹雪", - "阵雨夹雪", - "冻雨", - "雪", - "阵雪", - "小雪", - "中雪", - "大雪", - "暴雪", - "小雪-中雪", - "中雪-大雪", - "大雪-暴雪" - ].contains(description)) { - return Icons.ac_unit; - } - - if (["浮尘", "扬沙", "沙尘暴", "强沙尘暴"].contains(description)) { - return Icons.filter_drama; - } - - if (["龙卷风"].contains(description)) { - return Icons.waves; - } - - if (["热"].contains(description)) { - return Icons.local_fire_department; - } - - if (["冷"].contains(description)) { - return Icons.ac_unit; - } - - // 默认或未知天气 - return Icons.help_outline; - } } /// 动画点组件 diff --git a/lib/modules/agent/views/music_card.dart b/lib/modules/agent/views/music_card.dart new file mode 100644 index 000000000..d76270cad --- /dev/null +++ b/lib/modules/agent/views/music_card.dart @@ -0,0 +1,261 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../controllers/agent_controller.dart'; + +/// 构建音乐项目 +Widget buildMusicItem(BuildContext context, String id, String name, String url, + String singer, String image) { + final controller = Get.find(); // 获取控制器 + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + Map song = { + 'id': id, + 'url': url, + 'title': name, + 'artist': singer, + 'coverUrl': image, + }; + controller.musicStatus(); //刷新状态 + return Container( + padding: const EdgeInsets.all(14), + decoration: BoxDecoration( + color: isDarkMode ? Color(0xFF1E1E1E) : Colors.white, + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(isDarkMode ? 0.2 : 0.03), + blurRadius: 3, + spreadRadius: 1, + offset: const Offset(0, 1), + ), + ], + ), + child: Row( + children: [ + Container( + width: 50, + height: 50, + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + image: image.isNotEmpty + ? DecorationImage( + image: NetworkImage(image), + fit: BoxFit.cover, + ) + : null, + ), + child: image.isEmpty + ? Icon( + Icons.music_note, + size: 18, + color: Theme.of(context).primaryColor, + ) + : null, + ), + const SizedBox(width: 12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w500, + color: Colors.grey[800], + ), + overflow: TextOverflow.ellipsis, + ), + SizedBox(height: 2), + Text( + singer, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + Obx(() { + final isThisPlaying = + controller.isPlaying.value && controller.currentUrl.value == url; + + return IconButton( + onPressed: () { + if (controller.isPlaying.value) { + if (controller.currentUrl.value == url) { + controller.musicPause(); + } else { + controller.musicPlay(song); + } + } else { + if (controller.currentUrl.value == url) { + controller.musicResume(); + } else { + controller.musicPlay(song); + } + } + }, + icon: Icon( + isThisPlaying ? Icons.pause_circle : Icons.play_circle, + color: Theme.of(context).primaryColor, + ), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + iconSize: 36, + ); + }), + ], + ), + ); +} + +/// 构建音乐内容卡片 +Widget buildMusicContent( + BuildContext context, List> playlist) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '以下是一些推荐的音乐:', + style: TextStyle( + color: isDarkMode ? Colors.white.withOpacity(0.9) : 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.music_note, + size: 16, + color: Theme.of(context).primaryColor.withOpacity(0.7), + ), + const SizedBox(width: 6), + Text( + '播放列表'.tr, + style: TextStyle( + fontSize: 13, + fontWeight: FontWeight.w600, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.grey[300] + : Colors.grey[700], + ), + ), + ], + ), + const SizedBox(height: 10), + + // 动态生成音乐条目 + ...playlist.asMap().entries.take(4).map((entry) { + final index = entry.key; + final item = entry.value; + final id = item["id"]?.toString() ?? ""; + final name = item["name"] ?? "未知歌曲"; + final url = item["url"] ?? ""; + final singer = item["sgener"] ?? "未知歌手"; + final image = item["image"] ?? ""; + return Column( + children: [ + buildMusicItem(context, id, name, url, singer, image), + if (index != playlist.length - 1) + Divider( + height: 1, + thickness: 1, + color: Theme.of(context).brightness == Brightness.dark + ? Colors.grey[800] + : Colors.grey[100], + ), + ], + ); + }).toList(), + 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.play_arrow, 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.shuffle, 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), + // ), + // ), + // ), + // ], + // ), + ], + ); +} diff --git a/lib/modules/agent/views/weather_card.dart b/lib/modules/agent/views/weather_card.dart new file mode 100644 index 000000000..aba39038e --- /dev/null +++ b/lib/modules/agent/views/weather_card.dart @@ -0,0 +1,313 @@ +import 'package:common_utils/common_utils.dart'; +import 'package:flutter/material.dart'; + +/// 构建天气内容卡片 +Widget buildWeatherContent(BuildContext context, String city, + Map now, List> days) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '查询天气情况:', + style: TextStyle( + color: isDarkMode ? Colors.white.withOpacity(0.9) : Colors.black87, + fontSize: 14, + height: 1.4, + fontWeight: FontWeight.w400, + ), + ), + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + gradient: isDarkMode + ? LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF1E40AF), Color(0xFF1E3A8A)], + ) + : LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [Color(0xFF60A5FA), Color(0xFF3B82F6)], + ), + borderRadius: BorderRadius.circular(12), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(isDarkMode ? 0.3 : 0.1), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "${city} ${now["date"]}", + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + SizedBox(height: 4), + Text( + now["weatherCondition"], + style: TextStyle( + fontSize: 14, + color: Colors.white.withOpacity(0.9), + ), + ), + SizedBox(height: 8), + Row( + children: [ + Text( + "${now["temperature"]}°", + style: const TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '最高: ${now["maxTemperature"]}°', + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.9), + ), + ), + Text( + '最低: ${now["minTemperature"]}°', + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.9), + ), + ), + ], + ), + ], + ), + ], + ), + const Icon( + Icons.wb_sunny, + size: 48, + color: Colors.white, + ), + ], + ), + SizedBox(height: 16), + Container( + height: 1, + color: Colors.white.withOpacity(isDarkMode ? 0.15 : 0.2), + ), + SizedBox(height: 16), + const Text( + '未来预报', + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate( + days.length, + (index) { + String dateStr = days[index]['date']; + DateTime? date = DateUtil.getDateTime(dateStr); + IconData weatherIcon = + getWeatherIcon(days[index]['weatherCondition']); + String weekday = DateUtil.getWeekday(date, + languageCode: "zh", short: true); + String temp = "${days[index]['temperature']}°"; + return _buildForecastDay( + weekday, + weatherIcon, + temp, + ); + }, + ), + ), + SizedBox(height: 16), + Container( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.15) + : Colors.white.withOpacity(0.2), + borderRadius: BorderRadius.circular(8), + ), + child: const Row( + children: [ + Icon( + Icons.info_outline, + size: 16, + color: Colors.white, + ), + SizedBox(width: 8), + Flexible( + child: Text( + '今日紫外线指数较高,外出请做好防晒', + style: TextStyle( + fontSize: 12, + color: Colors.white, + ), + ), + ), + ], + ), + ), + ], + ), + ), + ], + ); +} + +/// 构建单日天气预报 +Widget _buildForecastDay(String day, IconData icon, String temp) { + return Column( + children: [ + Text( + day, + style: TextStyle( + fontSize: 12, + color: Colors.white.withOpacity(0.9), + ), + ), + SizedBox(height: 4), + Icon( + icon, + size: 20, + color: Colors.white, + ), + SizedBox(height: 4), + Text( + temp, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + ), + ], + ); +} + +/// 获取天气图标 +IconData getWeatherIcon(String description) { + description = description.trim(); + + if (["晴", "少云", "晴间多云"].contains(description)) { + return Icons.wb_sunny; + } + + if (["多云", "阴"].contains(description)) { + return Icons.cloud; + } + + if (description.contains("雾") || + ["雾", "浓雾", "强浓雾", "轻雾", "大雾", "特强浓雾", "霾", "中度霾", "重度霾", "严重霾"] + .contains(description)) { + return Icons.blur_on; + } + + if (description.contains("风") || + [ + "有风", + "平静", + "微风", + "和风", + "清风", + "强风/劲风", + "疾风", + "大风", + "烈风", + "风暴", + "狂爆风", + "飓风", + "热带风暴" + ].contains(description)) { + return Icons.air; + } + + if (description.contains("雨") || + [ + "阵雨", + "雷阵雨", + "雷阵雨并伴有冰雹", + "小雨", + "中雨", + "大雨", + "暴雨", + "大暴雨", + "特大暴雨", + "强阵雨", + "强雷阵雨", + "极端降雨", + "毛毛雨/细雨", + "雨", + "小雨-中雨", + "中雨-大雨", + "大雨-暴雨", + "暴雨-大暴雨", + "大暴雨-特大暴雨" + ].contains(description)) { + return Icons.grain; // 或使用 community icon 包:weather_icons 中的 rain + } + + if (description.contains("雪") || + [ + "雨雪天气", + "雨夹雪", + "阵雨夹雪", + "冻雨", + "雪", + "阵雪", + "小雪", + "中雪", + "大雪", + "暴雪", + "小雪-中雪", + "中雪-大雪", + "大雪-暴雪" + ].contains(description)) { + return Icons.ac_unit; + } + + if (["浮尘", "扬沙", "沙尘暴", "强沙尘暴"].contains(description)) { + return Icons.filter_drama; + } + + if (["龙卷风"].contains(description)) { + return Icons.waves; + } + + if (["热"].contains(description)) { + return Icons.local_fire_department; + } + + if (["冷"].contains(description)) { + return Icons.ac_unit; + } + + // 默认或未知天气 + return Icons.help_outline; +} diff --git a/pubspec.yaml b/pubspec.yaml index 7a8bbb5c2..cb2913215 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: flutter_screenutil: ^5.9.0 path_provider: ^2.1.5 audio_session: ^0.1.18 - flutter_markdown: ^0.6.18+2 + flutter_markdown: ^0.7.7+1 markdown: ^7.1.1 permission_handler: ^11.4.0 web_socket_channel: ^3.0.2