Browse Source

Merge branch 'main' of https://git.onewaygame.cn/deepsound/deepvoice_client into liwei

newdev_shunjiawei
liwei1dao 1 year ago
parent
commit
daa49c4e5f
  1. 109
      lib/modules/chat/controllers/chat_controller.dart
  2. 65
      lib/modules/chat/views/animated_gradient_background.dart
  3. 44
      lib/modules/chat/views/chat_navigation_card.dart
  4. 538
      lib/modules/chat/views/chat_view.dart

109
lib/modules/chat/controllers/chat_controller.dart

@ -86,10 +86,19 @@ class ChatController extends GetxController {
// 记录最后一次活动时间 // 记录最后一次活动时间
DateTime _lastActivityTime = DateTime.now(); DateTime _lastActivityTime = DateTime.now();
// 音乐播放
final player = AudioPlayer(); final player = AudioPlayer();
final RxString audiourl = ''.obs; final RxString audiourl = ''.obs;
final RxBool isPlay = false.obs; final RxBool isPlay = false.obs;
// 按住说话、文字输入切换
final RxBool isTextInput = false.obs;
final RxBool isHoldSpeak = false.obs;
String finalResult = '';
// 按住说话防抖
Timer? _talkDebounceTimer;
@override @override
Future<void> onInit() async { Future<void> onInit() async {
super.onInit(); super.onInit();
@ -283,6 +292,7 @@ class ChatController extends GetxController {
_voiceInteractionSubscription?.cancel(); _voiceInteractionSubscription?.cancel();
_autoStopTimer?.cancel(); _autoStopTimer?.cancel();
_scrollDebounceTimer?.cancel(); // 清理防抖计时器 _scrollDebounceTimer?.cancel(); // 清理防抖计时器
_talkDebounceTimer?.cancel();
_amapFlutter.dispose(); _amapFlutter.dispose();
_locationStream?.cancel(); _locationStream?.cancel();
@ -570,6 +580,59 @@ class ChatController extends GetxController {
} }
} }
// 按下开始说话
void talkLongPress() async {
_talkDebounceTimer?.cancel();
if (isHoldSpeak.value) return;
_voiceInteractionService.pauseVoiceInteraction();
try {
final recognitionStream = await _asrService.startContinuousRecognition();
await Future.delayed(const Duration(milliseconds: 150));
isHoldSpeak.value = true;
recognizedText.value = '';
finalResult = '';
// 监听识别事件
_recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent,
onError: _handleRecognitionError,
);
} catch (e) {
Logger.error('启动语音识别失败', e);
isHoldSpeak.value = false;
}
}
// 松开停止说话
void talkLongPressUp() async {
_talkDebounceTimer?.cancel();
_talkDebounceTimer = Timer(const Duration(milliseconds: 200), () async {
if (!isHoldSpeak.value) return;
try {
await _asrService.stopContinuousRecognition();
_recognitionSubscription?.cancel();
_recognitionSubscription = null;
final intermediateIndex =
messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
if (intermediateIndex >= 0) {
messages.removeAt(intermediateIndex);
}
sendMessage(finalResult);
recognizedText.value = '';
finalResult = '';
isHoldSpeak.value = false;
} catch (e) {
Logger.error('停止语音识别失败', e);
}
});
}
// 开始语音识别 // 开始语音识别
Future<void> startVoiceRecognition() async { Future<void> startVoiceRecognition() async {
if (isVoiceRecognitionActive.value) return; if (isVoiceRecognitionActive.value) return;
@ -682,7 +745,7 @@ class ChatController extends GetxController {
if (intermediateIndex >= 0) { if (intermediateIndex >= 0) {
// 更新已有的中间状态消息 // 更新已有的中间状态消息
messages[intermediateIndex] = messages[intermediateIndex].copyWith( messages[intermediateIndex] = messages[intermediateIndex].copyWith(
content: text, content: isVoiceRecognitionActive.value ? text : finalResult + text,
); );
messages.refresh(); messages.refresh();
} else { } else {
@ -703,26 +766,44 @@ class ChatController extends GetxController {
if (text.isEmpty) return; if (text.isEmpty) return;
Logger.info('处理语音识别最终结果: $text'); Logger.info('处理语音识别最终结果: $text');
recognizedText.value = text;
// 更新活动时间 // 更新活动时间
_updateActivityTime(); _updateActivityTime();
// 查找并移除用户的中间状态消息 if (isVoiceRecognitionActive.value) {
final intermediateIndex = // 一直语音说话
messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
if (intermediateIndex >= 0) { recognizedText.value = text;
messages.removeAt(intermediateIndex); // 查找并移除用户的中间状态消息
} final intermediateIndex =
messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
if (intermediateIndex >= 0) {
messages.removeAt(intermediateIndex);
}
// 发送识别到的文本 // 发送识别到的文本
sendMessage(text); sendMessage(text);
// 重置识别文本,但不关闭语音识别框
recognizedText.value = '';
// 重置语音检测状态
isSpeechDetected.value = false;
} else {
// 按住说话
// 重置识别文本,但不关闭语音识别框 finalResult += text;
recognizedText.value = ''; recognizedText.value = finalResult;
// 重置语音检测状态 final intermediateIndex =
isSpeechDetected.value = false; messages.indexWhere((m) => m.isIntermediate && m.role == 'user');
if (intermediateIndex >= 0) {
// 更新已有的中间状态消息
messages[intermediateIndex] = messages[intermediateIndex].copyWith(
content: finalResult,
);
messages.refresh();
}
}
} }
// 处理语音识别事件错误 // 处理语音识别事件错误

65
lib/modules/chat/views/animated_gradient_background.dart

@ -0,0 +1,65 @@
import 'package:flutter/material.dart';
class AnimatedGradientBackground extends StatefulWidget {
const AnimatedGradientBackground({super.key});
@override
State<AnimatedGradientBackground> createState() =>
_AnimatedGradientBackgroundState();
}
class _AnimatedGradientBackgroundState extends State<AnimatedGradientBackground>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Alignment?> _beginAnimation;
late Animation<Alignment?> _endAnimation;
final Color color1 = const Color(0xFFfbcfdd);
final Color color2 = const Color(0xFFe2e9fb);
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(seconds: 10),
)..repeat(reverse: true);
// 创建起始点动画
_beginAnimation = AlignmentTween(
begin: Alignment.topCenter,
end: Alignment.bottomLeft,
).animate(_controller);
// 创建结束点动画
_endAnimation = AlignmentTween(
begin: Alignment.bottomCenter,
end: Alignment.topRight,
).animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [color1, color2],
begin: _beginAnimation.value!,
end: _endAnimation.value!,
),
),
);
},
);
}
}

