Compare commits
4 Commits
0af37c5b87
...
aba8b44ab8
| Author | SHA1 | Date |
|---|---|---|
|
|
aba8b44ab8 | |
|
|
ac234d99ec | |
|
|
3e8fe73e27 | |
|
|
6146508bed |
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import '../interfaces/speech_recognition_service.dart';
|
import '../interfaces/speech_recognition_service.dart';
|
||||||
import '../yx_asr_service.dart';
|
import '../yx_asr_service.dart';
|
||||||
import '../models/speech_recognition_result.dart';
|
import '../models/speech_recognition_result.dart';
|
||||||
|
|
@ -69,6 +70,7 @@ class _RecordingButtonState extends State<RecordingButton>
|
||||||
late SpeechRecognitionService _speechService;
|
late SpeechRecognitionService _speechService;
|
||||||
bool _isListening = false;
|
bool _isListening = false;
|
||||||
bool _isInitialized = false;
|
bool _isInitialized = false;
|
||||||
|
bool _isProcessing = false; // 防抖标志
|
||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
late Animation<double> _scaleAnimation;
|
late Animation<double> _scaleAnimation;
|
||||||
|
|
||||||
|
|
@ -146,13 +148,29 @@ class _RecordingButtonState extends State<RecordingButton>
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _toggleRecording() async {
|
Future<void> _toggleRecording() async {
|
||||||
if (!_isInitialized || !widget.enabled) return;
|
// 防抖检查:如果正在处理中或未初始化或被禁用,则直接返回
|
||||||
|
if (_isProcessing || !_isInitialized || !widget.enabled) return;
|
||||||
|
|
||||||
|
// 设置处理中标志,防止重复点击
|
||||||
|
setState(() {
|
||||||
|
_isProcessing = true;
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 触觉反馈
|
||||||
|
await HapticFeedback.lightImpact();
|
||||||
|
|
||||||
|
// 播放点击动画
|
||||||
|
_animationController.forward().then((_) {
|
||||||
|
_animationController.reverse();
|
||||||
|
});
|
||||||
|
|
||||||
if (_isListening) {
|
if (_isListening) {
|
||||||
await _speechService.stopListening();
|
await _speechService.stopListening();
|
||||||
} else {
|
} else {
|
||||||
await _speechService.startListening(partialResults: widget.partialResults);
|
await _speechService.startListening(
|
||||||
|
partialResults: widget.partialResults,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
widget.onError?.call(SpeechRecognitionError(
|
widget.onError?.call(SpeechRecognitionError(
|
||||||
|
|
@ -160,51 +178,74 @@ class _RecordingButtonState extends State<RecordingButton>
|
||||||
errorMsg: '切换录音状态失败: $e',
|
errorMsg: '切换录音状态失败: $e',
|
||||||
errorCode: null,
|
errorCode: null,
|
||||||
));
|
));
|
||||||
|
} finally {
|
||||||
|
// 延迟重置处理标志,确保有足够的防抖时间
|
||||||
|
Future.delayed(const Duration(milliseconds: 300), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isProcessing = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
Color iconColor;
|
||||||
|
|
||||||
Color buttonColor;
|
|
||||||
if (!widget.enabled || !_isInitialized) {
|
if (!widget.enabled || !_isInitialized) {
|
||||||
buttonColor = widget.disabledColor ?? Colors.grey;
|
iconColor = widget.disabledColor ?? Colors.grey[850]!;
|
||||||
} else if (_isListening) {
|
|
||||||
buttonColor = widget.recordingColor ?? Colors.red;
|
|
||||||
} else {
|
} else {
|
||||||
buttonColor = widget.idleColor ?? theme.primaryColor;
|
iconColor = _isListening
|
||||||
|
? (widget.recordingColor ?? const Color(0xFFFF5252))
|
||||||
|
: (widget.idleColor ?? const Color(0xFF2196F3));
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget button = AnimatedBuilder(
|
Widget button = AnimatedBuilder(
|
||||||
animation: _scaleAnimation,
|
animation: _scaleAnimation,
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return Transform.scale(
|
return Container(
|
||||||
scale: _scaleAnimation.value,
|
width: widget.size,
|
||||||
child: Container(
|
height: widget.size,
|
||||||
width: widget.size,
|
decoration: BoxDecoration(
|
||||||
height: widget.size,
|
color: iconColor.withValues(alpha: 0.12),
|
||||||
decoration: BoxDecoration(
|
shape: BoxShape.circle,
|
||||||
color: buttonColor,
|
),
|
||||||
shape: BoxShape.circle,
|
child: Material(
|
||||||
boxShadow: [
|
color: Colors.transparent,
|
||||||
BoxShadow(
|
child: InkWell(
|
||||||
color: buttonColor.withValues(alpha: 0.3),
|
borderRadius: BorderRadius.circular(widget.size / 2),
|
||||||
blurRadius: _isListening ? 20 : 8,
|
onTap: _toggleRecording,
|
||||||
spreadRadius: _isListening ? 5 : 2,
|
child: Stack(
|
||||||
),
|
children: [
|
||||||
],
|
Positioned(
|
||||||
),
|
left: 0,
|
||||||
child: Material(
|
right: 0,
|
||||||
color: Colors.transparent,
|
bottom: 0,
|
||||||
child: InkWell(
|
top: 0,
|
||||||
borderRadius: BorderRadius.circular(widget.size / 2),
|
child: Icon(
|
||||||
onTap: _toggleRecording,
|
_isListening ? Icons.stop_rounded : Icons.mic_rounded,
|
||||||
child: Icon(
|
size: widget.size * 0.55,
|
||||||
_isListening ? Icons.stop : Icons.mic,
|
color: iconColor,
|
||||||
size: widget.size * 0.4,
|
),
|
||||||
color: Colors.white,
|
),
|
||||||
),
|
if (_isProcessing || _isListening)
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
top: 0,
|
||||||
|
child: SizedBox(
|
||||||
|
width: widget.size,
|
||||||
|
height: widget.size,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
strokeWidth: 2,
|
||||||
|
valueColor:
|
||||||
|
AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue