Commit 293c0e0a536592ddcc61fdfa4e70217f27b07836

Authored by weixin_45529955
1 parent e4c3c584

阿里云语音识别插件(安卓)OK demo

@@ -4,7 +4,13 @@ @@ -4,7 +4,13 @@
4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 "version": "0.2.0", 5 "version": "0.2.0",
6 "configurations": [ 6 "configurations": [
7 - 7 + {
  8 + "name": "Launch Chrome",
  9 + "request": "launch",
  10 + "type": "chrome",
  11 + "url": "http://localhost:8080",
  12 + "webRoot": "${workspaceFolder}"
  13 + },
8 { 14 {
9 "name": "Flutter-ReserveApp", 15 "name": "Flutter-ReserveApp",
10 "request": "launch", 16 "request": "launch",
@@ -363,6 +369,26 @@ @@ -363,6 +369,26 @@
363 "flutterMode": "release" 369 "flutterMode": "release"
364 }, 370 },
365 { 371 {
  372 + "name": "speech_to_text_alibaba",
  373 + "cwd": "library/speech_to_text_alibaba",
  374 + "request": "launch",
  375 + "type": "dart"
  376 + },
  377 + {
  378 + "name": "speech_to_text_alibaba (profile mode)",
  379 + "cwd": "library/speech_to_text_alibaba",
  380 + "request": "launch",
  381 + "type": "dart",
  382 + "flutterMode": "profile"
  383 + },
  384 + {
  385 + "name": "speech_to_text_alibaba (release mode)",
  386 + "cwd": "library/speech_to_text_alibaba",
  387 + "request": "launch",
  388 + "type": "dart",
  389 + "flutterMode": "release"
  390 + },
  391 + {
366 "name": "speech_to_text_wdn_ohos", 392 "name": "speech_to_text_wdn_ohos",
367 "cwd": "library/speech_to_text_wdn_ohos", 393 "cwd": "library/speech_to_text_wdn_ohos",
368 "request": "launch", 394 "request": "launch",
@@ -744,6 +770,26 @@ @@ -744,6 +770,26 @@
744 }, 770 },
745 { 771 {
746 "name": "example", 772 "name": "example",
  773 + "cwd": "library/speech_to_text_alibaba/example",
  774 + "request": "launch",
  775 + "type": "dart"
  776 + },
  777 + {
  778 + "name": "example (profile mode)",
  779 + "cwd": "library/speech_to_text_alibaba/example",
  780 + "request": "launch",
  781 + "type": "dart",
  782 + "flutterMode": "profile"
  783 + },
  784 + {
  785 + "name": "example (release mode)",
  786 + "cwd": "library/speech_to_text_alibaba/example",
  787 + "request": "launch",
  788 + "type": "dart",
  789 + "flutterMode": "release"
  790 + },
  791 + {
  792 + "name": "example",
747 "cwd": "library/speech_to_text_wdn_ohos/example", 793 "cwd": "library/speech_to_text_wdn_ohos/example",
748 "request": "launch", 794 "request": "launch",
749 "type": "dart" 795 "type": "dart"
@@ -20,19 +20,19 @@ if (flutterRoot == null) { @@ -20,19 +20,19 @@ if (flutterRoot == null) {
20 20
21 21
22 22
23 -def flutterVersionCode = "610" 23 +def flutterVersionCode = "611"
24 if (flutterVersionCode == null) { 24 if (flutterVersionCode == null) {
25 - flutterVersionCode = '610' 25 + flutterVersionCode = '611'
26 26
27 27
28 28
29 } 29 }
30 30
31 31
32 -def flutterVersionName = "6.1.0" 32 +def flutterVersionName = "6.1.1"
33 33
34 if (flutterVersionName == null) { 34 if (flutterVersionName == null) {
35 - flutterVersionName = '6.1.0' 35 + flutterVersionName = '6.1.1'
36 36
37 } 37 }
38 38
  1 +class PhoneNumberExtractor {
  2 + // 提取所有电话号码(手机+座机)
  3 + static List<String> extractAllPhoneNumbers(String text) {
  4 + final phoneRegExp = RegExp(r'(?:(?:\+|00)86)?1[3-9]\d{9}|' // 手机
  5 + r'(?:(?:0\d{2,3}[- ]?)?\d{7,8}(?:[- ]?\d{1,4})?)' // 座机
  6 + );
  7 +
  8 + return phoneRegExp
  9 + .allMatches(text)
  10 + .map((match) => match.group(0)!)
  11 + .toList();
  12 + }
  13 +
  14 + // 提取手机号码
  15 + static List<String> extractMobileNumbers(String text) {
  16 + final mobileRegExp = RegExp(r'(?:(?:\+|00)86)?' // 国际前缀
  17 + r'1[3-9]\d{9}' // 手机号段
  18 + );
  19 +
  20 + return mobileRegExp
  21 + .allMatches(text)
  22 + .map((match) => match.group(0)!)
  23 + .toList();
  24 + }
  25 +
  26 + // 提取座机号码
  27 + static List<String> extractLandlineNumbers(String text) {
  28 + final landlineRegExp = RegExp(r'(?:0\d{2,3}[- ]?)?' // 区号
  29 + r'\d{7,8}' // 主号码
  30 + r'(?:[- ]?\d{1,4})?' // 分机号
  31 + );
  32 +
  33 + return landlineRegExp
  34 + .allMatches(text)
  35 + .map((match) => match.group(0)!)
  36 + .toList();
  37 + }
  38 +
  39 + // 分类提取
  40 + static Map<String, List<String>> extractPhoneNumbersByType(String text) {
  41 + return {
  42 + 'mobile': extractMobileNumbers(text),
  43 + 'landline': extractLandlineNumbers(text),
  44 + 'all': extractAllPhoneNumbers(text),
  45 + };
  46 + }
  47 +
  48 + // 验证并格式化座机号码
  49 + static String? formatLandlineNumber(String number) {
  50 + // 清理输入
  51 + String cleaned = number.replaceAll(RegExp(r'[^\d\-]'), '');
  52 +
  53 + // 验证格式
  54 + final validRegExp = RegExp(r'^(0\d{2,3})?\d{7,8}$');
  55 +
  56 + if (!validRegExp.hasMatch(cleaned)) {
  57 + return null;
  58 + }
  59 +
  60 + // 格式化:添加分隔符
  61 + if (cleaned.length >= 11) {
  62 + // 有区号的情况
  63 + return '${cleaned.substring(0, 4)}-${cleaned.substring(4)}';
  64 + } else if (cleaned.length == 7 || cleaned.length == 8) {
  65 + // 无区号的情况
  66 + return cleaned;
  67 + }
  68 +
  69 + return cleaned;
  70 + }
  71 +
  72 + // 支持更多格式的座机号码
  73 +
  74 +// 提取函数
  75 + static List<String> extractFlexibleLandlines(String text) {
  76 + final flexibleLandlineRegExp = RegExp(
  77 + r'('
  78 + r'(?:0(?:10|2[0-57-9]|[3-9]\d{2}))?' // 区号(010、021-029等)
  79 + r'[- ]?' // 分隔符
  80 + r'(?:\d{7,8})' // 主号码
  81 + r'(?:[- ]?(?:转|分机|ext?\.?)?[- ]?\d{1,4})?' // 分机号
  82 + r')',
  83 + caseSensitive: false // 不区分大小写
  84 + );
  85 + return flexibleLandlineRegExp
  86 + .allMatches(text)
  87 + .map((match) => match.group(0)!)
  88 + .toList();
  89 + }
  90 +}
  91 +
  92 +
  93 +// 使用事例
  94 +
  95 +// final testText = '''
  96 +// 联系方式:
  97 +// 手机:13800138000、+8613800138001、008613800138002
  98 +// 座机:010-12345678、021-87654321、0755-1234567
  99 +// 带分机:028-87654321-1234
  100 +// 无区号:12345678
  101 +// 其他格式:(010)12345678、010 1234 5678
  102 +// ''';
  103 +
  104 +// // 提取所有电话号码
  105 +// final allNumbers = PhoneNumberExtractor.extractAllPhoneNumbers(testText);
  106 +// print('所有电话号码: $allNumbers');
  107 +
  108 +// // 只提取座机
  109 +// final landlines = PhoneNumberExtractor.extractLandlineNumbers(testText);
  110 +// print('座机号码: $landlines');
  111 +
  112 +// // 只提取手机
  113 +// final mobiles = PhoneNumberExtractor.extractMobileNumbers(testText);
  114 +// print('手机号码: $mobiles');
  115 +
  116 +// // 分类提取
  117 +// final categorized = PhoneNumberExtractor.extractPhoneNumbersByType(testText);
  118 +// print('分类结果: $categorized');
  119 +
  120 +// // 格式化示例
  121 +// final formatted = PhoneNumberExtractor.formatLandlineNumber('01012345678');
  122 +// print('格式化后: $formatted'); // 输出: 010-12345678
  123 +// }
  1 +import 'package:intl/intl.dart';
  2 +
  3 +class ChineseDateUtils {
  4 + // 提取文本中所有日期
  5 + static List<DateTime> extractAllDates(String text) {
  6 + List<DateTime> dates = [];
  7 +
  8 + // 匹配中文日期
  9 + dates.addAll(_extractChineseDates(text));
  10 +
  11 + // 匹配数字日期
  12 + dates.addAll(_extractNumericDates(text));
  13 +
  14 + // 去重
  15 + return dates.toSet().toList();
  16 + }
  17 +
  18 + // 提取第一个找到的日期
  19 + static DateTime? extractFirstDate(String text) {
  20 + final dates = extractAllDates(text);
  21 + return dates.isNotEmpty ? dates.first : null;
  22 + }
  23 +
  24 + // 提取中文日期
  25 + static List<DateTime> _extractChineseDates(String text) {
  26 + List<DateTime> dates = [];
  27 +
  28 + // 支持多种格式
  29 + final patterns = [
  30 + RegExp(r'([正元一二三四五六七八九十腊]{1,2})月([一二三四五六七八九十]{1,3})[日号]'),
  31 + RegExp(r'([正元一二三四五六七八九十腊]{1,2})月([初廿卅一二三四五六七八九十]{1,3})'),
  32 + ];
  33 +
  34 + for (var pattern in patterns) {
  35 + final matches = pattern.allMatches(text);
  36 +
  37 + for (var match in matches) {
  38 + final date = _parseChineseDateMatch(match);
  39 + if (date != null) {
  40 + dates.add(date);
  41 + }
  42 + }
  43 + }
  44 +
  45 + return dates;
  46 + }
  47 +
  48 + // 提取数字日期
  49 + static List<DateTime> _extractNumericDates(String text) {
  50 + List<DateTime> dates = [];
  51 +
  52 + final patterns = [
  53 + RegExp(r'(\d{1,2})[月\-/](\d{1,2})[日号]?'),
  54 + RegExp(r'(\d{4})年(\d{1,2})月(\d{1,2})日'),
  55 + ];
  56 +
  57 + for (var pattern in patterns) {
  58 + final matches = pattern.allMatches(text);
  59 +
  60 + for (var match in matches) {
  61 + DateTime? date;
  62 +
  63 + if (match.groupCount == 2) {
  64 + // 月日格式
  65 + int? month = int.tryParse(match.group(1)!);
  66 + int? day = int.tryParse(match.group(2)!);
  67 +
  68 + if (month != null && day != null) {
  69 + date = _createDate(month, day);
  70 + }
  71 + } else if (match.groupCount == 3) {
  72 + // 年月日格式
  73 + int? year = int.tryParse(match.group(1)!);
  74 + int? month = int.tryParse(match.group(2)!);
  75 + int? day = int.tryParse(match.group(3)!);
  76 +
  77 + if (year != null && month != null && day != null) {
  78 + date = DateTime(year, month, day);
  79 + }
  80 + }
  81 +
  82 + if (date != null) {
  83 + dates.add(date);
  84 + }
  85 + }
  86 + }
  87 +
  88 + return dates;
  89 + }
  90 +
  91 + static DateTime? _parseChineseDateMatch(Match match) {
  92 + final monthStr = match.group(1);
  93 + final dayStr = match.group(2);
  94 +
  95 + if (monthStr == null || dayStr == null) return null;
  96 +
  97 + final month = _chineseToMonth(monthStr);
  98 + final day = _chineseToDay(dayStr);
  99 +
  100 + if (month == null || day == null) return null;
  101 +
  102 + return _createDate(month, day);
  103 + }
  104 +
  105 + static int? _chineseToMonth(String chinese) {
  106 + final Map<String, int> map = {
  107 + '正': 1, '元': 1, '一': 1, '二': 2, '三': 3, '四': 4,
  108 + '五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10,
  109 + '十一': 11, '腊': 12, '十二': 12
  110 + };
  111 +
  112 + return map[chinese];
  113 + }
  114 +
  115 + static int? _chineseToDay(String chinese) {
  116 + // 处理农历日期前缀
  117 + if (chinese.startsWith('初')) {
  118 + return parseChineseNumber(chinese.substring(1));
  119 + } else if (chinese.startsWith('廿')) {
  120 + final rest = chinese.substring(1);
  121 + return 20 + (parseChineseNumber(rest) ?? 0);
  122 + } else if (chinese.startsWith('卅')) {
  123 + final rest = chinese.substring(1);
  124 + return 30 + (parseChineseNumber(rest) ?? 0);
  125 + }
  126 +
  127 + return parseChineseNumber(chinese);
  128 + }
  129 +
  130 + static int? parseChineseNumber(String chinese) {
  131 + if (chinese.isEmpty) return null;
  132 +
  133 + final Map<String, int> singleDigits = {
  134 + '一': 1, '二': 2, '三': 3, '四': 4, '五': 5,
  135 + '六': 6, '七': 7, '八': 8, '九': 9, '十': 10
  136 + };
  137 +
  138 + // 处理简单数字
  139 + if (singleDigits.containsKey(chinese)) {
  140 + return singleDigits[chinese];
  141 + }
  142 +
  143 + // 处理组合数字
  144 + if (chinese.contains('十')) {
  145 + final parts = chinese.split('十');
  146 +
  147 + if (parts.length == 2) {
  148 + int? tens = parts[0].isEmpty ? 1 : singleDigits[parts[0]];
  149 + int? ones = parts[1].isEmpty ? 0 : singleDigits[parts[1]];
  150 +
  151 + if (tens != null && ones != null) {
  152 + return tens * 10 + ones;
  153 + }
  154 + }
  155 + }
  156 +
  157 + return null;
  158 + }
  159 +
  160 + static DateTime? _createDate(int month, int day) {
  161 + try {
  162 + final now = DateTime.now();
  163 + final year = now.year;
  164 +
  165 + // 验证日期有效性
  166 + final lastDay = DateTime(year, month + 1, 0).day;
  167 + if (day < 1 || day > lastDay) return null;
  168 +
  169 + return DateTime(year, month, day);
  170 + } catch (e) {
  171 + return null;
  172 + }
  173 + }
  174 +
  175 + // 格式化日期输出
  176 + static String formatDate(DateTime date, {String format = 'yyyy年MM月dd日'}) {
  177 + return DateFormat(format).format(date);
  178 + }
  179 +
  180 + // 判断是否为有效日期
  181 + static bool isValidDate(int year, int month, int day) {
  182 + if (month < 1 || month > 12) return false;
  183 + if (day < 1) return false;
  184 +
  185 + final lastDay = DateTime(year, month + 1, 0).day;
  186 + return day <= lastDay;
  187 + }
  188 +}
@@ -26,8 +26,31 @@ class _LongPressFABState extends State<LongPressFAB> @@ -26,8 +26,31 @@ class _LongPressFABState extends State<LongPressFAB>
26 showModalBottomSheet( 26 showModalBottomSheet(
27 backgroundColor: Colors.transparent, 27 backgroundColor: Colors.transparent,
28 context: context, 28 context: context,
  29 + isScrollControlled: true, // 允许滚动控制
29 builder: (context) { 30 builder: (context) {
30 - return SpeechPage(); 31 + return MediaQuery.removePadding(
  32 + context: context,
  33 + removeTop: true,
  34 + child: Container(
  35 + height: MediaQuery.of(context).size.height *
  36 + 0.8, // 例如设置高度为屏幕的40%
  37 + color: Colors.white,
  38 +
  39 + padding: EdgeInsets.symmetric(horizontal: w(30)),
  40 + child: SpeechPage(),
  41 + ));
  42 + // return DraggableScrollableSheet(
  43 + // initialChildSize: 0.8, // 初始高度为屏幕的40%
  44 + // minChildSize: 0.8, // 最小高度为屏幕的20%
  45 + // maxChildSize: 0.8, // 最大高度为屏幕的80%
  46 + // builder: (BuildContext context,
  47 + // ScrollController scrollController) {
  48 + // return Container(
  49 + // color: Colors.white,
  50 + // padding: EdgeInsets.symmetric(horizontal: w(30)),
  51 + // child: SpeechPage(),
  52 + // );
  53 + // });
31 }); 54 });
32 }, 55 },
33 onLongPressStart: (_) async { 56 onLongPressStart: (_) async {
1 import 'package:common_utils/common_utils.dart'; 1 import 'package:common_utils/common_utils.dart';
2 import 'package:flutter/material.dart'; 2 import 'package:flutter/material.dart';
3 import 'package:flutter_screenutil/flutter_screenutil.dart'; 3 import 'package:flutter_screenutil/flutter_screenutil.dart';
  4 +import 'package:one_key/pages/home/%20phone_number_extractor.dart';
  5 +import 'package:one_key/pages/home/chinese_date_utils.dart';
  6 +import 'package:one_key/pages/home/wave_form_widget.dart';
4 import 'package:one_key/res/colors.dart'; 7 import 'package:one_key/res/colors.dart';
  8 +import 'package:one_key/utils/dialog_util.dart';
  9 +import 'package:one_key/utils/misc.dart';
5 import 'package:one_key/utils/utils.dart'; 10 import 'package:one_key/utils/utils.dart';
6 import 'package:one_key/widgets/round_button.dart'; 11 import 'package:one_key/widgets/round_button.dart';
7 import 'package:one_key/widgets/v_empty_view.dart'; 12 import 'package:one_key/widgets/v_empty_view.dart';
@@ -22,6 +27,20 @@ class SpeechPageState extends State<SpeechPage> { @@ -22,6 +27,20 @@ class SpeechPageState extends State<SpeechPage> {
22 List<dynamic> resultDate = []; 27 List<dynamic> resultDate = [];
23 28
24 List<dynamic> resultPnum = []; 29 List<dynamic> resultPnum = [];
  30 +
  31 + List<dynamic> resultName = [];
  32 +
  33 + List<dynamic> resultSex = [];
  34 + List<dynamic> resultDsolt = [];
  35 +
  36 + List<dynamic> resultPhone = [];
  37 +
  38 + List<dynamic> resultDdate = [];
  39 +
  40 + double volume = 0;
  41 +
  42 + bool isListening = false; //是否正在识别
  43 + bool isInit = false; //是否初始化成功
25 @override 44 @override
26 void initState() { 45 void initState() {
27 // TODO: implement initState 46 // TODO: implement initState
@@ -38,10 +57,35 @@ class SpeechPageState extends State<SpeechPage> { @@ -38,10 +57,35 @@ class SpeechPageState extends State<SpeechPage> {
38 SpeechToTextAlibaba.release(); 57 SpeechToTextAlibaba.release();
39 } 58 }
40 59
  60 + _clearResultData() {
  61 + result = '';
  62 + resultDate = [];
  63 +
  64 + resultPnum = [];
  65 +
  66 + resultName = [];
  67 +
  68 + resultSex = [];
  69 + resultDsolt = [];
  70 +
  71 + resultPhone = [];
  72 +
  73 + resultDdate = [];
  74 +
  75 + volume = 0;
  76 +
  77 + isListening = false; //是否正在识别
  78 + // bool isInit = false; //是否初始化成功
  79 + }
  80 +
41 // Platform messages are asynchronous, so we initialize in an async method. 81 // Platform messages are asynchronous, so we initialize in an async method.
42 Future<void> initAlibaba() async { 82 Future<void> initAlibaba() async {
43 await SpeechToTextAlibaba.initialize( 83 await SpeechToTextAlibaba.initialize(
44 - appKey: '88v9LR9mYYyvmSKw', token: '0bbc0ead105041239e2b118b47f30cde'); 84 + appKey: '88v9LR9mYYyvmSKw', token: '928034cf23c8486eaef7138e7d2cdc84');
  85 +
  86 + setState(() {
  87 + isInit = true;
  88 + });
45 SpeechToTextAlibaba.setEngineEventHandler( 89 SpeechToTextAlibaba.setEngineEventHandler(
46 onCallBegin: () { 90 onCallBegin: () {
47 // setState(() { 91 // setState(() {
@@ -60,25 +104,48 @@ class SpeechPageState extends State<SpeechPage> { @@ -60,25 +104,48 @@ class SpeechPageState extends State<SpeechPage> {
60 onError: (error) { 104 onError: (error) {
61 print("---------------发生错误-----------------"); 105 print("---------------发生错误-----------------");
62 Utils.showToast(error); 106 Utils.showToast(error);
  107 + isListening = false;
  108 +
  109 + setState(() {});
  110 +
63 // _addMessage("发生错误: $error", false); 111 // _addMessage("发生错误: $error", false);
64 }, 112 },
65 onPermission: (isPermission) { 113 onPermission: (isPermission) {
66 print("---------------onPermission-----------------"); 114 print("---------------onPermission-----------------");
67 // 没有权限的话弹窗提示跳转设置页 115 // 没有权限的话弹窗提示跳转设置页
  116 +
  117 + if (isPermission == false) {
  118 + //没有获得权限,跳转至系统权限设置页
  119 + DialogUtil.openAlertDialog(
  120 + context, '未获得麦克风权限', "请在手机的权限管理里,将麦克风权限开启", '去设置', 340.0,
  121 + onTap: () {
  122 + openSetting();
  123 + if (Navigator.canPop(context)) {
  124 + Navigator.pop(context, '');
  125 + }
  126 + });
  127 + }
68 }, 128 },
69 onStopping: (isStop) { 129 onStopping: (isStop) {
  130 + if (isStop == false) {
  131 + //开始按钮亮起,结束按钮置灰
  132 + isListening = false;
  133 + setState(() {});
  134 + }
70 print("---------------onNetworkQuality-----------------"); 135 print("---------------onNetworkQuality-----------------");
71 // setState(() { 136 // setState(() {
72 // _networkQuality = quality; 137 // _networkQuality = quality;
73 // }); 138 // });
74 }, 139 },
75 - onVoiceIdChanged: (voiceId) {  
76 - print("---------------onVoiceIdChanged-----------------");  
77 - // _addMessage("AI声音已切换: $voiceId", false);  
78 - },  
79 - onRoleChanged: (role) {  
80 - print("---------------onRoleChanged-----------------");  
81 - // _addMessage("AI角色已切换: $role", false); 140 + onNuiAudioRMSChanged: (double val) {
  141 + print(val);
  142 + // volume = val;
  143 + // setState(() {});
  144 + /**
  145 + * 音频能量值回调
  146 + 音频数据能量值回调,范围-160至0,一般用于UI展示语音动效
  147 + */
  148 + //
82 }, 149 },
83 onEventAsrStarted: (state) { 150 onEventAsrStarted: (state) {
84 print("---------------nEventAsrStarted----------------- $state"); 151 print("---------------nEventAsrStarted----------------- $state");
@@ -133,9 +200,10 @@ class SpeechPageState extends State<SpeechPage> { @@ -133,9 +200,10 @@ class SpeechPageState extends State<SpeechPage> {
133 // break; 200 // break;
134 // } 201 // }
135 }); 202 });
  203 + _getDate(result);
136 }, 204 },
137 ); 205 );
138 - await SpeechToTextAlibaba.startDialog(); 206 + // await SpeechToTextAlibaba.startDialog();
139 } 207 }
140 208
141 Map<String, int> chineseNumberMap = { 209 Map<String, int> chineseNumberMap = {
@@ -186,10 +254,13 @@ class SpeechPageState extends State<SpeechPage> { @@ -186,10 +254,13 @@ class SpeechPageState extends State<SpeechPage> {
186 254
187 //筛选日期 255 //筛选日期
188 _getDate(String result) { 256 _getDate(String result) {
189 - result = result.replaceAll('幺', '1'); 257 + resultDate = [];
190 258
  259 + // result = '录入预订一个下周一的晚餐,姓名王冬妮,性别女士,电话是幺八幺六六六六六六六六,预订日期是下周一,到店时间是十一点半,十五人';
  260 + // -****************************** 预订或者变更日期 ***********************************-
191 // 定义正则表达式,匹配“明天”或“春节” 261 // 定义正则表达式,匹配“明天”或“春节”
192 - RegExp regExp = RegExp(r"今天 | 明天|后天|大后天|元旦|春节|除夕|国庆"); 262 + // result = '预订一个今天明天后天大后天元旦春节除夕国庆';
  263 + RegExp regExp = RegExp(r"今天|明天|后天|大后天|元旦|春节|除夕|国庆");
193 264
194 // 使用RegExp的allMatches方法来找到所有匹配项 265 // 使用RegExp的allMatches方法来找到所有匹配项
195 Iterable<Match> matches = regExp.allMatches(result); 266 Iterable<Match> matches = regExp.allMatches(result);
@@ -200,12 +271,14 @@ class SpeechPageState extends State<SpeechPage> { @@ -200,12 +271,14 @@ class SpeechPageState extends State<SpeechPage> {
200 // 打印结果 271 // 打印结果
201 print(dates); // 输出: [明天, 春节] 272 print(dates); // 输出: [明天, 春节]
202 if (dates.contains('春节')) { 273 if (dates.contains('春节')) {
203 - String str = ''; 274 + // String str = '';
204 final lunarCalendar = LunarCalendar.from( 275 final lunarCalendar = LunarCalendar.from(
205 utcDateTime: DateTime.now().toUtc(), 276 utcDateTime: DateTime.now().toUtc(),
206 ); 277 );
207 - str = '${lunarCalendar.chineseNewYearString}';  
208 - resultDate.add(str); 278 + // str = '${lunarCalendar.chineseNewYearString}';
  279 + // resultDate.add(str);
  280 + resultDate.add(DateUtil.formatDate(lunarCalendar.chineseNewYear,
  281 + format: DateFormats.y_mo_d));
209 } 282 }
210 283
211 if (dates.contains('国庆')) { 284 if (dates.contains('国庆')) {
@@ -222,21 +295,20 @@ class SpeechPageState extends State<SpeechPage> { @@ -222,21 +295,20 @@ class SpeechPageState extends State<SpeechPage> {
222 } 295 }
223 if (dates.contains('元旦')) { 296 if (dates.contains('元旦')) {
224 // 获取当前年份的元旦日期 297 // 获取当前年份的元旦日期
225 - DateTime nationalDay = DateTime(DateTime.now().year, 1, 1); 298 + DateTime newYearDay = DateTime(DateTime.now().year, 1, 1);
226 // 检查元旦是否已经到来 299 // 检查元旦是否已经到来
227 - if (DateTime.now().isAfter(nationalDay)) { 300 + if (DateTime.now().isAfter(newYearDay)) {
228 // '元旦已经过去,用明年的日期 301 // '元旦已经过去,用明年的日期
229 - nationalDay = DateTime(DateTime.now().year + 1, 1, 1); 302 + newYearDay = DateTime(DateTime.now().year + 1, 1, 1);
230 } 303 }
231 resultDate 304 resultDate
232 - .add(DateUtil.formatDate(nationalDay, format: DateFormats.y_mo_d)); 305 + .add(DateUtil.formatDate(newYearDay, format: DateFormats.y_mo_d));
233 } 306 }
234 if (dates.contains('除夕')) { 307 if (dates.contains('除夕')) {
235 final lunarCalendar = LunarCalendar.from( 308 final lunarCalendar = LunarCalendar.from(
236 utcDateTime: DateTime.now().toUtc(), 309 utcDateTime: DateTime.now().toUtc(),
237 ); 310 );
238 - DateTime chuxi = DateUtil.getDateTime(lunarCalendar.chineseNewYearString)  
239 - .subtract(Duration(days: 1)); 311 + DateTime chuxi = lunarCalendar.chineseNewYear.subtract(Duration(days: 1));
240 resultDate.add(DateUtil.formatDate(chuxi, format: DateFormats.y_mo_d)); 312 resultDate.add(DateUtil.formatDate(chuxi, format: DateFormats.y_mo_d));
241 } 313 }
242 //今天 314 //今天
@@ -262,18 +334,46 @@ class SpeechPageState extends State<SpeechPage> { @@ -262,18 +334,46 @@ class SpeechPageState extends State<SpeechPage> {
262 //大后天 334 //大后天
263 if (dates.contains('大后天')) { 335 if (dates.contains('大后天')) {
264 // 获取当前日期和时间 336 // 获取当前日期和时间
265 - DateTime houday = DateTime.now().add(Duration(days: 3));  
266 - resultDate.add(DateUtil.formatDate(houday, format: DateFormats.y_mo_d)); 337 + DateTime dhouday = DateTime.now().add(Duration(days: 3));
  338 + resultDate.add(DateUtil.formatDate(dhouday, format: DateFormats.y_mo_d));
267 } 339 }
268 340
  341 + print(resultDate);
  342 +
269 // 几月几号 343 // 几月几号
270 - DateTime? dateHao = _haoDate(result);  
271 - if (dateHao != null) {  
272 - resultDate.add(DateUtil.formatDate(dateHao, format: DateFormats.y_mo_d)); 344 +
  345 + // 方法1
  346 + // final dates1 = DateExtractor.extractChineseDates(text);
  347 + // print('提取到的日期:');
  348 + // for (var date in dates1) {
  349 + // if (date != null) {
  350 + // print(DateFormat('yyyy-MM-dd').format(date));
  351 + // }
  352 + // }
  353 +
  354 + // // 方法3(推荐)
  355 + // final dates2 = ChineseDateUtils.extractAllDates(text);
  356 + // print('\n使用工具类提取:');
  357 + // for (var date in dates2) {
  358 + // print(ChineseDateUtils.formatDate(date));
  359 + // }
  360 +
  361 + // result = '预订一个十一月二十号的预订';
  362 + // 提取混合文本中的日期
  363 + final mixedDates = ChineseDateUtils.extractAllDates(result);
  364 + print(mixedDates);
  365 + // print('\n混合文本中的日期:');
  366 + for (var date in mixedDates) {
  367 + resultDate.add(DateUtil.formatDate(date, format: DateFormats.y_mo_d));
  368 + // print(ChineseDateUtils.formatDate(date, format: 'MM月dd日'));
273 } 369 }
274 370
  371 + print(resultDate);
  372 +
275 //2025年3月5日 373 //2025年3月5日
276 - _extractDatesFromText(result); 374 + // _extractDatesFromText(result);
  375 +
  376 + // result = '预订一个周一的预订';
277 377
278 //周几 378 //周几
279 RegExp regExpWeek = RegExp(r'周一|周二|周三|周四|周五|周六|周日'); 379 RegExp regExpWeek = RegExp(r'周一|周二|周三|周四|周五|周六|周日');
@@ -311,24 +411,247 @@ class SpeechPageState extends State<SpeechPage> { @@ -311,24 +411,247 @@ class SpeechPageState extends State<SpeechPage> {
311 } 411 }
312 412
313 //下周几 413 //下周几
  414 + // result = '预订一个下周二的预订';
  415 + RegExp regExpWeekNext = RegExp(r'下周一|下周二|下周三|下周四|下周五|下周六|下周日|下周天');
  416 + Iterable<Match> matchesWeekNext = regExpWeekNext.allMatches(result);
  417 + // 获取当前是周几(0表示周日,1表示周一,依此类推)
  418 + int weekdayNext = now.weekday;
314 419
315 - //姓名(非必填)  
316 -//性别(非必填)识别女士、先生、公司  
317 -//手机号(非必填)识别手机号、座机号  
318 -//到店时间(必填,没有准确到店时间,有日期也行)  
319 -//餐段(必填)识别早上|早餐|早饭|上午|中午|午餐|午饭|下午|晚上|晚餐|晚饭 420 + for (Match matcht in matchesWeekNext) {
  421 + int? targetWeekday;
  422 + String? weekdayText = matcht.group(0); // 获取匹配到的文本
  423 + if (weekdayText == '下周一') {
  424 + targetWeekday = 0; // 周三对应的数字是4(周一为1)
  425 + } else if (weekdayText == '下周二') {
  426 + targetWeekday = 1; // 周三对应的数字是4(周一为1)
  427 + } else if (weekdayText == '下周三') {
  428 + targetWeekday = 2; // 周三对应的数字是4(周一为1)
  429 + } else if (weekdayText == '下周四') {
  430 + targetWeekday = 3; // 周三对应的数字是4(周一为1)
  431 + } else if (weekdayText == '下周五') {
  432 + targetWeekday = 4; // 周三对应的数字是4(周一为1)
  433 + } else if (weekdayText == '下周六') {
  434 + targetWeekday = 5; // 周三对应的数字是4(周一为1)
  435 + } else if (weekdayText == '下周日' || weekdayText == '下周天') {
  436 + targetWeekday = 5; // 周三对应的数字是4(周一为1)
  437 + } else {
  438 + // 处理其他情况或不做任何操作。
  439 + }
  440 + if (targetWeekday != null) {
  441 + // 计算下周的某一天,例如下周二
  442 + // 注意:DateTime的星期是从0开始的,0代表星期一,1代表星期二,以此类推
  443 + DateTime nextWeek =
  444 + now.add(Duration(days: 8 - now.weekday + targetWeekday));
  445 + resultDate
  446 + .add(DateUtil.formatDate(nextWeek, format: DateFormats.y_mo_d));
  447 + }
  448 + }
320 449
321 - // 合并数组并去重 450 + // 时间结果去重 ======== 合并数组并去重
322 Set setArr = new Set(); //用set进行去重 451 Set setArr = new Set(); //用set进行去重
323 setArr.addAll(resultDate); //把数组塞进set里 452 setArr.addAll(resultDate); //把数组塞进set里
324 resultDate = setArr.toList(); 453 resultDate = setArr.toList();
325 454
326 - //提取人数  
327 - RegExp regExpPnum = RegExp(r'\b(几[人个位]{1,2,3})\b');  
328 - Iterable<RegExpMatch> matchesPnum = regExpPnum.allMatches(result);  
329 - for (RegExpMatch match in matchesPnum) {  
330 - resultPnum.add(match.group(1)); 455 + // -****************************** 预订者姓名 ***********************************-
  456 +
  457 + //姓名(非必填)
  458 +
  459 + // result = '预订人王冬妮电话18161897899';
  460 +
  461 + // RegExp nameRegex =
  462 + // RegExp(r'[\u4e00-\u9fa5]{2,5}(?:[\u4e00-\u9fa5·]{1,5})?');
  463 + // Iterable<Match> matchesName = nameRegex.allMatches(result);
  464 + // for (Match matchname in matchesName) {
  465 + // // print(match[0]); // 输出: 张三 或 李四
  466 + // resultName.add(matchname[0] ?? '');
  467 + // }
  468 +
  469 + // String text = "参加会议的人员有:张三、李四,还有王五。请通知他们。";
  470 +
  471 + // 匹配2-4个连续的中文字符(覆盖多数单姓和复姓)
  472 + // RegExp namePattern = RegExp(r'[\u4e00-\u9fa5]{2,4}');
  473 +
  474 +// RegExp(r'([李王张刘陈杨赵黄周吴徐孙胡朱高林何郭马罗梁宋郑谢韩唐冯董萧程曹袁邓许傅沈曾彭吕苏卢蒋蔡魏贾丁叶阎余潘杜戴钟姜范江孔崔覃史顾侯邵孟龙万段雷钱汤尹黎易常武乔贺龚文欧]'
  475 +// r'[\u4e00-\u9fa5]{1,2}'
  476 +// r'(?:·[\u4e00-\u9fa5]{1,3})?)'
  477 +// )
  478 +
  479 + // Iterable<Match> matchesName = namePattern.allMatches(result);
  480 + // List<String> extractedNames = [];
  481 +
  482 + // for (Match match in matchesName) {
  483 + // String potentialName = match.group(0)!;
  484 + // // 简单的过滤:排除明显不是姓名的常用词汇
  485 + // if (!['人员', '还有', '通知', '他们'].contains(potentialName)) {
  486 + // extractedNames.add(potentialName);
  487 + // }
  488 + // }
  489 +
  490 + // print(extractedNames); // 输出:[张三, 李四, 王五]
  491 +
  492 + // 使用捕获组 () 捕获名字
  493 +
  494 + // 录入的时候需要说姓名xxx后面需要有,号或者其他非汉字符号间隔
  495 + // result = "录入一个预订,姓名王冬妮,电话18729999999";
  496 +
  497 + // 使用括号 () 创建捕获组,提取“姓名是”后面的字符
  498 + // \u4e00-\u9fa5 是匹配中文字符的范围
  499 + RegExp reg = RegExp(r"姓名([\u4e00-\u9fa5]{2,4})");
  500 + Match? match = reg.firstMatch(result);
  501 + if (match != null) {
  502 + // group(0) 是完整匹配到的字符串 "姓名是张三"
  503 + // group(1) 是第一个括号内捕获的内容 "张三"
  504 + // print(match.group(1)); // 输出: 张三
  505 + resultName.add(match.group(1));
331 } 506 }
  507 + print(resultName);
  508 +
  509 + // 合并数组并去重
  510 + Set setArrName = new Set(); //用set进行去重
  511 + setArrName.addAll(resultName); //把数组塞进set里
  512 + resultName = setArrName.toList();
  513 +
  514 + // -****************************** 预订者性别 ***********************************-
  515 +
  516 + //性别(非必填)识别女士、先生、公司、贵宾
  517 +
  518 + RegExp sexRegex = RegExp(r'女士|先生|公司|贵宾');
  519 + Iterable<Match> matchesSex = sexRegex.allMatches(result);
  520 + for (Match match in matchesSex) {
  521 + // print(match[0]); // 输出: 张三 或 李四
  522 + resultSex.add(match[0] ?? '');
  523 + }
  524 +
  525 + // 合并数组并去重
  526 + Set setArrSex = new Set(); //用set进行去重
  527 + setArrSex.addAll(resultSex); //把数组塞进set里
  528 + resultSex = setArrSex.toList();
  529 +
  530 + // -****************************** 餐段 ***********************************-
  531 +
  532 + //餐段(必填)识别早上|早餐|早饭|上午|中午|午餐|午饭|下午|晚上|晚餐|晚饭
  533 +
  534 + RegExp dsoltRegex = RegExp(r'早上|早餐|早饭|上午|中午|午餐|午饭|下午|晚上|晚餐|晚饭');
  535 + Iterable<Match> matchesDsolt = dsoltRegex.allMatches(result);
  536 + for (Match match in matchesDsolt) {
  537 + // print(match[0]); //
  538 + resultDsolt.add(match[0] ?? '');
  539 + }
  540 +
  541 + // 合并数组并去重
  542 + Set setArrDsolt = new Set(); //用set进行去重
  543 + setArrDsolt.addAll(resultDsolt); //把数组塞进set里
  544 + resultDsolt = setArrDsolt.toList();
  545 +
  546 + // -****************************** 手机或者座机号 ***********************************-
  547 +
  548 +//手机号(非必填)识别手机号、座机号
  549 +
  550 + String phoneData = convertChineseNumberToArabic(result);
  551 +
  552 +// String text = "我的手机号是13812345678,座机号是010-12345678。";
  553 +
  554 + print(phoneData);
  555 +
  556 + // final testText = '''
  557 + // 联系方式:
  558 + // 手机:13800138000、+8613800138001、008613800138002
  559 + // 座机:010-12345678、021-87654321、0755-1234567
  560 + // 带分机:028-87654321-1234
  561 + // 无区号:12345678
  562 + // 其他格式:(010)12345678、010 1234 5678
  563 + // ''';
  564 +
  565 + // 提取所有电话号码
  566 + final allNumbers = PhoneNumberExtractor.extractAllPhoneNumbers(phoneData);
  567 + print('所有电话号码: $allNumbers');
  568 + resultPhone.addAll(allNumbers);
  569 + // 创建正则表达式对象
  570 + // RegExp phoneRegex = RegExp(r'(^1\d{10}$)');
  571 + // RegExp landlineRegex = RegExp(r'(\d{3,4}-\d{7,8})');
  572 +
  573 + // // 查找手机号
  574 + // Iterable<Match> phoneMatches = phoneRegex.allMatches(phoneData);
  575 + // List<String?> phones = phoneMatches.map((match) => match.group(0)).toList();
  576 + // print('手机号: $phones');
  577 + // resultPhone.addAll(phones);
  578 +
  579 + // 查找座机号
  580 + // Iterable<Match> landlineMatches = landlineRegex.allMatches(phoneData);
  581 + // List<String?> landlines =
  582 + // landlineMatches.map((match) => match.group(0)).toList();
  583 + // print('座机号: $landlines');
  584 + // resultPhone.addAll(landlines);
  585 +
  586 + //电话号码结果去重 ========== 合并数组并去重
  587 + Set setArrPhone = new Set(); //用set进行去重
  588 + setArrPhone.addAll(resultPhone); //把数组塞进set里
  589 + resultPhone = setArrPhone.toList();
  590 +
  591 + // -****************************** 到店时间 ***********************************-
  592 +
  593 + // //到店时间(必填,没有准确到店时间,有日期也行)
  594 + // // 找到 hour 和 minute
  595 + // //把汉字数字转换成阿拉伯数字
  596 + // String ddateData = convertChineseNumberToArabic(result);
  597 + // // 测试字符串
  598 + // // String testStrings = '会议在9点30分开始, 午饭时间是12点半, 现在是8点, 这不是时间';
  599 +
  600 + // // 定义正则表达式
  601 + // final timePattern = RegExp(r'(\d{1,2})点(\d{1,2})分|(\d{1,2})点半|(\d+)点');
  602 + // List<Match> matchesDdate = timePattern.allMatches(ddateData).toList();
  603 + // for (Match match in matchesDdate) {
  604 + // // 检查是哪种匹配模式,并提取捕获组
  605 + // if (match.group(1) != null && match.group(2) != null) {
  606 + // // 匹配了 "H点M分" 格式
  607 + // print('找到时间: ${match.group(1)}点${match.group(2)}分');
  608 + // int hour = int.parse(match.group(1)!); // 小时部分,注意使用!来断言非null
  609 + // int minute = int.parse(match.group(2)!); // 小时部分,注意使用!来断言非null
  610 +
  611 + // print('$hour:$minute');
  612 +
  613 + // // 上午
  614 + // resultDdate.add('$hour:$minute');
  615 + // // 下午
  616 + // resultDdate.add('${hour + 12}:$minute');
  617 + // } else if (match.group(3) != null) {
  618 + // // 匹配了 "H点半" 格式
  619 + // int hour = int.parse(match[3]!); // 小时部分,注意使用!来断言非null
  620 + // print('找到时间: ${match.group(3)}点半');
  621 + // print('$hour:30');
  622 + // // 上午
  623 + // resultDdate.add('$hour:30');
  624 + // // 下午
  625 + // resultDdate.add('${hour + 12}:30');
  626 + // } else if (match.group(4) != null) {
  627 + // // 匹配了 "H点" 格式
  628 + // print('找到时间: ${match.group(4)}点');
  629 + // int hour = int.parse(match.group(4)!); // 提取小时数并转换为整数
  630 + // print('$hour:00');
  631 + // // 上午
  632 + // resultDdate.add('$hour:00');
  633 + // // 下午
  634 + // resultDdate.add('${hour + 12}:00');
  635 + // }
  636 + // }
  637 +
  638 + // -****************************** 到店时间 ***********************************-
  639 +
  640 + List<String> times = extractTimes(result);
  641 + resultDdate.addAll(times);
  642 +
  643 + // 到店时间结果去重 ======== 合并数组并去重
  644 + Set setArrDdate = new Set(); //用set进行去重
  645 + setArrDdate.addAll(resultDdate); //把数组塞进set里
  646 + resultDdate = setArrDdate.toList();
  647 +
  648 + // -****************************** 人数 ***********************************-
  649 +
  650 + //提取人数
  651 +
  652 + List<String> pnum = extractNumbers(result);
  653 + resultPnum.addAll(pnum);
  654 +
332 // 合并数组并去重 655 // 合并数组并去重
333 Set setArrPnum = new Set(); //用set进行去重 656 Set setArrPnum = new Set(); //用set进行去重
334 setArrPnum.addAll(resultPnum); //把数组塞进set里 657 setArrPnum.addAll(resultPnum); //把数组塞进set里
@@ -336,15 +659,294 @@ class SpeechPageState extends State<SpeechPage> { @@ -336,15 +659,294 @@ class SpeechPageState extends State<SpeechPage> {
336 setState(() {}); 659 setState(() {});
337 } 660 }
338 661
  662 + List<String> extractNames(String text) {
  663 + List<String> names = [];
  664 + // 匹配一个或多个汉字
  665 + RegExp nameRegex = RegExp(r'[\u4e00-\u9fa5]+');
  666 + Iterable<Match> matches = nameRegex.allMatches(text);
  667 +
  668 + for (Match match in matches) {
  669 + String name = match.group(0)!;
  670 + // 可以在这里过滤,比如只提取 "张三"
  671 + if (name == '张三') {
  672 + names.add(name);
  673 + }
  674 + }
  675 + return names;
  676 + }
  677 +
  678 + List<String> extractNumbers(String text) {
  679 + List<String> numbers = [];
  680 + // 匹配中文数字和阿拉伯数字
  681 + // RegExp regex = RegExp(r'(\d+|[零一二三四五六七八九十百千万亿](个人|位|人)');
  682 +
  683 + final patterns = [
  684 + RegExp(r'([0-9零一二三四五六七八九十百千万亿]+)[位人员名]'),
  685 + RegExp(r'([0-9]+)\s*个人'),
  686 + RegExp(r'([0-9]+)\s*人'),
  687 + RegExp(r'([0-9]+)\s*位'),
  688 + ];
  689 +
  690 + for (var pattern in patterns) {
  691 + final Iterable<Match> matches = pattern.allMatches(text);
  692 + for (var match in matches) {
  693 + String numberStr = match.group(1)!;
  694 +
  695 + bool isNmuber = RegExp(r'^\d+$').hasMatch(numberStr);
  696 + String? number;
  697 + if (isNmuber) {
  698 + //提取出来的是阿拉伯数字
  699 + number = numberStr;
  700 + } else {
  701 + number = ChineseDateUtils.parseChineseNumber(numberStr).toString();
  702 + }
  703 +
  704 + if (number != null) {
  705 + numbers.add(number);
  706 + }
  707 + }
  708 + }
  709 + return numbers;
  710 + }
  711 +
  712 + List<String> extractTimes(String text) {
  713 + List<String> extractedTimes = [];
  714 + // final String text = '''
  715 + // 我今天十一点半要去开会,然后十二点吃午饭,
  716 + // 下午一点有约会,上午两点还有另一个会议。
  717 + // 记得三点钟打电话,晚上七点吃饭。
  718 + // ''';
  719 + // 正则表达式匹配中文时间格式
  720 + RegExp timeRegExp = RegExp(
  721 + r'([上下]午)?([零一二三四五六七八九十两]+)点(半|整|[零一二三四五六七八九十]+分?)?',
  722 + caseSensitive: false);
  723 +
  724 + Iterable<Match> matches = timeRegExp.allMatches(text);
  725 + extractedTimes = matches.map((match) => match.group(0)!).toList();
  726 +
  727 + // 提取并转换时间
  728 + List<String> convertedTimes = [];
  729 + for (var time in extractedTimes) {
  730 + String? standardTime = convertChineseTime(time);
  731 + if (standardTime != null) {
  732 + print(time); //提取出来的汉字时间 十一点半| 下午一点|十二点|上午两点|三点|七点
  733 + print(standardTime); // 转换后的 07:30 | 11:30
  734 + convertedTimes.add('$standardTime');
  735 + }
  736 + }
  737 +
  738 + // setState(() {
  739 + extractedTimes = convertedTimes;
  740 + return extractedTimes;
  741 + // });
  742 + }
  743 +
  744 + String? convertChineseTime(String chineseTime) {
  745 + // 移除空格
  746 + String time = chineseTime.trim();
  747 +
  748 + // 处理上午/下午
  749 + bool isAfternoon = time.contains('下午');
  750 + bool isMorning = time.contains('上午');
  751 +
  752 + // 移除上午/下午前缀
  753 + if (isAfternoon || isMorning) {
  754 + time = time.replaceAll(RegExp(r'[上下]午'), '');
  755 + }
  756 +
  757 + // 提取小时和分钟
  758 + RegExp hourMinuteReg =
  759 + RegExp(r'([零一二三四五六七八九十两]+)点(半|整|([零一二三四五六七八九十]+)分?)?');
  760 + Match? match = hourMinuteReg.firstMatch(time);
  761 +
  762 + if (match == null) return null;
  763 +
  764 + String hourChinese = match.group(1)!;
  765 + String minuteChinese = match.group(2) ?? '整';
  766 +
  767 + // 转换中文数字
  768 + int hour = chineseToNumberTime(hourChinese);
  769 +
  770 + // 处理12小时制转24小时制
  771 + if (isAfternoon && hour < 12) {
  772 + hour += 12;
  773 + } else if (isMorning && hour == 12) {
  774 + hour = 0; // 上午12点就是0点
  775 + }
  776 +
  777 + // 转换分钟
  778 + int minute = 0;
  779 + if (minuteChinese.contains('半')) {
  780 + minute = 30;
  781 + } else if (minuteChinese.contains('分')) {
  782 + String minuteNum = minuteChinese.replaceAll('分', '');
  783 + minute = chineseToNumberTime(minuteNum);
  784 + }
  785 +
  786 + return '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
  787 + }
  788 +
  789 + int chineseToNumberTime(String chinese) {
  790 + Map<String, int> numberMap = {
  791 + '零': 0,
  792 + '一': 1,
  793 + '二': 2,
  794 + '三': 3,
  795 + '四': 4,
  796 + '五': 5,
  797 + '六': 6,
  798 + '七': 7,
  799 + '八': 8,
  800 + '九': 9,
  801 + '十': 10,
  802 + '十一': 11,
  803 + '十二': 12,
  804 + '两': 2,
  805 + '廿': 20,
  806 + '卅': 30
  807 + };
  808 +
  809 + // 处理复合数字
  810 + if (chinese.length > 1 && !numberMap.containsKey(chinese)) {
  811 + if (chinese.startsWith('十')) {
  812 + return 10 + numberMap[chinese.substring(1)]!;
  813 + } else if (chinese.endsWith('十')) {
  814 + return numberMap[chinese.substring(0, 1)]! * 10;
  815 + }
  816 + }
  817 +
  818 + return numberMap[chinese] ?? int.tryParse(chinese) ?? 0;
  819 + }
  820 +
  821 + DateTime parseChineseDate(String dateString) {
  822 + dateString = '十一月二十号';
  823 + // 定义中文月份到数字月份的映射
  824 + Map<String, int> monthMap = {
  825 + "一月": 1,
  826 + "二月": 2,
  827 + "三月": 3,
  828 + "四月": 4,
  829 + "五月": 5,
  830 + "六月": 6,
  831 + "七月": 7,
  832 + "八月": 8,
  833 + "九月": 9,
  834 + "十月": 10,
  835 + "十一月": 11,
  836 + "十二月": 12,
  837 + };
  838 +
  839 + // 定义中文日到数字日的映射
  840 + Map<String, int> dayMap = {
  841 + "初一": 1, "初二": 2, "初三": 3,
  842 + "初四": 4, "初五": 5, "初六": 6,
  843 + "初七": 7, "初八": 8, "初九": 9,
  844 + "初十": 10, "十一": 11, "十二": 12,
  845 + "十三": 13, "十四": 14, "十五": 15,
  846 + "十六": 16, "十七": 17, "十八": 18,
  847 + "十九": 19, "二十": 20, "廿一": 21, // 注意这里的“廿”在某些情况下可能需要根据具体语境调整或省略
  848 + "廿二": 22, "廿三": 23, "廿四": 24,
  849 + "廿五": 25, "廿六": 26, "廿七": 27,
  850 + "廿八": 28, "廿九": 29, "三十": 30,
  851 + "三十一": 31 // 注意这里的“卅一”在某些情况下可能需要根据具体语境调整或省略
  852 + };
  853 +
  854 + // 使用正则表达式提取月份和日期部分
  855 + RegExp regExp =
  856 + RegExp(r'([\u4e00-\u9fa5]+)(\d+)号'); // 注意这里的正则表达式可能需要根据你的具体格式调整
  857 + Iterable<RegExpMatch> matches = regExp.allMatches(dateString);
  858 + if (matches.isNotEmpty) {
  859 + RegExpMatch match = matches.first;
  860 + String? monthStr = match.group(1);
  861 + String? dayStr = match.group(2);
  862 + int month = monthMap[monthStr] ?? -1;
  863 + int day = dayMap[dayStr ?? '' + '号'] ?? -1; // 这里假设日期后面总是跟着“号”字,如果不是,请调整逻辑
  864 +
  865 + if (month != -1 && day != -1) {
  866 + // 获取当前年份,你也可以根据需要传入特定的年份
  867 + int year = DateTime.now().year;
  868 + return DateTime(year, month, day);
  869 + } else {
  870 + throw Exception('无法解析日期:$dateString');
  871 + }
  872 + } else {
  873 + throw Exception('无法找到匹配的日期格式:$dateString');
  874 + }
  875 + }
  876 +
  877 + void extractChineseDate(String text) {
  878 + // 定义月份中文到数字的映射
  879 + final Map<String, int> monthMap = {
  880 + '一': 1,
  881 + '二': 2,
  882 + '三': 3,
  883 + '四': 4,
  884 + '五': 5,
  885 + '六': 6,
  886 + '七': 7,
  887 + '八': 8,
  888 + '九': 9,
  889 + '十': 10,
  890 + '十一': 11,
  891 + '十二': 12,
  892 + };
  893 +
  894 + // 定义一个能匹配中文日期格式的正则
  895 + // (一..十二)月? (数字)号
  896 + RegExp dateRegExp = RegExp(r'((一|二|三|四|五|六|七|八|九|十|十一|十二)月)?(\d{1,2})号');
  897 +
  898 + var matches = dateRegExp.allMatches(text);
  899 +
  900 + for (var match in matches) {
  901 + String? monthStr = match.group(2); // 捕获月份名 (如 "十")
  902 + String? dayStr = match.group(3); // 捕获日期数字 (如 "20")
  903 +
  904 + int month =
  905 + monthStr != null ? (monthMap[monthStr] ?? 0) : 0; // 获取月份数字,默认为0
  906 + int day = int.tryParse(dayStr ?? '0') ?? 0; // 转换日期数字
  907 +
  908 + // 假设当前年份 (这里可以根据需要修改或从文本中提取)
  909 + int currentYear = DateTime.now().year;
  910 +
  911 + if (month > 0 && day > 0) {
  912 + try {
  913 + // 使用 intl 包格式化日期
  914 + DateTime date = DateTime(currentYear, month, day);
  915 + String formattedDate = DateFormat('yyyy-MM-dd').format(date);
  916 + print('匹配到日期: ${match.group(0)} -> 格式化日期: $formattedDate');
  917 + } catch (e) {
  918 + print('日期转换错误: ${e.toString()}');
  919 + }
  920 + }
  921 + }
  922 + }
  923 +
339 //带有“几月几号”日期解析 924 //带有“几月几号”日期解析
340 DateTime? _haoDate(String result) { 925 DateTime? _haoDate(String result) {
  926 + //
  927 + // RegExp regExp =
  928 + // RegExp(r'([零一二三四五六七八九十百千万亿]+)月([零一二三四五六七八九十百千万亿]+)号'); // 正则表达式匹配数字和“号”
341 RegExp regExp = RegExp(r'(\d+)月(\d+)号'); // 正则表达式匹配数字和“号” 929 RegExp regExp = RegExp(r'(\d+)月(\d+)号'); // 正则表达式匹配数字和“号”
342 Match? match = regExp.firstMatch(result); 930 Match? match = regExp.firstMatch(result);
343 if (match != null) { 931 if (match != null) {
344 int month = int.parse(match.group(1)!); // 获取月份并转换为整数 932 int month = int.parse(match.group(1)!); // 获取月份并转换为整数
345 int day = int.parse(match.group(2)!); // 获取日期并转换为整数 933 int day = int.parse(match.group(2)!); // 获取日期并转换为整数
  934 +
  935 + //如果月份小于此月,则用year + 1,否则用year
  936 +
  937 + DateTime now = DateTime.now();
  938 + int monthNow = now.month;
  939 + int dayNow = now.day;
  940 + int year = now.year;
  941 + if (month < monthNow) {
  942 + year = now.year + 1;
  943 + } else if (month == monthNow) {
  944 + if (day < dayNow) {
  945 + year = now.year + 1;
  946 + }
  947 + }
346 DateTime date = 948 DateTime date =
347 - DateTime(2000, month, day); // 假设年份为2000,根据需要可以调整年份或使用完整日期信息。 949 + DateTime(year, month, day); // 假设年份为2000,根据需要可以调整年份或使用完整日期信息。
348 return date; 950 return date;
349 // print("解析后的日期: ${date.toString()}"); // 输出解析后的日期 951 // print("解析后的日期: ${date.toString()}"); // 输出解析后的日期
350 } else { 952 } else {
@@ -353,6 +955,243 @@ class SpeechPageState extends State<SpeechPage> { @@ -353,6 +955,243 @@ class SpeechPageState extends State<SpeechPage> {
353 } 955 }
354 } 956 }
355 957
  958 + //把字符串中的汉字数字转换成阿拉伯数字 月份
  959 + String convertChineseNumberToArabicMonth(String chineseNumber) {
  960 + Map<String, String> chineseToArabic = {
  961 + '零': '0',
  962 + '一': '1',
  963 + '幺': '1',
  964 + '二': '2',
  965 + '三': '3',
  966 + '四': '4',
  967 + '五': '5',
  968 + '六': '6',
  969 + '七': '7',
  970 + '八': '8',
  971 + '九': '9',
  972 + '十': '10',
  973 + '十一': '11',
  974 + '十二': '12',
  975 + '十三': '13',
  976 + '十四': '14',
  977 + '十五': '15',
  978 + '十六': '16',
  979 + '十七': '17',
  980 + '十八': '18',
  981 + '十九': '19',
  982 + '二十': '20',
  983 + '二十一': '21',
  984 + '二十二': '22',
  985 + '二十三': '23',
  986 + '二十四': '24',
  987 + '二十五': '25',
  988 + '二十六': '26',
  989 + '二十七': '27',
  990 + '二十八': '28',
  991 + '二十九': '29',
  992 + '三十': '30',
  993 + '三十一': '31',
  994 + };
  995 + String result = '';
  996 + for (int i = 0; i < chineseNumber.length; i++) {
  997 + String char = chineseNumber[i];
  998 + if (chineseToArabic.containsKey(char)) {
  999 + result += chineseToArabic[char]!;
  1000 + } else {
  1001 + result += char; // 如果不是汉字数字,直接添加到结果中
  1002 + }
  1003 + }
  1004 + return result;
  1005 + }
  1006 +
  1007 + int chineseNumberToArabic(String text) {
  1008 + /// 汉字数字到阿拉伯数字映射
  1009 + const Map<String, int> chineseToDigit = {
  1010 + '零': 0,
  1011 + '一': 1,
  1012 + '二': 2,
  1013 + '三': 3,
  1014 + '四': 4,
  1015 + '五': 5,
  1016 + '六': 6,
  1017 + '七': 7,
  1018 + '八': 8,
  1019 + '九': 9,
  1020 + };
  1021 +
  1022 + /// 单位映射
  1023 + const Map<String, int> unitMap = {
  1024 + '十': 10,
  1025 + '百': 100,
  1026 + '千': 1000,
  1027 + '万': 10000,
  1028 + '亿': 100000000,
  1029 + };
  1030 + int result = 0;
  1031 + int currentNum = 0; // 当前数字(如“三”中的 3)
  1032 + int currentUnitValue = 1; // 当前单位值 (如“十”是 10)
  1033 +
  1034 + // 遍历字符串,优先处理“万”和“亿”
  1035 + for (int i = 0; i < text.length; i++) {
  1036 + String char = text[i];
  1037 +
  1038 + if (chineseToDigit.containsKey(char)) {
  1039 + currentNum = chineseToDigit[char]!;
  1040 + // 如果是“一十”,则直接变成 10,而不是 1*10
  1041 + if (currentNum == 1 &&
  1042 + (i > 0 &&
  1043 + (text[i - 1] == '十' ||
  1044 + text[i - 1] == '百' ||
  1045 + text[i - 1] == '千'))) {
  1046 + currentNum = 0; // 避免“一十”变成 10
  1047 + }
  1048 + } else if (unitMap.containsKey(char)) {
  1049 + int unit = unitMap[char]!;
  1050 + if (unit >= 10000) {
  1051 + // 处理万、亿,先结算前面部分
  1052 + result += currentNum * currentUnitValue;
  1053 + currentNum = 0; // 重置当前数字
  1054 + currentUnitValue = 1; // 重置单位
  1055 + result *= unit; // 乘以万或亿
  1056 + } else {
  1057 + result += currentNum * currentUnitValue;
  1058 + currentNum = 0; // 重置当前数字
  1059 + currentUnitValue = unit; // 更新单位
  1060 + }
  1061 + } else {
  1062 + // 遇到非数字/单位字符,先结算之前的
  1063 + result += currentNum * currentUnitValue;
  1064 + currentNum = 0;
  1065 + currentUnitValue = 1;
  1066 + }
  1067 + }
  1068 + // 结算最后一部分
  1069 + result += currentNum * currentUnitValue;
  1070 + return result;
  1071 + }
  1072 +
  1073 + String convertChineseToArabic(String text) {
  1074 + // 中文数字到阿拉伯数字的映射
  1075 + Map<String, int> chineseNumbers = {
  1076 + '零': 0,
  1077 + '一': 1,
  1078 + '二': 2,
  1079 + '三': 3,
  1080 + '四': 4,
  1081 + '五': 5,
  1082 + '六': 6,
  1083 + '七': 7,
  1084 + '八': 8,
  1085 + '九': 9,
  1086 + '十': 10,
  1087 + '百': 100,
  1088 + '千': 1000,
  1089 + '万': 10000,
  1090 + '亿': 100000000
  1091 + };
  1092 +
  1093 + // 处理多位数字,例如“二十二”
  1094 + RegExp regExp = RegExp(r'([零一二三四五六七八九]{1,4})(百|千|万|亿)?');
  1095 + List<String?> matches =
  1096 + regExp.allMatches(text).map((m) => m.group(0)).toList();
  1097 +
  1098 + int convertChineseNum(String numStr) {
  1099 + int result = 0;
  1100 + int unit = 1; // 个位
  1101 + for (int i = numStr.length - 1; i >= 0; i--) {
  1102 + String char = numStr[i];
  1103 + if (char == '十') {
  1104 + unit = 10;
  1105 + } else if (char == '百') {
  1106 + unit = 100;
  1107 + } else if (char == '千') {
  1108 + unit = 1000;
  1109 + } else if (char == '万') {
  1110 + unit = 10000;
  1111 + } else if (char == '亿') {
  1112 + unit = 100000000;
  1113 + } else {
  1114 + result += chineseNumbers[char]! * unit;
  1115 + unit = 1; // 重置单位为个位
  1116 + }
  1117 + }
  1118 + return result;
  1119 + }
  1120 +
  1121 + String result = text;
  1122 + for (String? match in matches.reversed) {
  1123 + // 从后往前替换,避免索引错位
  1124 + if (match != null) {
  1125 + result = result.replaceAll(match, convertChineseNum(match).toString());
  1126 + }
  1127 + }
  1128 + return result;
  1129 + }
  1130 +
  1131 + String chineseToNumber(String chinese) {
  1132 + Map<String, int> map = {
  1133 + "零": 0,
  1134 + "一": 1,
  1135 + "二": 2,
  1136 + "三": 3,
  1137 + "四": 4,
  1138 + "五": 5,
  1139 + "六": 6,
  1140 + "七": 7,
  1141 + "八": 8,
  1142 + "九": 9,
  1143 + "十": 10,
  1144 + "百": 100,
  1145 + "千": 1000,
  1146 + "万": 10000,
  1147 + "亿": 100000000
  1148 + };
  1149 + List<String> parts = chinese.split(RegExp(r'(\d+)')); // 分割数字和单位
  1150 + int result = 0;
  1151 + int temp = 1; // 临时变量,用于记录当前位的权值
  1152 + for (String part in parts) {
  1153 + if (map.containsKey(part)) {
  1154 + if (map[part]! >= 10) {
  1155 + // 是单位
  1156 + temp = map[part]!;
  1157 + } else {
  1158 + // 是数字
  1159 + result += temp * map[part]!;
  1160 + temp = 1; // 重置temp为1,准备下一个数字的乘法
  1161 + }
  1162 + }
  1163 + }
  1164 + return result.toString();
  1165 + }
  1166 +
  1167 +//把字符串中的汉字数字转换成阿拉伯数字
  1168 + String convertChineseNumberToArabic(String chineseNumber) {
  1169 + Map<String, String> chineseToArabic = {
  1170 + '零': '0',
  1171 + '一': '1',
  1172 + '幺': '1',
  1173 + '二': '2',
  1174 + '三': '3',
  1175 + '四': '4',
  1176 + '五': '5',
  1177 + '六': '6',
  1178 + '七': '7',
  1179 + '八': '8',
  1180 + '九': '9'
  1181 + };
  1182 +
  1183 + String result = '';
  1184 + for (int i = 0; i < chineseNumber.length; i++) {
  1185 + String char = chineseNumber[i];
  1186 + if (chineseToArabic.containsKey(char)) {
  1187 + result += chineseToArabic[char]!;
  1188 + } else {
  1189 + result += char; // 如果不是汉字数字,直接添加到结果中
  1190 + }
  1191 + }
  1192 + return result;
  1193 + }
  1194 +
356 _extractDatesFromText(String text) { 1195 _extractDatesFromText(String text) {
357 // 定义一个正则表达式来匹配日期 1196 // 定义一个正则表达式来匹配日期
358 // 示例:匹配形如 "2023年3月15日" 或 "15/03/2023" 的日期 1197 // 示例:匹配形如 "2023年3月15日" 或 "15/03/2023" 的日期
@@ -399,6 +1238,7 @@ class SpeechPageState extends State<SpeechPage> { @@ -399,6 +1238,7 @@ class SpeechPageState extends State<SpeechPage> {
399 return Center( 1238 return Center(
400 child: SingleChildScrollView( 1239 child: SingleChildScrollView(
401 child: Column( 1240 child: Column(
  1241 + crossAxisAlignment: CrossAxisAlignment.start,
402 children: [ 1242 children: [
403 Container( 1243 Container(
404 height: ScreenUtil().setWidth(48), 1244 height: ScreenUtil().setWidth(48),
@@ -407,57 +1247,162 @@ class SpeechPageState extends State<SpeechPage> { @@ -407,57 +1247,162 @@ class SpeechPageState extends State<SpeechPage> {
407 style: TextStyle( 1247 style: TextStyle(
408 fontWeight: FontWeight.bold, 1248 fontWeight: FontWeight.bold,
409 fontSize: ScreenUtil().setSp(32), 1249 fontSize: ScreenUtil().setSp(32),
410 - color: MyColours.color_ffffff)), 1250 + color: MyColours.color_515151)),
411 ), 1251 ),
412 - VEmptyView(40), 1252 + // VEmptyView(20),
  1253 + // WaveformWidget(volume: volume), // 在录音时传入音量值
413 Container( 1254 Container(
414 // height: ScreenUtil().setWidth(48), 1255 // height: ScreenUtil().setWidth(48),
415 - alignment: Alignment.center, 1256 + alignment: Alignment.centerLeft,
416 child: Text(result, 1257 child: Text(result,
417 style: TextStyle( 1258 style: TextStyle(
418 fontWeight: FontWeight.bold, 1259 fontWeight: FontWeight.bold,
419 fontSize: ScreenUtil().setSp(32), 1260 fontSize: ScreenUtil().setSp(32),
420 - color: MyColours.color_ffffff)), 1261 + color: MyColours.color_515151)),
421 ), 1262 ),
422 VEmptyView(40), 1263 VEmptyView(40),
423 Container( 1264 Container(
424 // height: ScreenUtil().setWidth(48), 1265 // height: ScreenUtil().setWidth(48),
425 - alignment: Alignment.center,  
426 - child: Text('日期:${resultDate.join('、')}', 1266 + alignment: Alignment.centerLeft,
  1267 + child: Text('预订日期:${resultDate.join('、')}',
427 style: TextStyle( 1268 style: TextStyle(
428 fontWeight: FontWeight.bold, 1269 fontWeight: FontWeight.bold,
429 fontSize: ScreenUtil().setSp(32), 1270 fontSize: ScreenUtil().setSp(32),
430 - color: MyColours.color_ffffff)), 1271 + color: MyColours.color_515151)),
431 ), 1272 ),
432 VEmptyView(40), 1273 VEmptyView(40),
433 Container( 1274 Container(
434 // height: ScreenUtil().setWidth(48), 1275 // height: ScreenUtil().setWidth(48),
435 - alignment: Alignment.center, 1276 + alignment: Alignment.centerLeft,
  1277 + child: Text('姓名:${resultName.join('、')}',
  1278 + style: TextStyle(
  1279 + fontWeight: FontWeight.bold,
  1280 + fontSize: ScreenUtil().setSp(32),
  1281 + color: MyColours.color_515151)),
  1282 + ),
  1283 + VEmptyView(40),
  1284 + Container(
  1285 + // height: ScreenUtil().setWidth(48),
  1286 + alignment: Alignment.centerLeft,
  1287 + child: Text('性别:${resultSex.join('、')}',
  1288 + style: TextStyle(
  1289 + fontWeight: FontWeight.bold,
  1290 + fontSize: ScreenUtil().setSp(32),
  1291 + color: MyColours.color_515151)),
  1292 + ),
  1293 + VEmptyView(40),
  1294 + Container(
  1295 + // height: ScreenUtil().setWidth(48),
  1296 + alignment: Alignment.centerLeft,
  1297 + child: Text('电话:${resultPhone.join('、')}',
  1298 + style: TextStyle(
  1299 + fontWeight: FontWeight.bold,
  1300 + fontSize: ScreenUtil().setSp(32),
  1301 + color: MyColours.color_515151)),
  1302 + ),
  1303 + VEmptyView(40),
  1304 + Container(
  1305 + // height: ScreenUtil().setWidth(48),
  1306 + alignment: Alignment.centerLeft,
436 child: Text('人数:${resultPnum.join('、')}', 1307 child: Text('人数:${resultPnum.join('、')}',
437 style: TextStyle( 1308 style: TextStyle(
438 fontWeight: FontWeight.bold, 1309 fontWeight: FontWeight.bold,
439 fontSize: ScreenUtil().setSp(32), 1310 fontSize: ScreenUtil().setSp(32),
440 - color: MyColours.color_ffffff)), 1311 + color: MyColours.color_515151)),
  1312 + ),
  1313 + VEmptyView(40),
  1314 + Container(
  1315 + // height: ScreenUtil().setWidth(48),
  1316 + alignment: Alignment.centerLeft,
  1317 + child: Text('餐段:${resultDsolt.join('、')}',
  1318 + style: TextStyle(
  1319 + fontWeight: FontWeight.bold,
  1320 + fontSize: ScreenUtil().setSp(32),
  1321 + color: MyColours.color_515151)),
441 ), 1322 ),
442 VEmptyView(40), 1323 VEmptyView(40),
443 - RoundButton(  
444 - text: '结束识别',  
445 - width:  
446 - (ScreenUtil().screenWidth - ScreenUtil().setWidth(62) * 2) / 1324 + Container(
  1325 + // height: ScreenUtil().setWidth(48),
  1326 + alignment: Alignment.centerLeft,
  1327 + child: Text('到店时间:${resultDdate.join('、')}',
  1328 + style: TextStyle(
  1329 + fontWeight: FontWeight.bold,
  1330 + fontSize: ScreenUtil().setSp(32),
  1331 + color: MyColours.color_515151)),
  1332 + ),
  1333 + VEmptyView(50),
  1334 + Row(
  1335 + mainAxisAlignment: MainAxisAlignment.spaceEvenly,
  1336 + children: [
  1337 + RoundButton(
  1338 + text: '开始识别',
  1339 + width: (ScreenUtil().screenWidth -
  1340 + ScreenUtil().setWidth(82) * 2) /
  1341 + 2,
  1342 + height: ScreenUtil().setWidth(95),
  1343 + radius: 30,
  1344 + gradientStart: isInit && !isListening
  1345 + ? MyColours.color_438E93
  1346 + : MyColours.color_999999,
  1347 + gradientEnd: isInit && !isListening
  1348 + ? MyColours.color_438E93
  1349 + : MyColours.color_999999,
  1350 + textColor: MyColours.color_ffffff,
  1351 + isBold: false,
  1352 + fontSize: 30,
  1353 + onPressed: () async {
  1354 + _clearResultData();
  1355 + setState(() {
  1356 + isListening = true;
  1357 + });
  1358 + await SpeechToTextAlibaba.startDialog();
  1359 + // _getDate('5点');
  1360 + }),
  1361 + RoundButton(
  1362 + text: '结束识别',
  1363 + width: (ScreenUtil().screenWidth -
  1364 + ScreenUtil().setWidth(82) * 2) /
447 2, 1365 2,
448 - height: ScreenUtil().setWidth(112),  
449 - // radius: ScreenUtil().setWidth(10),  
450 - gradientStart: Colors.transparent,  
451 - gradientEnd: Colors.transparent,  
452 - textColor: MyColours.color_ffffff,  
453 - isBold: true,  
454 - fontSize: 34,  
455 - onPressed: () async {  
456 - await SpeechToTextAlibaba.stopDialog();  
457 - // if (Navigator.canPop(context)) {  
458 - // Navigator.pop(context);  
459 - // }  
460 - }), 1366 + height: ScreenUtil().setWidth(95),
  1367 + radius: 30,
  1368 + gradientStart: isListening
  1369 + ? MyColours.color_438E93
  1370 + : MyColours.color_999999,
  1371 + gradientEnd: isListening
  1372 + ? MyColours.color_438E93
  1373 + : MyColours.color_999999,
  1374 + textColor: MyColours.color_ffffff,
  1375 + isBold: false,
  1376 + fontSize: 30,
  1377 + onPressed: () async {
  1378 + setState(() {
  1379 + isListening = false; //主动结束识别
  1380 + });
  1381 + await SpeechToTextAlibaba.stopDialog();
  1382 + // _getDate('5点');
  1383 + }),
  1384 + ],
  1385 + )
  1386 + // Align(
  1387 + // alignment: Alignment.center,
  1388 + // child: RoundButton(
  1389 + // text: '结束识别',
  1390 + // width:
  1391 + // (ScreenUtil().screenWidth - ScreenUtil().setWidth(62) * 2) /
  1392 + // 2,
  1393 + // height: ScreenUtil().setWidth(95),
  1394 + // radius: 30,
  1395 + // gradientStart: MyColours.color_438E93,
  1396 + // gradientEnd: MyColours.color_438E93,
  1397 + // textColor: MyColours.color_ffffff,
  1398 + // isBold: false,
  1399 + // fontSize: 30,
  1400 + // onPressed: () async {
  1401 + // await SpeechToTextAlibaba.stopDialog();
  1402 + // // _getDate('5点');
  1403 + // }),
  1404 + // ),
  1405 + // VEmptyView(300),
461 ], 1406 ],
462 ), 1407 ),
463 )); 1408 ));
  1 +import 'package:flutter/material.dart';
  2 +import 'package:one_key/res/colors.dart';
  3 +
  4 +/**
  5 + *
  6 + *
  7 + * WaveformWidget(volume: _volume) // 在录音时传入音量值
  8 + *
  9 + *
  10 + */
  11 +
  12 +class WaveformWidget extends StatefulWidget {
  13 + final double volume;
  14 +
  15 + const WaveformWidget({Key? key, required this.volume}) : super(key: key);
  16 +
  17 + @override
  18 + _WaveformWidgetState createState() => _WaveformWidgetState();
  19 +}
  20 +
  21 +class _WaveformWidgetState extends State<WaveformWidget>
  22 + with SingleTickerProviderStateMixin {
  23 + late AnimationController _controller;
  24 + List<double> _waveData = List.generate(20, (index) => 0.0); // 存储波形数据
  25 +
  26 + @override
  27 + void initState() {
  28 + super.initState();
  29 + _controller = AnimationController(
  30 + vsync: this,
  31 + duration: Duration(milliseconds: 100),
  32 + )..repeat();
  33 + }
  34 +
  35 + @override
  36 + void didUpdateWidget(WaveformWidget oldWidget) {
  37 + super.didUpdateWidget(oldWidget);
  38 + // 更新波形数据
  39 + _waveData.removeAt(0);
  40 + _waveData.add(widget.volume);
  41 + }
  42 +
  43 + @override
  44 + Widget build(BuildContext context) {
  45 + return AnimatedBuilder(
  46 + animation: _controller,
  47 + builder: (context, child) {
  48 + return CustomPaint(
  49 + painter: WaveformPainter(_waveData),
  50 + size: Size(300, 100),
  51 + );
  52 + },
  53 + );
  54 + }
  55 +}
  56 +
  57 +class WaveformPainter extends CustomPainter {
  58 + final List<double> waveData;
  59 +
  60 + WaveformPainter(this.waveData);
  61 +
  62 + @override
  63 + void paint(Canvas canvas, Size size) {
  64 + final paint = Paint()
  65 + ..color = MyColours.color_438E93
  66 + ..strokeWidth = 2
  67 + ..style = PaintingStyle.stroke;
  68 +
  69 + final path = Path();
  70 + final xStep = size.width / (waveData.length - 1);
  71 +
  72 + for (int i = 0; i < waveData.length; i++) {
  73 + final x = i * xStep;
  74 + final y = size.height / 2 + waveData[i] * 10; // 调整振幅
  75 +
  76 + if (i == 0) {
  77 + path.moveTo(x, y);
  78 + } else {
  79 + path.lineTo(x, y);
  80 + }
  81 + }
  82 +
  83 + canvas.drawPath(path, paint);
  84 + }
  85 +
  86 + @override
  87 + bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
  88 +}
@@ -173,7 +173,7 @@ public class Auth { @@ -173,7 +173,7 @@ public class Auth {
173 String STS_AK_ID = "STS.<服务器生成的具有时效性的临时凭证>"; 173 String STS_AK_ID = "STS.<服务器生成的具有时效性的临时凭证>";
174 String STS_AK_SECRET = "<服务器生成的具有时效性的临时凭证>"; 174 String STS_AK_SECRET = "<服务器生成的具有时效性的临时凭证>";
175 String STS_TOKEN = "<服务器生成的具有时效性的临时凭证>"; 175 String STS_TOKEN = "<服务器生成的具有时效性的临时凭证>";
176 - String TOKEN ="0bbc0ead105041239e2b118b47f30cde"; //"<由STS生成的临时访问令牌>"; 176 + String TOKEN ="928034cf23c8486eaef7138e7d2cdc84"; //"<由STS生成的临时访问令牌>";
177 final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN); 177 final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
178 // Thread th = new Thread() { 178 // Thread th = new Thread() {
179 // @Override 179 // @Override
@@ -357,7 +357,8 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware , @@ -357,7 +357,8 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware ,
357 // EVENT_MIC_ERROR表示2s未传入音频数据,请检查录音相关代码、权限或录音模块是否被其他应用占用。 357 // EVENT_MIC_ERROR表示2s未传入音频数据,请检查录音相关代码、权限或录音模块是否被其他应用占用。
358 final String msg_text = Utils.getMsgWithErrorCode(resultCode, "start"); 358 final String msg_text = Utils.getMsgWithErrorCode(resultCode, "start");
359 mStopping = false; 359 mStopping = false;
360 - new Handler(Looper.getMainLooper()).post(() -> { channel.invokeMethod("onError", msg_text); 360 + new Handler(Looper.getMainLooper()).post(() -> {
  361 + channel.invokeMethod("onError", msg_text);
361 channel.invokeMethod("onStopping", false);}); 362 channel.invokeMethod("onStopping", false);});
362 363
363 364
@@ -500,7 +501,14 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware , @@ -500,7 +501,14 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware ,
500 501
501 @Override 502 @Override
502 public void onNuiAudioRMSChanged(float val) { 503 public void onNuiAudioRMSChanged(float val) {
503 -// Log.i(TAG, "onNuiAudioRMSChanged vol " + val); 504 + Log.i(TAG, "onNuiAudioRMSChanged vol " + val);
  505 +
  506 + new Handler(Looper.getMainLooper()).post(() -> {
  507 + channel.invokeMethod("onNuiAudioRMSChanged", val);});
  508 +
  509 +
  510 +
  511 +
504 } 512 }
505 513
506 @Override 514 @Override
@@ -654,7 +662,7 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware , @@ -654,7 +662,7 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware ,
654 662
655 // 注意: 若需要更换appkey和token,可以直接传入参数 663 // 注意: 若需要更换appkey和token,可以直接传入参数
656 dialog_param.put("app_key", "88v9LR9mYYyvmSKw"); 664 dialog_param.put("app_key", "88v9LR9mYYyvmSKw");
657 - dialog_param.put("token", "0bbc0ead105041239e2b118b47f30cde"); 665 + dialog_param.put("token", "928034cf23c8486eaef7138e7d2cdc84");
658 params = dialog_param.toString(); 666 params = dialog_param.toString();
659 } catch (JSONException e) { 667 } catch (JSONException e) {
660 e.printStackTrace(); 668 e.printStackTrace();
1 -  
2 -  
3 import 'speech_to_text_alibaba_method_channel.dart'; 1 import 'speech_to_text_alibaba_method_channel.dart';
4 import 'speech_to_text_alibaba_platform_interface.dart'; 2 import 'speech_to_text_alibaba_platform_interface.dart';
5 3
6 class SpeechToTextAlibaba { 4 class SpeechToTextAlibaba {
7 -  
8 - /// 获取平台实现实例  
9 - static SpeechToTextAlibabaPlatform get _platform => SpeechToTextAlibabaPlatform.instance; 5 + /// 获取平台实现实例
  6 + static SpeechToTextAlibabaPlatform get _platform =>
  7 + SpeechToTextAlibabaPlatform.instance;
10 8
11 /// 设置引擎回调函数 9 /// 设置引擎回调函数
12 /// 10 ///
@@ -28,9 +26,7 @@ class SpeechToTextAlibaba { @@ -28,9 +26,7 @@ class SpeechToTextAlibaba {
28 void Function(String)? onError, 26 void Function(String)? onError,
29 void Function(bool)? onPermission, 27 void Function(bool)? onPermission,
30 void Function(bool)? onStopping, 28 void Function(bool)? onStopping,
31 - void Function(Map<String, int>)? onVideoSizeChanged,  
32 - void Function(String)? onVoiceIdChanged,  
33 - void Function(String)? onRoleChanged, 29 + void Function(double)? onNuiAudioRMSChanged,
34 void Function(String)? onEventAsrStarted, 30 void Function(String)? onEventAsrStarted,
35 void Function(String)? onEventAsrResult, 31 void Function(String)? onEventAsrResult,
36 void Function(String)? onEventAsrPartitalResult, 32 void Function(String)? onEventAsrPartitalResult,
@@ -43,9 +39,7 @@ class SpeechToTextAlibaba { @@ -43,9 +39,7 @@ class SpeechToTextAlibaba {
43 onError: onError, 39 onError: onError,
44 onPermission: onPermission, 40 onPermission: onPermission,
45 onStopping: onStopping, 41 onStopping: onStopping,
46 - onVideoSizeChanged: onVideoSizeChanged,  
47 - onVoiceIdChanged: onVoiceIdChanged,  
48 - onRoleChanged: onRoleChanged, 42 + onNuiAudioRMSChanged: onNuiAudioRMSChanged,
49 onEventAsrStarted: onEventAsrStarted, 43 onEventAsrStarted: onEventAsrStarted,
50 onEventAsrResult: onEventAsrResult, 44 onEventAsrResult: onEventAsrResult,
51 onEventAsrPartitalResult: onEventAsrPartitalResult, 45 onEventAsrPartitalResult: onEventAsrPartitalResult,
@@ -54,12 +48,14 @@ class SpeechToTextAlibaba { @@ -54,12 +48,14 @@ class SpeechToTextAlibaba {
54 } 48 }
55 } 49 }
56 50
57 - /// 初始化引擎 51 + /// 初始化引擎
58 /// [userId] 用户ID 52 /// [userId] 用户ID
59 - static Future<void> initialize({required String appKey,required String token}) {  
60 - return _platform.initialize(appKey: appKey,token:token); 53 + static Future<void> initialize(
  54 + {required String appKey, required String token}) {
  55 + return _platform.initialize(appKey: appKey, token: token);
61 } 56 }
62 - /// 发起通话 57 +
  58 + /// 发起通话
63 /// [rtcToken] RTC通话令牌 59 /// [rtcToken] RTC通话令牌
64 /// [aiAgentInstanceId] AI代理实例ID 60 /// [aiAgentInstanceId] AI代理实例ID
65 /// [aiAgentUserId] AI代理用户ID 61 /// [aiAgentUserId] AI代理用户ID
@@ -81,14 +77,12 @@ class SpeechToTextAlibaba { @@ -81,14 +77,12 @@ class SpeechToTextAlibaba {
81 /// 开始一句话识别 77 /// 开始一句话识别
82 static Future<void> startDialog() => _platform.startDialog(); 78 static Future<void> startDialog() => _platform.startDialog();
83 79
84 - /// 结束识别 80 + /// 结束识别
85 static Future<void> stopDialog() => _platform.stopDialog(); 81 static Future<void> stopDialog() => _platform.stopDialog();
86 82
87 /// 页面dispose的时候释放 83 /// 页面dispose的时候释放
88 static Future<void> release() => _platform.release(); 84 static Future<void> release() => _platform.release();
89 85
90 -  
91 -  
92 /// 切换麦克风状态 86 /// 切换麦克风状态
93 /// [on] true开启,false关闭 87 /// [on] true开启,false关闭
94 static Future<void> switchMicrophone(bool on) => 88 static Future<void> switchMicrophone(bool on) =>
@@ -117,6 +111,4 @@ class SpeechToTextAlibaba { @@ -117,6 +111,4 @@ class SpeechToTextAlibaba {
117 /// [roleName] 角色名称 111 /// [roleName] 角色名称
118 static Future<void> setAIRole(String roleId, String roleName) => 112 static Future<void> setAIRole(String roleId, String roleName) =>
119 _platform.setAIRole(roleId, roleName); 113 _platform.setAIRole(roleId, roleName);
120 -  
121 } 114 }
122 -  
@@ -30,13 +30,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform { @@ -30,13 +30,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
30 void Function(bool)? _onStopping; 30 void Function(bool)? _onStopping;
31 31
32 /// 视频尺寸变化回调,包含宽高信息 32 /// 视频尺寸变化回调,包含宽高信息
33 - void Function(Map<String, int>)? _onVideoSizeChanged;  
34 -  
35 - /// 声音ID变化回调  
36 - void Function(String)? _onVoiceIdChanged;  
37 -  
38 - /// ���色变化回调  
39 - void Function(String)? _onRoleChanged; 33 + void Function(double)? _onNuiAudioRMSChanged;
40 34
41 /// AI代理状态变化回调 35 /// AI代理状态变化回调
42 void Function(String)? _onAIAgentStateChanged; 36 void Function(String)? _onAIAgentStateChanged;
@@ -131,9 +125,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform { @@ -131,9 +125,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
131 void Function(String)? onEventAsrStarted, 125 void Function(String)? onEventAsrStarted,
132 void Function(bool)? onPermission, 126 void Function(bool)? onPermission,
133 void Function(bool)? onStopping, 127 void Function(bool)? onStopping,
134 - void Function(Map<String, int>)? onVideoSizeChanged,  
135 - void Function(String)? onVoiceIdChanged,  
136 - void Function(String)? onRoleChanged, 128 + void Function(double)? onNuiAudioRMSChanged,
137 void Function(String)? onAIAgentStateChanged, 129 void Function(String)? onAIAgentStateChanged,
138 void Function(String)? onEventAsrResult, 130 void Function(String)? onEventAsrResult,
139 void Function(String)? onEventAsrPartitalResult, 131 void Function(String)? onEventAsrPartitalResult,
@@ -145,9 +137,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform { @@ -145,9 +137,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
145 _onEventAsrStarted = onEventAsrStarted; 137 _onEventAsrStarted = onEventAsrStarted;
146 _onPermission = onPermission; 138 _onPermission = onPermission;
147 _onStopping = onStopping; 139 _onStopping = onStopping;
148 - _onVideoSizeChanged = onVideoSizeChanged;  
149 - _onVoiceIdChanged = onVoiceIdChanged;  
150 - _onRoleChanged = onRoleChanged; 140 + _onNuiAudioRMSChanged = onNuiAudioRMSChanged;
151 _onAIAgentStateChanged = onAIAgentStateChanged; 141 _onAIAgentStateChanged = onAIAgentStateChanged;
152 _onEventAsrResult = onEventAsrResult; 142 _onEventAsrResult = onEventAsrResult;
153 _onEventAsrPartitalResult = onEventAsrPartitalResult; 143 _onEventAsrPartitalResult = onEventAsrPartitalResult;
@@ -177,15 +167,8 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform { @@ -177,15 +167,8 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
177 case 'onStopping': 167 case 'onStopping':
178 _onStopping?.call(call.arguments as bool); 168 _onStopping?.call(call.arguments as bool);
179 break; 169 break;
180 - case 'onVideoSizeChanged':  
181 - _onVideoSizeChanged  
182 - ?.call(Map<String, int>.from(call.arguments as Map));  
183 - break;  
184 - case 'onVoiceIdChanged':  
185 - _onVoiceIdChanged?.call(call.arguments as String);  
186 - break;  
187 - case 'onRoleChanged':  
188 - _onRoleChanged?.call(call.arguments as String); 170 + case 'onNuiAudioRMSChanged':
  171 + _onNuiAudioRMSChanged?.call(call.arguments as double);
189 break; 172 break;
190 case 'onAIAgentStateChanged': 173 case 'onAIAgentStateChanged':
191 _onAIAgentStateChanged?.call(call.arguments as String); 174 _onAIAgentStateChanged?.call(call.arguments as String);