44
lib/modules/chat/views/chat_navigation_card.dart

@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:amap_flutter/amap_flutter.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
@ -25,10 +24,7 @@ class _ChatNavigationCardState extends State<ChatNavigationCard> {
LatLng startPoint = LatLng(116.397428, 39.90923); // 起点坐标 LatLng startPoint = LatLng(116.397428, 39.90923); // 起点坐标
LatLng endPoint = LatLng(116.397026, 39.903303); // 终点坐标 LatLng endPoint = LatLng(116.397026, 39.903303); // 终点坐标
final _amapFlutter = AmapFlutter(); late Future _mapUrl;
StreamSubscription? _locationStream;
String _mapUrl = '';
@override @override
void initState() { void initState() {
@ -36,30 +32,18 @@ class _ChatNavigationCardState extends State<ChatNavigationCard> {
_getLocation(); _getLocation();
} }
@override void _getLocation() {
void dispose() { List startPointList = widget.data?['location'].split(',');
_amapFlutter.dispose();
_locationStream?.cancel();
super.dispose();
}
void _getLocation() async {
List endPointList = widget.data?['destination'].split(','); List endPointList = widget.data?['destination'].split(',');
startPoint = LatLng(
double.parse(startPointList[0]),
double.parse(startPointList[1]),
);
endPoint = LatLng( endPoint = LatLng(
double.parse(endPointList[0]), double.parse(endPointList[0]),
double.parse(endPointList[1]), double.parse(endPointList[1]),
); );
await _amapFlutter.initLocation(); _mapUrl = _getMapUrl();
_locationStream = _amapFlutter.locationStream().listen((data) async {
startPoint = LatLng(
data['longitude'],
data['latitude'],
);
_mapUrl = await _getMapUrl();
_amapFlutter.stopLocation();
setState(() {});
});
_amapFlutter.startLocation();
} }
Future<String> _getMapUrl() async { Future<String> _getMapUrl() async {
@ -193,9 +177,15 @@ class _ChatNavigationCardState extends State<ChatNavigationCard> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
_mapUrl.isNotEmpty FutureBuilder(
? Image.network(_mapUrl) future: _mapUrl,
: const CircularProgressIndicator(), builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Image.network(snapshot.data);
}
return const CircularProgressIndicator();
},
),
10.verticalSpace, 10.verticalSpace,
Text( Text(
widget.content, widget.content,

538
lib/modules/chat/views/chat_view.dart

@ -5,6 +5,7 @@ import 'dart:math';
import '../controllers/chat_controller.dart'; import '../controllers/chat_controller.dart';
import '../models/message_model.dart'; import '../models/message_model.dart';
import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_colors.dart';
import 'animated_gradient_background.dart';
import 'chat_music_card.dart'; import 'chat_music_card.dart';
import 'chat_navigation_card.dart'; import 'chat_navigation_card.dart';
import 'weather_day_card.dart'; import 'weather_day_card.dart';
@ -16,10 +17,17 @@ class ChatView extends GetView<ChatController> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold( return Obx(
backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], () => Scaffold(
appBar: _buildAppBar(isDarkMode), backgroundColor:
body: _buildBody(isDarkMode), isDarkMode ? AppColors.darkBackground : Colors.grey[50],
appBar: controller.isVoiceRecognitionActive.value
? null
: _buildAppBar(isDarkMode),
body: controller.isVoiceRecognitionActive.value
? _buildVoiceInputPanel(isDarkMode)
: _buildBody(isDarkMode),
),
); );
} }
@ -47,7 +55,7 @@ class ChatView extends GetView<ChatController> {
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.1),
blurRadius: 3, blurRadius: 3,
offset: Offset(0, 1), offset: const Offset(0, 1),
), ),
], ],
), ),
@ -92,7 +100,7 @@ class ChatView extends GetView<ChatController> {
Container( Container(
width: 6.r, width: 6.r,
height: 6.r, height: 6.r,
decoration: BoxDecoration( decoration: const BoxDecoration(
color: Colors.green, color: Colors.green,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
@ -113,6 +121,13 @@ class ChatView extends GetView<ChatController> {
], ],
), ),
actions: [ actions: [
IconButton(
icon: Icon(
Icons.phone,
color: isDarkMode ? Colors.white : Colors.black87,
),
onPressed: controller.startVoiceRecognition,
),
// TTS开关 // TTS开关
Obx(() => IconButton( Obx(() => IconButton(
icon: Icon( icon: Icon(
@ -157,7 +172,7 @@ class ChatView extends GetView<ChatController> {
value: 'clear', value: 'clear',
child: Row( child: Row(
children: [ children: [
Icon(Icons.delete_outline, color: Colors.red), const Icon(Icons.delete_outline, color: Colors.red),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text( Text(
'clearChatHistory'.tr, // 清空聊天记录 'clearChatHistory'.tr, // 清空聊天记录
@ -198,7 +213,7 @@ class ChatView extends GetView<ChatController> {
), ),
// 底部输入区域 // 底部输入区域
_buildInputArea(isDarkMode), _buildTextInputPanel(isDarkMode),
], ],
); );
} }
@ -370,139 +385,128 @@ class ChatView extends GetView<ChatController> {
return box; return box;
} }
// 构建输入区域 // 构建文本输入面板
Widget _buildInputArea(bool isDarkMode) { Widget _buildTextInputPanel(bool isDarkMode) {
final textController = final textController =
TextEditingController(text: controller.inputText.value); TextEditingController(text: controller.inputText.value);
// 检查是否处于语音识别模式
return Obx(() {
if (controller.isVoiceRecognitionActive.value) {
return _buildVoiceInputPanel(isDarkMode);
} else {
return _buildTextInputPanel(isDarkMode, textController);
}
});
}
// 构建文本输入面板
Widget _buildTextInputPanel(
bool isDarkMode, TextEditingController textController) {
return Container( return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), height: 48.w,
margin: EdgeInsets.only(left: 16.w, right: 16.w, top: 10.w, bottom: 20.w),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? AppColors.cardBackground : Colors.white, color: controller.isHoldSpeak.value
? AppColors.primary
: isDarkMode
? AppColors.cardBackground
: Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: Colors.black.withOpacity(0.05),
blurRadius: 3, blurRadius: 3,
offset: Offset(0, -1), offset: const Offset(0, -1),
), ),
], ],
), ),
child: SafeArea( child: Row(
child: Row( children: [
children: [ Expanded(
// 语音识别按钮 child: Obx(
Container( () => controller.isTextInput.value
width: 40.w, ? Center(
height: 40.w, child: TextField(
decoration: BoxDecoration( controller: textController,
color: isDarkMode style: TextStyle(
? Colors.white.withOpacity(0.1) fontSize: 15.sp,
: Colors.grey[100], color: isDarkMode ? Colors.white : Colors.black87,
shape: BoxShape.circle, ),
), textInputAction: TextInputAction.send,
child: IconButton( decoration: InputDecoration(
icon: Icon( hintText: 'inputMessage'.tr, // 输入消息...
Icons.mic_none, hintStyle: TextStyle(color: Colors.grey[500]),
color: isDarkMode ? Colors.white : Colors.grey[700], filled: false,
size: 20.r, border: InputBorder.none,
), focusedBorder: InputBorder.none,
padding: EdgeInsets.zero, contentPadding: EdgeInsets.only(left: 10.w),
onPressed: controller.startVoiceRecognition, ),
), onSubmitted: (value) {
if (value.trim().isNotEmpty) {
controller.sendMessage(value);
textController.clear();
controller.inputText.value = ''; // 清空输入框
}
},
),
)
: Listener(
onPointerDown: (event) {
controller.talkLongPress();
},
onPointerUp: (event) {
controller.talkLongPressUp();
},
child: Container(
padding: EdgeInsets.only(
left: controller.isHoldSpeak.value ? 0 : 46.w),
color: Colors.transparent,
child: controller.isHoldSpeak.value
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(40, (int index) {
return Container(
width: 3.w,
height: 10.w,
margin:
EdgeInsets.symmetric(horizontal: 2.w),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(3.r),
),
);
}),
)
: Center(
child: Text(
'按住说话',
style: TextStyle(
fontSize: 16.sp,
color: isDarkMode
? Colors.white
: Colors.black87,
),
),
),
),
),
), ),
),
SizedBox(width: 8.w), if (!controller.isHoldSpeak.value)
GestureDetector(
// 输入框 onTap: () {
Expanded( controller.isTextInput.value = !controller.isTextInput.value;
child: TextField( },
controller: textController, child: Container(
decoration: InputDecoration( width: 30.w,
hintText: 'inputMessage'.tr, // 输入消息... height: 30.w,
hintStyle: TextStyle( margin: EdgeInsets.all(8.w),
color: isDarkMode ? Colors.grey[500] : Colors.grey[500]), alignment: Alignment.center,
filled: true, decoration: BoxDecoration(
fillColor: isDarkMode borderRadius: BorderRadius.circular(30.r),
? Colors.white.withOpacity(0.1) border: Border.all(
: Colors.grey[100], color: isDarkMode ? Colors.white : Colors.black87,
border: OutlineInputBorder( width: 2,
borderRadius: BorderRadius.circular(20.r),
borderSide: BorderSide.none,
),
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 10.h,
), ),
), ),
maxLines: null, child: Obx(
textInputAction: TextInputAction.newline, () => Icon(
style: TextStyle( controller.isTextInput.value ? Icons.mic : Icons.keyboard,
fontSize: 15.sp, size: 20.w,
color: isDarkMode ? Colors.white : Colors.black87, ),
), ),
onChanged: (value) => controller.inputText.value = value,
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
controller.sendMessage(value);
textController.clear();
controller.inputText.value = ''; // 清空输入框
}
},
), ),
), ),
],
SizedBox(width: 8.w),
// 发送按钮
Obx(() {
final hasText = controller.inputText.value.isNotEmpty;
return Container(
width: 40.w,
height: 40.w,
decoration: BoxDecoration(
color: hasText
? AppColors.primary
: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.grey[200],
shape: BoxShape.circle,
),
child: IconButton(
icon: Icon(
Icons.send,
color: hasText
? Colors.white
: isDarkMode
? Colors.white.withOpacity(0.5)
: Colors.grey[400],
size: 18.r,
),
padding: EdgeInsets.zero,
onPressed: hasText
? () {
controller.sendMessage(controller.inputText.value);
textController.clear();
controller.inputText.value = ''; // 清空输入框
}
: null,
),
);
}),
],
),
), ),
); );
} }
@ -512,158 +516,166 @@ class ChatView extends GetView<ChatController> {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode ? AppColors.cardBackground : Colors.white, color: isDarkMode ? AppColors.cardBackground : Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
offset: Offset(0, -1),
),
],
), ),
child: SafeArea( child: SafeArea(
child: SizedBox( child: Column(
height: 80.h, children: [
child: Padding( Row(
padding: EdgeInsets.symmetric(horizontal: 20.w), children: [],
child: Row( ),
mainAxisAlignment: MainAxisAlignment.spaceBetween, Expanded(
crossAxisAlignment: CrossAxisAlignment.center, child: AnimatedGradientBackground(),
children: [ ),
// 语音录制按钮 SizedBox(
Container( height: 80.h,
width: 60.r, child: Padding(
height: 60.r, padding: EdgeInsets.symmetric(horizontal: 20.w),
decoration: BoxDecoration( child: Row(
color: isDarkMode mainAxisAlignment: MainAxisAlignment.spaceBetween,
? Colors.white.withOpacity(0.1) crossAxisAlignment: CrossAxisAlignment.center,
: Colors.white, children: [
shape: BoxShape.circle, // 语音录制按钮
boxShadow: [ Container(
BoxShadow( width: 60.r,
color: Colors.black.withOpacity(0.1), height: 60.r,
blurRadius: 8, decoration: BoxDecoration(
spreadRadius: 1, color: isDarkMode
? Colors.white.withOpacity(0.1)
: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
spreadRadius: 1,
),
],
), ),
], child: Center(
), child: Obx(() => Icon(
child: Center( controller.isListening.value
child: Obx(() => Icon( ? Icons.mic
controller.isListening.value : Icons.mic_none,
? Icons.mic color: controller.isListening.value
: Icons.mic_none, ? AppColors.primary
color: controller.isListening.value : Colors.grey.shade400,
? AppColors.primary size: 28.r,
: Colors.grey.shade400, )),
size: 28.r, ),
)), ),
),
),
// 中间音频波形和状态文本 // 中间音频波形和状态文本
Expanded( Expanded(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
// 音频波形区域 // 音频波形区域
Container( Container(
height: 50.h, height: 50.h,
child: Obx(() { child: Obx(() {
final isSpeaking = controller.isSpeechDetected.value; final isSpeaking =
return Center( controller.isSpeechDetected.value;
child: Container( return Center(
width: 220.w, child: Container(
height: 30.h, width: 220.w,
child: Row( height: 30.h,
mainAxisSize: MainAxisSize.min, child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
children: List.generate( mainAxisAlignment: MainAxisAlignment.center,
23, children: List.generate(
(index) { 23,
// 随机高度模拟波形 (index) {
double height = isSpeaking // 随机高度模拟波形
? (Random().nextDouble() * 20.0 + 5.0).h double height = isSpeaking
: 2.h; ? (Random().nextDouble() * 20.0 +
5.0)
.h
: 2.h;
return SizedBox( return SizedBox(
width: 6.w, width: 6.w,
child: Center( child: Center(
child: AnimatedContainer( child: AnimatedContainer(
duration: Duration(milliseconds: 500), duration: const Duration(
width: 2.5.w, milliseconds: 500),
height: height, width: 2.5.w,
margin: EdgeInsets.symmetric( height: height,
horizontal: 1.5.w), margin: EdgeInsets.symmetric(
decoration: BoxDecoration( horizontal: 1.5.w),
color: isSpeaking decoration: BoxDecoration(
? AppColors.primary color: isSpeaking
: isDarkMode ? AppColors.primary
? Colors.grey.shade600 : isDarkMode
: Colors.grey.shade400, ? Colors.grey.shade600
borderRadius: : Colors.grey.shade400,
BorderRadius.circular(2.r), borderRadius:
BorderRadius.circular(2.r),
),
),
), ),
), );
), },
); ),
}, ),
), ),
), );
), }),
); ),
}),
),
SizedBox(height: 8.h),
// 语音状态文本 // 语音状态文本
Obx(() => Text( Obx(() => Text(
controller.isSpeechDetected.value controller.isSpeechDetected.value
? 'imListening'.tr // 正在聆听... ? 'imListening'.tr // 正在聆听...
: 'canStartTalking'.tr, // 你可以开始说话 : 'canStartTalking'.tr, // 你可以开始说话
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: isDarkMode color: isDarkMode
? Colors.grey[400] ? Colors.grey[400]
: Colors.grey[700], : Colors.grey[700],
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
), ),
)), )),
], ],
), ),
), ),
// 关闭按钮 // 关闭按钮
Container( Container(
width: 60.r, width: 60.r,
height: 60.r, height: 60.r,
decoration: BoxDecoration( decoration: BoxDecoration(
color: isDarkMode color: isDarkMode
? Colors.white.withOpacity(0.1) ? Colors.white.withOpacity(0.1)
: Colors.white, : Colors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.1), color: Colors.black.withOpacity(0.1),
blurRadius: 8, blurRadius: 8,
spreadRadius: 1, spreadRadius: 1,
),
],
), ),
], child: Center(
), child: IconButton(
child: Center( icon: Icon(
child: IconButton( Icons.close,
icon: Icon( color: isDarkMode
Icons.close, ? Colors.white
color: isDarkMode ? Colors.white : Colors.grey.shade600, : Colors.grey.shade600,
size: 28.r, size: 28.r,
),
padding: EdgeInsets.zero,
onPressed: controller.stopVoiceRecognition,
),
), ),
padding: EdgeInsets.zero,
onPressed: controller.stopVoiceRecognition,
), ),
), ],
), ),
], ),
), ),
), ],
), ),
), ),
); );
@ -683,7 +695,7 @@ class ChatView extends GetView<ChatController> {
BoxShadow( BoxShadow(
color: Colors.black.withOpacity(0.05), color: Colors.black.withOpacity(0.05),
blurRadius: 2, blurRadius: 2,
offset: Offset(0, 1), offset: const Offset(0, 1),
), ),
], ],
), ),
@ -721,7 +733,7 @@ class ChatView extends GetView<ChatController> {
// 构建加载动画点 // 构建加载动画点
Widget _buildLoadingDot(int index, bool isDarkMode) { Widget _buildLoadingDot(int index, bool isDarkMode) {
return AnimatedBuilder( return AnimatedBuilder(
animation: AlwaysStoppedAnimation(0), animation: const AlwaysStoppedAnimation(0),
builder: (context, child) { builder: (context, child) {
return Container( return Container(
margin: EdgeInsets.symmetric(horizontal: 2.w), margin: EdgeInsets.symmetric(horizontal: 2.w),

Loading…
Cancel
Save