diff --git a/.gitignore b/.gitignore index 339524773..c05ba28a1 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,9 @@ coverage/ *.js_ *.js.deps *.js.map +linux/flutter/generated_plugin_registrant.cc +linux/flutter/generated_plugin_registrant.h +linux/flutter/generated_plugins.cmake +windows/flutter/generated_plugin_registrant.cc +windows/flutter/generated_plugin_registrant.h +windows/flutter/generated_plugins.cmake diff --git a/lib/modules/agent/views/agent_view.dart b/lib/modules/agent/views/agent_view.dart index dd642fd4e..f4bed8cc6 100644 --- a/lib/modules/agent/views/agent_view.dart +++ b/lib/modules/agent/views/agent_view.dart @@ -149,28 +149,32 @@ class AgentView extends StatelessWidget { // 聊天内容 Expanded( child: Obx( - () => ListView.builder( - controller: controller.scrollController, - reverse: true, - padding: - const EdgeInsets.symmetric(vertical: 16, horizontal: 16), - itemCount: controller.messages.length, - itemBuilder: (context, index) { - final message = controller - .messages[controller.messages.length - 1 - index]; - return Padding( - padding: const EdgeInsets.only(bottom: 8), - child: MessageBubble( - isUser: message.isUser, - message: message.text, - timestamp: message.timestamp, - isRecognizing: message.isRecognizing, - hasImage: message.hasImage, - imagePath: message.imagePath, - mate: message.meta, - ), - ); - }, + () => Align( + alignment: Alignment.topCenter, + child: ListView.builder( + controller: controller.scrollController, + shrinkWrap: true, + reverse: true, + padding: + const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + itemCount: controller.messages.length, + itemBuilder: (context, index) { + final message = controller + .messages[controller.messages.length - 1 - index]; + return Padding( + padding: const EdgeInsets.only(bottom: 8), + child: MessageBubble( + isUser: message.isUser, + message: message.text, + timestamp: message.timestamp, + isRecognizing: message.isRecognizing, + hasImage: message.hasImage, + imagePath: message.imagePath, + meta: message.meta, + ), + ); + }, + ), ), ), ), @@ -308,7 +312,7 @@ class AgentView extends StatelessWidget { if (isTextMode) { // 文本输入模式 - 注意观察图片模式时的高度变化 return Container( - height: 44, + //height: 44, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.grey[200], @@ -378,7 +382,7 @@ class AgentView extends StatelessWidget { } }, child: Container( - height: 44, + height: 50, padding: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( diff --git a/lib/modules/agent/views/message_bubble.dart b/lib/modules/agent/views/message_bubble.dart index c432b0e65..cfc505157 100644 --- a/lib/modules/agent/views/message_bubble.dart +++ b/lib/modules/agent/views/message_bubble.dart @@ -15,7 +15,7 @@ class MessageBubble extends GetView { final bool isRecognizing; final bool hasImage; final String? imagePath; - final Map? mate; + final Map? meta; const MessageBubble({ Key? key, @@ -25,7 +25,7 @@ class MessageBubble extends GetView { this.isRecognizing = false, this.hasImage = false, this.imagePath, - this.mate, + this.meta, }) : super(key: key); @override @@ -39,8 +39,8 @@ class MessageBubble extends GetView { isUser ? MainAxisAlignment.end : MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (!isUser) _buildAvatar(isUser, context), - const SizedBox(width: 8), + //if (!isUser) _buildAvatar(isUser, context), + //const SizedBox(width: 8), Flexible( child: Column( crossAxisAlignment: @@ -50,15 +50,23 @@ class MessageBubble extends GetView { padding: hasImage ? (message == '[图片]' ? EdgeInsets.zero - : const EdgeInsets.all(12)) - : const EdgeInsets.all(12), + : isUser + ? const EdgeInsets.fromLTRB( + 12, 12, 16, 12) // 用户消息右侧多一点内边距 + : const EdgeInsets.fromLTRB( + 16, 12, 12, 12)) // AI消息左侧多一点内边距 + : isUser + ? const EdgeInsets.fromLTRB( + 12, 12, 16, 12) // 用户消息右侧多一点内边距 + : const EdgeInsets.fromLTRB( + 16, 12, 12, 12), // AI消息左侧多一点内边距 decoration: BoxDecoration( color: _getBubbleColor(context), borderRadius: BorderRadius.circular(18).copyWith( - bottomLeft: isUser + topLeft: isUser ? const Radius.circular(18) : const Radius.circular(4), - bottomRight: !isUser + topRight: !isUser ? const Radius.circular(18) : const Radius.circular(4), ), @@ -86,8 +94,8 @@ class MessageBubble extends GetView { ], ), ), - const SizedBox(width: 8), - if (isUser) _buildAvatar(isUser, context), + //const SizedBox(width: 8), + //if (isUser) _buildAvatar(isUser, context), ], ), ); @@ -231,28 +239,29 @@ class MessageBubble extends GetView { } // 判断消息内容,显示特殊卡片 - if (!isUser && mate != null) { - if (mate!.keys.contains('card_music')) { - final musicId = mate!['card_music']['id'].toString(); - final musicName = mate!['card_music']['name'] as String; - final musicUrl = mate!['card_music']['url'] as String; - final musicSinger = mate!['card_music']['sgener'] as String; - final musicImage = mate!['card_music']['image'] as String; + if (!isUser && meta != null) { + // 判断消息类型,显示不同的卡片 + if (meta!.keys.contains('card_music')) { + final musicId = meta!['card_music']['id'].toString(); + final musicName = meta!['card_music']['name'] as String; + final musicUrl = meta!['card_music']['url'] as String; + final musicSinger = meta!['card_music']['sgener'] as String; + final musicImage = meta!['card_music']['image'] as String; //音乐卡片 return _buildMusicItem( context, musicId, musicName, musicUrl, musicSinger, musicImage); - } else if (mate!.keys.contains('card_musiclist')) { - final rawList = mate!['card_musiclist']['musics'] as List; + } 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); - } else if (mate!.keys.contains('card_weather')) { - final city = mate!['card_weather']['city'] as String; - final rawList = mate!['card_weather']['dayweather'] as List; + } else if (meta!.keys.contains('card_weather')) { + final city = meta!['card_weather']['city'] as String; + final rawList = meta!['card_weather']['dayweather'] as List; final daylist = rawList.map((e) => Map.from(e)).toList(); - final now = mate!['card_weather']['day'] as Map; + final now = meta!['card_weather']['day'] as Map; //天气 return _buildWeatherContent( context, @@ -321,122 +330,123 @@ class MessageBubble extends GetView { ), ), 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: Colors.grey[700], - ), - ), - ], - ), - const SizedBox(height: 10), - - // 动态生成音乐条目 - ...playlist.asMap().entries.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: 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, + // 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: [ - 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), + 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: Colors.grey[700], ), - textStyle: TextStyle(fontSize: 12), ), - ), + ], ), - SizedBox(width: 8), - Expanded( - child: OutlinedButton.icon( + 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: Colors.grey[100]), + ], + ); + }).toList(), + const SizedBox(height: 8), + Center( + child: TextButton( 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), + 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, ), - textStyle: TextStyle(fontSize: 12), ), ), ), ], ), + //), + //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), + // ), + // ), + // ), + // ], + // ), ], ); } @@ -578,79 +588,102 @@ class MessageBubble extends GetView { 'coverUrl': image, }; controller.musicStatus(); //刷新状态 - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - Container( - width: 38, - height: 38, - decoration: BoxDecoration( - color: Colors.grey[100], - borderRadius: BorderRadius.circular(8), - ), - child: Icon( - Icons.music_note, - size: 18, - color: Theme.of(context).primaryColor, - ), + return 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), ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.grey[800], + ], + ), + 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, ), - overflow: TextOverflow.ellipsis, - ), - SizedBox(height: 2), - Text( - singer, - style: TextStyle( - fontSize: 12, - color: Colors.grey[600], + SizedBox(height: 2), + Text( + singer, + style: TextStyle( + fontSize: 12, + color: Colors.grey[600], + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, - ), - ], + ], + ), ), - ), - Obx(() { - final isThisPlaying = controller.isPlaying.value && - controller.currentUrl.value == url; + 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(); + return IconButton( + onPressed: () { + if (controller.isPlaying.value) { + if (controller.currentUrl.value == url) { + controller.musicPause(); + } else { + controller.musicPlay(song); + } } else { - controller.musicPlay(song); + 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: 24, - ); - }), - ], + }, + icon: Icon( + isThisPlaying ? Icons.pause_circle : Icons.play_circle, + color: Theme.of(context).primaryColor, + ), + padding: EdgeInsets.zero, + constraints: BoxConstraints(), + iconSize: 36, + ); + }), + ], + ), ), ); } diff --git a/lib/modules/home/controllers/home_controller.dart b/lib/modules/home/controllers/home_controller.dart index 7fd3e6f0a..501918a38 100644 --- a/lib/modules/home/controllers/home_controller.dart +++ b/lib/modules/home/controllers/home_controller.dart @@ -103,6 +103,9 @@ class HomeController extends GetxController { // 设置监听器监听蓝牙服务状态变化 _setupStateListeners(); + + // 在初始化时自动触发一次刷新设备连接状态 + refreshDeviceConnection(); } /// 设置状态监听器