Commit 293c0e0a536592ddcc61fdfa4e70217f27b07836

Authored by weixin_45529955
1 parent e4c3c584

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

... ... @@ -4,7 +4,13 @@
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch Chrome",
"request": "launch",
"type": "chrome",
"url": "http://localhost:8080",
"webRoot": "${workspaceFolder}"
},
{
"name": "Flutter-ReserveApp",
"request": "launch",
... ... @@ -363,6 +369,26 @@
"flutterMode": "release"
},
{
"name": "speech_to_text_alibaba",
"cwd": "library/speech_to_text_alibaba",
"request": "launch",
"type": "dart"
},
{
"name": "speech_to_text_alibaba (profile mode)",
"cwd": "library/speech_to_text_alibaba",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "speech_to_text_alibaba (release mode)",
"cwd": "library/speech_to_text_alibaba",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "speech_to_text_wdn_ohos",
"cwd": "library/speech_to_text_wdn_ohos",
"request": "launch",
... ... @@ -744,6 +770,26 @@
},
{
"name": "example",
"cwd": "library/speech_to_text_alibaba/example",
"request": "launch",
"type": "dart"
},
{
"name": "example (profile mode)",
"cwd": "library/speech_to_text_alibaba/example",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "example (release mode)",
"cwd": "library/speech_to_text_alibaba/example",
"request": "launch",
"type": "dart",
"flutterMode": "release"
},
{
"name": "example",
"cwd": "library/speech_to_text_wdn_ohos/example",
"request": "launch",
"type": "dart"
... ...
... ... @@ -20,19 +20,19 @@ if (flutterRoot == null) {
def flutterVersionCode = "610"
def flutterVersionCode = "611"
if (flutterVersionCode == null) {
flutterVersionCode = '610'
flutterVersionCode = '611'
}
def flutterVersionName = "6.1.0"
def flutterVersionName = "6.1.1"
if (flutterVersionName == null) {
flutterVersionName = '6.1.0'
flutterVersionName = '6.1.1'
}
... ...
class PhoneNumberExtractor {
// 提取所有电话号码(手机+座机)
static List<String> extractAllPhoneNumbers(String text) {
final phoneRegExp = RegExp(r'(?:(?:\+|00)86)?1[3-9]\d{9}|' // 手机
r'(?:(?:0\d{2,3}[- ]?)?\d{7,8}(?:[- ]?\d{1,4})?)' // 座机
);
return phoneRegExp
.allMatches(text)
.map((match) => match.group(0)!)
.toList();
}
// 提取手机号码
static List<String> extractMobileNumbers(String text) {
final mobileRegExp = RegExp(r'(?:(?:\+|00)86)?' // 国际前缀
r'1[3-9]\d{9}' // 手机号段
);
return mobileRegExp
.allMatches(text)
.map((match) => match.group(0)!)
.toList();
}
// 提取座机号码
static List<String> extractLandlineNumbers(String text) {
final landlineRegExp = RegExp(r'(?:0\d{2,3}[- ]?)?' // 区号
r'\d{7,8}' // 主号码
r'(?:[- ]?\d{1,4})?' // 分机号
);
return landlineRegExp
.allMatches(text)
.map((match) => match.group(0)!)
.toList();
}
// 分类提取
static Map<String, List<String>> extractPhoneNumbersByType(String text) {
return {
'mobile': extractMobileNumbers(text),
'landline': extractLandlineNumbers(text),
'all': extractAllPhoneNumbers(text),
};
}
// 验证并格式化座机号码
static String? formatLandlineNumber(String number) {
// 清理输入
String cleaned = number.replaceAll(RegExp(r'[^\d\-]'), '');
// 验证格式
final validRegExp = RegExp(r'^(0\d{2,3})?\d{7,8}$');
if (!validRegExp.hasMatch(cleaned)) {
return null;
}
// 格式化:添加分隔符
if (cleaned.length >= 11) {
// 有区号的情况
return '${cleaned.substring(0, 4)}-${cleaned.substring(4)}';
} else if (cleaned.length == 7 || cleaned.length == 8) {
// 无区号的情况
return cleaned;
}
return cleaned;
}
// 支持更多格式的座机号码
// 提取函数
static List<String> extractFlexibleLandlines(String text) {
final flexibleLandlineRegExp = RegExp(
r'('
r'(?:0(?:10|2[0-57-9]|[3-9]\d{2}))?' // 区号(010、021-029等)
r'[- ]?' // 分隔符
r'(?:\d{7,8})' // 主号码
r'(?:[- ]?(?:转|分机|ext?\.?)?[- ]?\d{1,4})?' // 分机号
r')',
caseSensitive: false // 不区分大小写
);
return flexibleLandlineRegExp
.allMatches(text)
.map((match) => match.group(0)!)
.toList();
}
}
// 使用事例
// final testText = '''
// 联系方式:
// 手机:13800138000、+8613800138001、008613800138002
// 座机:010-12345678、021-87654321、0755-1234567
// 带分机:028-87654321-1234
// 无区号:12345678
// 其他格式:(010)12345678、010 1234 5678
// ''';
// // 提取所有电话号码
// final allNumbers = PhoneNumberExtractor.extractAllPhoneNumbers(testText);
// print('所有电话号码: $allNumbers');
// // 只提取座机
// final landlines = PhoneNumberExtractor.extractLandlineNumbers(testText);
// print('座机号码: $landlines');
// // 只提取手机
// final mobiles = PhoneNumberExtractor.extractMobileNumbers(testText);
// print('手机号码: $mobiles');
// // 分类提取
// final categorized = PhoneNumberExtractor.extractPhoneNumbersByType(testText);
// print('分类结果: $categorized');
// // 格式化示例
// final formatted = PhoneNumberExtractor.formatLandlineNumber('01012345678');
// print('格式化后: $formatted'); // 输出: 010-12345678
// }
... ...
import 'package:intl/intl.dart';
class ChineseDateUtils {
// 提取文本中所有日期
static List<DateTime> extractAllDates(String text) {
List<DateTime> dates = [];
// 匹配中文日期
dates.addAll(_extractChineseDates(text));
// 匹配数字日期
dates.addAll(_extractNumericDates(text));
// 去重
return dates.toSet().toList();
}
// 提取第一个找到的日期
static DateTime? extractFirstDate(String text) {
final dates = extractAllDates(text);
return dates.isNotEmpty ? dates.first : null;
}
// 提取中文日期
static List<DateTime> _extractChineseDates(String text) {
List<DateTime> dates = [];
// 支持多种格式
final patterns = [
RegExp(r'([正元一二三四五六七八九十腊]{1,2})月([一二三四五六七八九十]{1,3})[日号]'),
RegExp(r'([正元一二三四五六七八九十腊]{1,2})月([初廿卅一二三四五六七八九十]{1,3})'),
];
for (var pattern in patterns) {
final matches = pattern.allMatches(text);
for (var match in matches) {
final date = _parseChineseDateMatch(match);
if (date != null) {
dates.add(date);
}
}
}
return dates;
}
// 提取数字日期
static List<DateTime> _extractNumericDates(String text) {
List<DateTime> dates = [];
final patterns = [
RegExp(r'(\d{1,2})[月\-/](\d{1,2})[日号]?'),
RegExp(r'(\d{4})年(\d{1,2})月(\d{1,2})日'),
];
for (var pattern in patterns) {
final matches = pattern.allMatches(text);
for (var match in matches) {
DateTime? date;
if (match.groupCount == 2) {
// 月日格式
int? month = int.tryParse(match.group(1)!);
int? day = int.tryParse(match.group(2)!);
if (month != null && day != null) {
date = _createDate(month, day);
}
} else if (match.groupCount == 3) {
// 年月日格式
int? year = int.tryParse(match.group(1)!);
int? month = int.tryParse(match.group(2)!);
int? day = int.tryParse(match.group(3)!);
if (year != null && month != null && day != null) {
date = DateTime(year, month, day);
}
}
if (date != null) {
dates.add(date);
}
}
}
return dates;
}
static DateTime? _parseChineseDateMatch(Match match) {
final monthStr = match.group(1);
final dayStr = match.group(2);
if (monthStr == null || dayStr == null) return null;
final month = _chineseToMonth(monthStr);
final day = _chineseToDay(dayStr);
if (month == null || day == null) return null;
return _createDate(month, day);
}
static int? _chineseToMonth(String chinese) {
final Map<String, int> map = {
'正': 1, '元': 1, '一': 1, '二': 2, '三': 3, '四': 4,
'五': 5, '六': 6, '七': 7, '八': 8, '九': 9, '十': 10,
'十一': 11, '腊': 12, '十二': 12
};
return map[chinese];
}
static int? _chineseToDay(String chinese) {
// 处理农历日期前缀
if (chinese.startsWith('初')) {
return parseChineseNumber(chinese.substring(1));
} else if (chinese.startsWith('廿')) {
final rest = chinese.substring(1);
return 20 + (parseChineseNumber(rest) ?? 0);
} else if (chinese.startsWith('卅')) {
final rest = chinese.substring(1);
return 30 + (parseChineseNumber(rest) ?? 0);
}
return parseChineseNumber(chinese);
}
static int? parseChineseNumber(String chinese) {
if (chinese.isEmpty) return null;
final Map<String, int> singleDigits = {
'一': 1, '二': 2, '三': 3, '四': 4, '五': 5,
'六': 6, '七': 7, '八': 8, '九': 9, '十': 10
};
// 处理简单数字
if (singleDigits.containsKey(chinese)) {
return singleDigits[chinese];
}
// 处理组合数字
if (chinese.contains('十')) {
final parts = chinese.split('十');
if (parts.length == 2) {
int? tens = parts[0].isEmpty ? 1 : singleDigits[parts[0]];
int? ones = parts[1].isEmpty ? 0 : singleDigits[parts[1]];
if (tens != null && ones != null) {
return tens * 10 + ones;
}
}
}
return null;
}
static DateTime? _createDate(int month, int day) {
try {
final now = DateTime.now();
final year = now.year;
// 验证日期有效性
final lastDay = DateTime(year, month + 1, 0).day;
if (day < 1 || day > lastDay) return null;
return DateTime(year, month, day);
} catch (e) {
return null;
}
}
// 格式化日期输出
static String formatDate(DateTime date, {String format = 'yyyy年MM月dd日'}) {
return DateFormat(format).format(date);
}
// 判断是否为有效日期
static bool isValidDate(int year, int month, int day) {
if (month < 1 || month > 12) return false;
if (day < 1) return false;
final lastDay = DateTime(year, month + 1, 0).day;
return day <= lastDay;
}
}
\ No newline at end of file
... ...
... ... @@ -26,8 +26,31 @@ class _LongPressFABState extends State<LongPressFAB>
showModalBottomSheet(
backgroundColor: Colors.transparent,
context: context,
isScrollControlled: true, // 允许滚动控制
builder: (context) {
return SpeechPage();
return MediaQuery.removePadding(
context: context,
removeTop: true,
child: Container(
height: MediaQuery.of(context).size.height *
0.8, // 例如设置高度为屏幕的40%
color: Colors.white,
padding: EdgeInsets.symmetric(horizontal: w(30)),
child: SpeechPage(),
));
// return DraggableScrollableSheet(
// initialChildSize: 0.8, // 初始高度为屏幕的40%
// minChildSize: 0.8, // 最小高度为屏幕的20%
// maxChildSize: 0.8, // 最大高度为屏幕的80%
// builder: (BuildContext context,
// ScrollController scrollController) {
// return Container(
// color: Colors.white,
// padding: EdgeInsets.symmetric(horizontal: w(30)),
// child: SpeechPage(),
// );
// });
});
},
onLongPressStart: (_) async {
... ...
import 'package:common_utils/common_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:one_key/pages/home/%20phone_number_extractor.dart';
import 'package:one_key/pages/home/chinese_date_utils.dart';
import 'package:one_key/pages/home/wave_form_widget.dart';
import 'package:one_key/res/colors.dart';
import 'package:one_key/utils/dialog_util.dart';
import 'package:one_key/utils/misc.dart';
import 'package:one_key/utils/utils.dart';
import 'package:one_key/widgets/round_button.dart';
import 'package:one_key/widgets/v_empty_view.dart';
... ... @@ -22,6 +27,20 @@ class SpeechPageState extends State<SpeechPage> {
List<dynamic> resultDate = [];
List<dynamic> resultPnum = [];
List<dynamic> resultName = [];
List<dynamic> resultSex = [];
List<dynamic> resultDsolt = [];
List<dynamic> resultPhone = [];
List<dynamic> resultDdate = [];
double volume = 0;
bool isListening = false; //是否正在识别
bool isInit = false; //是否初始化成功
@override
void initState() {
// TODO: implement initState
... ... @@ -38,10 +57,35 @@ class SpeechPageState extends State<SpeechPage> {
SpeechToTextAlibaba.release();
}
_clearResultData() {
result = '';
resultDate = [];
resultPnum = [];
resultName = [];
resultSex = [];
resultDsolt = [];
resultPhone = [];
resultDdate = [];
volume = 0;
isListening = false; //是否正在识别
// bool isInit = false; //是否初始化成功
}
// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initAlibaba() async {
await SpeechToTextAlibaba.initialize(
appKey: '88v9LR9mYYyvmSKw', token: '0bbc0ead105041239e2b118b47f30cde');
appKey: '88v9LR9mYYyvmSKw', token: '928034cf23c8486eaef7138e7d2cdc84');
setState(() {
isInit = true;
});
SpeechToTextAlibaba.setEngineEventHandler(
onCallBegin: () {
// setState(() {
... ... @@ -60,25 +104,48 @@ class SpeechPageState extends State<SpeechPage> {
onError: (error) {
print("---------------发生错误-----------------");
Utils.showToast(error);
isListening = false;
setState(() {});
// _addMessage("发生错误: $error", false);
},
onPermission: (isPermission) {
print("---------------onPermission-----------------");
// 没有权限的话弹窗提示跳转设置页
if (isPermission == false) {
//没有获得权限,跳转至系统权限设置页
DialogUtil.openAlertDialog(
context, '未获得麦克风权限', "请在手机的权限管理里,将麦克风权限开启", '去设置', 340.0,
onTap: () {
openSetting();
if (Navigator.canPop(context)) {
Navigator.pop(context, '');
}
});
}
},
onStopping: (isStop) {
if (isStop == false) {
//开始按钮亮起,结束按钮置灰
isListening = false;
setState(() {});
}
print("---------------onNetworkQuality-----------------");
// setState(() {
// _networkQuality = quality;
// });
},
onVoiceIdChanged: (voiceId) {
print("---------------onVoiceIdChanged-----------------");
// _addMessage("AI声音已切换: $voiceId", false);
},
onRoleChanged: (role) {
print("---------------onRoleChanged-----------------");
// _addMessage("AI角色已切换: $role", false);
onNuiAudioRMSChanged: (double val) {
print(val);
// volume = val;
// setState(() {});
/**
* 音频能量值回调
音频数据能量值回调,范围-160至0,一般用于UI展示语音动效
*/
//
},
onEventAsrStarted: (state) {
print("---------------nEventAsrStarted----------------- $state");
... ... @@ -133,9 +200,10 @@ class SpeechPageState extends State<SpeechPage> {
// break;
// }
});
_getDate(result);
},
);
await SpeechToTextAlibaba.startDialog();
// await SpeechToTextAlibaba.startDialog();
}
Map<String, int> chineseNumberMap = {
... ... @@ -186,10 +254,13 @@ class SpeechPageState extends State<SpeechPage> {
//筛选日期
_getDate(String result) {
result = result.replaceAll('幺', '1');
resultDate = [];
// result = '录入预订一个下周一的晚餐,姓名王冬妮,性别女士,电话是幺八幺六六六六六六六六,预订日期是下周一,到店时间是十一点半,十五人';
// -****************************** 预订或者变更日期 ***********************************-
// 定义正则表达式,匹配“明天”或“春节”
RegExp regExp = RegExp(r"今天 | 明天|后天|大后天|元旦|春节|除夕|国庆");
// result = '预订一个今天明天后天大后天元旦春节除夕国庆';
RegExp regExp = RegExp(r"今天|明天|后天|大后天|元旦|春节|除夕|国庆");
// 使用RegExp的allMatches方法来找到所有匹配项
Iterable<Match> matches = regExp.allMatches(result);
... ... @@ -200,12 +271,14 @@ class SpeechPageState extends State<SpeechPage> {
// 打印结果
print(dates); // 输出: [明天, 春节]
if (dates.contains('春节')) {
String str = '';
// String str = '';
final lunarCalendar = LunarCalendar.from(
utcDateTime: DateTime.now().toUtc(),
);
str = '${lunarCalendar.chineseNewYearString}';
resultDate.add(str);
// str = '${lunarCalendar.chineseNewYearString}';
// resultDate.add(str);
resultDate.add(DateUtil.formatDate(lunarCalendar.chineseNewYear,
format: DateFormats.y_mo_d));
}
if (dates.contains('国庆')) {
... ... @@ -222,21 +295,20 @@ class SpeechPageState extends State<SpeechPage> {
}
if (dates.contains('元旦')) {
// 获取当前年份的元旦日期
DateTime nationalDay = DateTime(DateTime.now().year, 1, 1);
DateTime newYearDay = DateTime(DateTime.now().year, 1, 1);
// 检查元旦是否已经到来
if (DateTime.now().isAfter(nationalDay)) {
if (DateTime.now().isAfter(newYearDay)) {
// '元旦已经过去,用明年的日期
nationalDay = DateTime(DateTime.now().year + 1, 1, 1);
newYearDay = DateTime(DateTime.now().year + 1, 1, 1);
}
resultDate
.add(DateUtil.formatDate(nationalDay, format: DateFormats.y_mo_d));
.add(DateUtil.formatDate(newYearDay, format: DateFormats.y_mo_d));
}
if (dates.contains('除夕')) {
final lunarCalendar = LunarCalendar.from(
utcDateTime: DateTime.now().toUtc(),
);
DateTime chuxi = DateUtil.getDateTime(lunarCalendar.chineseNewYearString)
.subtract(Duration(days: 1));
DateTime chuxi = lunarCalendar.chineseNewYear.subtract(Duration(days: 1));
resultDate.add(DateUtil.formatDate(chuxi, format: DateFormats.y_mo_d));
}
//今天
... ... @@ -262,18 +334,46 @@ class SpeechPageState extends State<SpeechPage> {
//大后天
if (dates.contains('大后天')) {
// 获取当前日期和时间
DateTime houday = DateTime.now().add(Duration(days: 3));
resultDate.add(DateUtil.formatDate(houday, format: DateFormats.y_mo_d));
DateTime dhouday = DateTime.now().add(Duration(days: 3));
resultDate.add(DateUtil.formatDate(dhouday, format: DateFormats.y_mo_d));
}
print(resultDate);
// 几月几号
DateTime? dateHao = _haoDate(result);
if (dateHao != null) {
resultDate.add(DateUtil.formatDate(dateHao, format: DateFormats.y_mo_d));
// 方法1
// final dates1 = DateExtractor.extractChineseDates(text);
// print('提取到的日期:');
// for (var date in dates1) {
// if (date != null) {
// print(DateFormat('yyyy-MM-dd').format(date));
// }
// }
// // 方法3(推荐)
// final dates2 = ChineseDateUtils.extractAllDates(text);
// print('\n使用工具类提取:');
// for (var date in dates2) {
// print(ChineseDateUtils.formatDate(date));
// }
// result = '预订一个十一月二十号的预订';
// 提取混合文本中的日期
final mixedDates = ChineseDateUtils.extractAllDates(result);
print(mixedDates);
// print('\n混合文本中的日期:');
for (var date in mixedDates) {
resultDate.add(DateUtil.formatDate(date, format: DateFormats.y_mo_d));
// print(ChineseDateUtils.formatDate(date, format: 'MM月dd日'));
}
print(resultDate);
//2025年3月5日
_extractDatesFromText(result);
// _extractDatesFromText(result);
// result = '预订一个周一的预订';
//周几
RegExp regExpWeek = RegExp(r'周一|周二|周三|周四|周五|周六|周日');
... ... @@ -311,24 +411,247 @@ class SpeechPageState extends State<SpeechPage> {
}
//下周几
// result = '预订一个下周二的预订';
RegExp regExpWeekNext = RegExp(r'下周一|下周二|下周三|下周四|下周五|下周六|下周日|下周天');
Iterable<Match> matchesWeekNext = regExpWeekNext.allMatches(result);
// 获取当前是周几(0表示周日,1表示周一,依此类推)
int weekdayNext = now.weekday;
//姓名(非必填)
//性别(非必填)识别女士、先生、公司
//手机号(非必填)识别手机号、座机号
//到店时间(必填,没有准确到店时间,有日期也行)
//餐段(必填)识别早上|早餐|早饭|上午|中午|午餐|午饭|下午|晚上|晚餐|晚饭
for (Match matcht in matchesWeekNext) {
int? targetWeekday;
String? weekdayText = matcht.group(0); // 获取匹配到的文本
if (weekdayText == '下周一') {
targetWeekday = 0; // 周三对应的数字是4(周一为1)
} else if (weekdayText == '下周二') {
targetWeekday = 1; // 周三对应的数字是4(周一为1)
} else if (weekdayText == '下周三') {
targetWeekday = 2; // 周三对应的数字是4(周一为1)
} else if (weekdayText == '下周四') {
targetWeekday = 3; // 周三对应的数字是4(周一为1)
} else if (weekdayText == '下周五') {
targetWeekday = 4; // 周三对应的数字是4(周一为1)
} else if (weekdayText == '下周六') {
targetWeekday = 5; // 周三对应的数字是4(周一为1)
} else if (weekdayText == '下周日' || weekdayText == '下周天') {
targetWeekday = 5; // 周三对应的数字是4(周一为1)
} else {
// 处理其他情况或不做任何操作。
}
if (targetWeekday != null) {
// 计算下周的某一天,例如下周二
// 注意:DateTime的星期是从0开始的,0代表星期一,1代表星期二,以此类推
DateTime nextWeek =
now.add(Duration(days: 8 - now.weekday + targetWeekday));
resultDate
.add(DateUtil.formatDate(nextWeek, format: DateFormats.y_mo_d));
}
}
// 合并数组并去重
// 时间结果去重 ======== 合并数组并去重
Set setArr = new Set(); //用set进行去重
setArr.addAll(resultDate); //把数组塞进set里
resultDate = setArr.toList();
//提取人数
RegExp regExpPnum = RegExp(r'\b(几[人个位]{1,2,3})\b');
Iterable<RegExpMatch> matchesPnum = regExpPnum.allMatches(result);
for (RegExpMatch match in matchesPnum) {
resultPnum.add(match.group(1));
// -****************************** 预订者姓名 ***********************************-
//姓名(非必填)
// result = '预订人王冬妮电话18161897899';
// RegExp nameRegex =
// RegExp(r'[\u4e00-\u9fa5]{2,5}(?:[\u4e00-\u9fa5·]{1,5})?');
// Iterable<Match> matchesName = nameRegex.allMatches(result);
// for (Match matchname in matchesName) {
// // print(match[0]); // 输出: 张三 或 李四
// resultName.add(matchname[0] ?? '');
// }
// String text = "参加会议的人员有:张三、李四,还有王五。请通知他们。";
// 匹配2-4个连续的中文字符(覆盖多数单姓和复姓)
// RegExp namePattern = RegExp(r'[\u4e00-\u9fa5]{2,4}');
// RegExp(r'([李王张刘陈杨赵黄周吴徐孙胡朱高林何郭马罗梁宋郑谢韩唐冯董萧程曹袁邓许傅沈曾彭吕苏卢蒋蔡魏贾丁叶阎余潘杜戴钟姜范江孔崔覃史顾侯邵孟龙万段雷钱汤尹黎易常武乔贺龚文欧]'
// r'[\u4e00-\u9fa5]{1,2}'
// r'(?:·[\u4e00-\u9fa5]{1,3})?)'
// )
// Iterable<Match> matchesName = namePattern.allMatches(result);
// List<String> extractedNames = [];
// for (Match match in matchesName) {
// String potentialName = match.group(0)!;
// // 简单的过滤:排除明显不是姓名的常用词汇
// if (!['人员', '还有', '通知', '他们'].contains(potentialName)) {
// extractedNames.add(potentialName);
// }
// }
// print(extractedNames); // 输出:[张三, 李四, 王五]
// 使用捕获组 () 捕获名字
// 录入的时候需要说姓名xxx后面需要有,号或者其他非汉字符号间隔
// result = "录入一个预订,姓名王冬妮,电话18729999999";
// 使用括号 () 创建捕获组,提取“姓名是”后面的字符
// \u4e00-\u9fa5 是匹配中文字符的范围
RegExp reg = RegExp(r"姓名([\u4e00-\u9fa5]{2,4})");
Match? match = reg.firstMatch(result);
if (match != null) {
// group(0) 是完整匹配到的字符串 "姓名是张三"
// group(1) 是第一个括号内捕获的内容 "张三"
// print(match.group(1)); // 输出: 张三
resultName.add(match.group(1));
}
print(resultName);
// 合并数组并去重
Set setArrName = new Set(); //用set进行去重
setArrName.addAll(resultName); //把数组塞进set里
resultName = setArrName.toList();
// -****************************** 预订者性别 ***********************************-
//性别(非必填)识别女士、先生、公司、贵宾
RegExp sexRegex = RegExp(r'女士|先生|公司|贵宾');
Iterable<Match> matchesSex = sexRegex.allMatches(result);
for (Match match in matchesSex) {
// print(match[0]); // 输出: 张三 或 李四
resultSex.add(match[0] ?? '');
}
// 合并数组并去重
Set setArrSex = new Set(); //用set进行去重
setArrSex.addAll(resultSex); //把数组塞进set里
resultSex = setArrSex.toList();
// -****************************** 餐段 ***********************************-
//餐段(必填)识别早上|早餐|早饭|上午|中午|午餐|午饭|下午|晚上|晚餐|晚饭
RegExp dsoltRegex = RegExp(r'早上|早餐|早饭|上午|中午|午餐|午饭|下午|晚上|晚餐|晚饭');
Iterable<Match> matchesDsolt = dsoltRegex.allMatches(result);
for (Match match in matchesDsolt) {
// print(match[0]); //
resultDsolt.add(match[0] ?? '');
}
// 合并数组并去重
Set setArrDsolt = new Set(); //用set进行去重
setArrDsolt.addAll(resultDsolt); //把数组塞进set里
resultDsolt = setArrDsolt.toList();
// -****************************** 手机或者座机号 ***********************************-
//手机号(非必填)识别手机号、座机号
String phoneData = convertChineseNumberToArabic(result);
// String text = "我的手机号是13812345678,座机号是010-12345678。";
print(phoneData);
// final testText = '''
// 联系方式:
// 手机:13800138000、+8613800138001、008613800138002
// 座机:010-12345678、021-87654321、0755-1234567
// 带分机:028-87654321-1234
// 无区号:12345678
// 其他格式:(010)12345678、010 1234 5678
// ''';
// 提取所有电话号码
final allNumbers = PhoneNumberExtractor.extractAllPhoneNumbers(phoneData);
print('所有电话号码: $allNumbers');
resultPhone.addAll(allNumbers);
// 创建正则表达式对象
// RegExp phoneRegex = RegExp(r'(^1\d{10}$)');
// RegExp landlineRegex = RegExp(r'(\d{3,4}-\d{7,8})');
// // 查找手机号
// Iterable<Match> phoneMatches = phoneRegex.allMatches(phoneData);
// List<String?> phones = phoneMatches.map((match) => match.group(0)).toList();
// print('手机号: $phones');
// resultPhone.addAll(phones);
// 查找座机号
// Iterable<Match> landlineMatches = landlineRegex.allMatches(phoneData);
// List<String?> landlines =
// landlineMatches.map((match) => match.group(0)).toList();
// print('座机号: $landlines');
// resultPhone.addAll(landlines);
//电话号码结果去重 ========== 合并数组并去重
Set setArrPhone = new Set(); //用set进行去重
setArrPhone.addAll(resultPhone); //把数组塞进set里
resultPhone = setArrPhone.toList();
// -****************************** 到店时间 ***********************************-
// //到店时间(必填,没有准确到店时间,有日期也行)
// // 找到 hour 和 minute
// //把汉字数字转换成阿拉伯数字
// String ddateData = convertChineseNumberToArabic(result);
// // 测试字符串
// // String testStrings = '会议在9点30分开始, 午饭时间是12点半, 现在是8点, 这不是时间';
// // 定义正则表达式
// final timePattern = RegExp(r'(\d{1,2})点(\d{1,2})分|(\d{1,2})点半|(\d+)点');
// List<Match> matchesDdate = timePattern.allMatches(ddateData).toList();
// for (Match match in matchesDdate) {
// // 检查是哪种匹配模式,并提取捕获组
// if (match.group(1) != null && match.group(2) != null) {
// // 匹配了 "H点M分" 格式
// print('找到时间: ${match.group(1)}点${match.group(2)}分');
// int hour = int.parse(match.group(1)!); // 小时部分,注意使用!来断言非null
// int minute = int.parse(match.group(2)!); // 小时部分,注意使用!来断言非null
// print('$hour:$minute');
// // 上午
// resultDdate.add('$hour:$minute');
// // 下午
// resultDdate.add('${hour + 12}:$minute');
// } else if (match.group(3) != null) {
// // 匹配了 "H点半" 格式
// int hour = int.parse(match[3]!); // 小时部分,注意使用!来断言非null
// print('找到时间: ${match.group(3)}点半');
// print('$hour:30');
// // 上午
// resultDdate.add('$hour:30');
// // 下午
// resultDdate.add('${hour + 12}:30');
// } else if (match.group(4) != null) {
// // 匹配了 "H点" 格式
// print('找到时间: ${match.group(4)}点');
// int hour = int.parse(match.group(4)!); // 提取小时数并转换为整数
// print('$hour:00');
// // 上午
// resultDdate.add('$hour:00');
// // 下午
// resultDdate.add('${hour + 12}:00');
// }
// }
// -****************************** 到店时间 ***********************************-
List<String> times = extractTimes(result);
resultDdate.addAll(times);
// 到店时间结果去重 ======== 合并数组并去重
Set setArrDdate = new Set(); //用set进行去重
setArrDdate.addAll(resultDdate); //把数组塞进set里
resultDdate = setArrDdate.toList();
// -****************************** 人数 ***********************************-
//提取人数
List<String> pnum = extractNumbers(result);
resultPnum.addAll(pnum);
// 合并数组并去重
Set setArrPnum = new Set(); //用set进行去重
setArrPnum.addAll(resultPnum); //把数组塞进set里
... ... @@ -336,15 +659,294 @@ class SpeechPageState extends State<SpeechPage> {
setState(() {});
}
List<String> extractNames(String text) {
List<String> names = [];
// 匹配一个或多个汉字
RegExp nameRegex = RegExp(r'[\u4e00-\u9fa5]+');
Iterable<Match> matches = nameRegex.allMatches(text);
for (Match match in matches) {
String name = match.group(0)!;
// 可以在这里过滤,比如只提取 "张三"
if (name == '张三') {
names.add(name);
}
}
return names;
}
List<String> extractNumbers(String text) {
List<String> numbers = [];
// 匹配中文数字和阿拉伯数字
// RegExp regex = RegExp(r'(\d+|[零一二三四五六七八九十百千万亿](个人|位|人)');
final patterns = [
RegExp(r'([0-9零一二三四五六七八九十百千万亿]+)[位人员名]'),
RegExp(r'([0-9]+)\s*个人'),
RegExp(r'([0-9]+)\s*人'),
RegExp(r'([0-9]+)\s*位'),
];
for (var pattern in patterns) {
final Iterable<Match> matches = pattern.allMatches(text);
for (var match in matches) {
String numberStr = match.group(1)!;
bool isNmuber = RegExp(r'^\d+$').hasMatch(numberStr);
String? number;
if (isNmuber) {
//提取出来的是阿拉伯数字
number = numberStr;
} else {
number = ChineseDateUtils.parseChineseNumber(numberStr).toString();
}
if (number != null) {
numbers.add(number);
}
}
}
return numbers;
}
List<String> extractTimes(String text) {
List<String> extractedTimes = [];
// final String text = '''
// 我今天十一点半要去开会,然后十二点吃午饭,
// 下午一点有约会,上午两点还有另一个会议。
// 记得三点钟打电话,晚上七点吃饭。
// ''';
// 正则表达式匹配中文时间格式
RegExp timeRegExp = RegExp(
r'([上下]午)?([零一二三四五六七八九十两]+)点(半|整|[零一二三四五六七八九十]+分?)?',
caseSensitive: false);
Iterable<Match> matches = timeRegExp.allMatches(text);
extractedTimes = matches.map((match) => match.group(0)!).toList();
// 提取并转换时间
List<String> convertedTimes = [];
for (var time in extractedTimes) {
String? standardTime = convertChineseTime(time);
if (standardTime != null) {
print(time); //提取出来的汉字时间 十一点半| 下午一点|十二点|上午两点|三点|七点
print(standardTime); // 转换后的 07:30 | 11:30
convertedTimes.add('$standardTime');
}
}
// setState(() {
extractedTimes = convertedTimes;
return extractedTimes;
// });
}
String? convertChineseTime(String chineseTime) {
// 移除空格
String time = chineseTime.trim();
// 处理上午/下午
bool isAfternoon = time.contains('下午');
bool isMorning = time.contains('上午');
// 移除上午/下午前缀
if (isAfternoon || isMorning) {
time = time.replaceAll(RegExp(r'[上下]午'), '');
}
// 提取小时和分钟
RegExp hourMinuteReg =
RegExp(r'([零一二三四五六七八九十两]+)点(半|整|([零一二三四五六七八九十]+)分?)?');
Match? match = hourMinuteReg.firstMatch(time);
if (match == null) return null;
String hourChinese = match.group(1)!;
String minuteChinese = match.group(2) ?? '整';
// 转换中文数字
int hour = chineseToNumberTime(hourChinese);
// 处理12小时制转24小时制
if (isAfternoon && hour < 12) {
hour += 12;
} else if (isMorning && hour == 12) {
hour = 0; // 上午12点就是0点
}
// 转换分钟
int minute = 0;
if (minuteChinese.contains('半')) {
minute = 30;
} else if (minuteChinese.contains('分')) {
String minuteNum = minuteChinese.replaceAll('分', '');
minute = chineseToNumberTime(minuteNum);
}
return '${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}';
}
int chineseToNumberTime(String chinese) {
Map<String, int> numberMap = {
'零': 0,
'一': 1,
'二': 2,
'三': 3,
'四': 4,
'五': 5,
'六': 6,
'七': 7,
'八': 8,
'九': 9,
'十': 10,
'十一': 11,
'十二': 12,
'两': 2,
'廿': 20,
'卅': 30
};
// 处理复合数字
if (chinese.length > 1 && !numberMap.containsKey(chinese)) {
if (chinese.startsWith('十')) {
return 10 + numberMap[chinese.substring(1)]!;
} else if (chinese.endsWith('十')) {
return numberMap[chinese.substring(0, 1)]! * 10;
}
}
return numberMap[chinese] ?? int.tryParse(chinese) ?? 0;
}
DateTime parseChineseDate(String dateString) {
dateString = '十一月二十号';
// 定义中文月份到数字月份的映射
Map<String, int> monthMap = {
"一月": 1,
"二月": 2,
"三月": 3,
"四月": 4,
"五月": 5,
"六月": 6,
"七月": 7,
"八月": 8,
"九月": 9,
"十月": 10,
"十一月": 11,
"十二月": 12,
};
// 定义中文日到数字日的映射
Map<String, int> dayMap = {
"初一": 1, "初二": 2, "初三": 3,
"初四": 4, "初五": 5, "初六": 6,
"初七": 7, "初八": 8, "初九": 9,
"初十": 10, "十一": 11, "十二": 12,
"十三": 13, "十四": 14, "十五": 15,
"十六": 16, "十七": 17, "十八": 18,
"十九": 19, "二十": 20, "廿一": 21, // 注意这里的“廿”在某些情况下可能需要根据具体语境调整或省略
"廿二": 22, "廿三": 23, "廿四": 24,
"廿五": 25, "廿六": 26, "廿七": 27,
"廿八": 28, "廿九": 29, "三十": 30,
"三十一": 31 // 注意这里的“卅一”在某些情况下可能需要根据具体语境调整或省略
};
// 使用正则表达式提取月份和日期部分
RegExp regExp =
RegExp(r'([\u4e00-\u9fa5]+)(\d+)号'); // 注意这里的正则表达式可能需要根据你的具体格式调整
Iterable<RegExpMatch> matches = regExp.allMatches(dateString);
if (matches.isNotEmpty) {
RegExpMatch match = matches.first;
String? monthStr = match.group(1);
String? dayStr = match.group(2);
int month = monthMap[monthStr] ?? -1;
int day = dayMap[dayStr ?? '' + '号'] ?? -1; // 这里假设日期后面总是跟着“号”字,如果不是,请调整逻辑
if (month != -1 && day != -1) {
// 获取当前年份,你也可以根据需要传入特定的年份
int year = DateTime.now().year;
return DateTime(year, month, day);
} else {
throw Exception('无法解析日期:$dateString');
}
} else {
throw Exception('无法找到匹配的日期格式:$dateString');
}
}
void extractChineseDate(String text) {
// 定义月份中文到数字的映射
final Map<String, int> monthMap = {
'一': 1,
'二': 2,
'三': 3,
'四': 4,
'五': 5,
'六': 6,
'七': 7,
'八': 8,
'九': 9,
'十': 10,
'十一': 11,
'十二': 12,
};
// 定义一个能匹配中文日期格式的正则
// (一..十二)月? (数字)号
RegExp dateRegExp = RegExp(r'((一|二|三|四|五|六|七|八|九|十|十一|十二)月)?(\d{1,2})号');
var matches = dateRegExp.allMatches(text);
for (var match in matches) {
String? monthStr = match.group(2); // 捕获月份名 (如 "十")
String? dayStr = match.group(3); // 捕获日期数字 (如 "20")
int month =
monthStr != null ? (monthMap[monthStr] ?? 0) : 0; // 获取月份数字,默认为0
int day = int.tryParse(dayStr ?? '0') ?? 0; // 转换日期数字
// 假设当前年份 (这里可以根据需要修改或从文本中提取)
int currentYear = DateTime.now().year;
if (month > 0 && day > 0) {
try {
// 使用 intl 包格式化日期
DateTime date = DateTime(currentYear, month, day);
String formattedDate = DateFormat('yyyy-MM-dd').format(date);
print('匹配到日期: ${match.group(0)} -> 格式化日期: $formattedDate');
} catch (e) {
print('日期转换错误: ${e.toString()}');
}
}
}
}
//带有“几月几号”日期解析
DateTime? _haoDate(String result) {
//
// RegExp regExp =
// RegExp(r'([零一二三四五六七八九十百千万亿]+)月([零一二三四五六七八九十百千万亿]+)号'); // 正则表达式匹配数字和“号”
RegExp regExp = RegExp(r'(\d+)月(\d+)号'); // 正则表达式匹配数字和“号”
Match? match = regExp.firstMatch(result);
if (match != null) {
int month = int.parse(match.group(1)!); // 获取月份并转换为整数
int day = int.parse(match.group(2)!); // 获取日期并转换为整数
//如果月份小于此月,则用year + 1,否则用year
DateTime now = DateTime.now();
int monthNow = now.month;
int dayNow = now.day;
int year = now.year;
if (month < monthNow) {
year = now.year + 1;
} else if (month == monthNow) {
if (day < dayNow) {
year = now.year + 1;
}
}
DateTime date =
DateTime(2000, month, day); // 假设年份为2000,根据需要可以调整年份或使用完整日期信息。
DateTime(year, month, day); // 假设年份为2000,根据需要可以调整年份或使用完整日期信息。
return date;
// print("解析后的日期: ${date.toString()}"); // 输出解析后的日期
} else {
... ... @@ -353,6 +955,243 @@ class SpeechPageState extends State<SpeechPage> {
}
}
//把字符串中的汉字数字转换成阿拉伯数字 月份
String convertChineseNumberToArabicMonth(String chineseNumber) {
Map<String, String> chineseToArabic = {
'零': '0',
'一': '1',
'幺': '1',
'二': '2',
'三': '3',
'四': '4',
'五': '5',
'六': '6',
'七': '7',
'八': '8',
'九': '9',
'十': '10',
'十一': '11',
'十二': '12',
'十三': '13',
'十四': '14',
'十五': '15',
'十六': '16',
'十七': '17',
'十八': '18',
'十九': '19',
'二十': '20',
'二十一': '21',
'二十二': '22',
'二十三': '23',
'二十四': '24',
'二十五': '25',
'二十六': '26',
'二十七': '27',
'二十八': '28',
'二十九': '29',
'三十': '30',
'三十一': '31',
};
String result = '';
for (int i = 0; i < chineseNumber.length; i++) {
String char = chineseNumber[i];
if (chineseToArabic.containsKey(char)) {
result += chineseToArabic[char]!;
} else {
result += char; // 如果不是汉字数字,直接添加到结果中
}
}
return result;
}
int chineseNumberToArabic(String text) {
/// 汉字数字到阿拉伯数字映射
const Map<String, int> chineseToDigit = {
'零': 0,
'一': 1,
'二': 2,
'三': 3,
'四': 4,
'五': 5,
'六': 6,
'七': 7,
'八': 8,
'九': 9,
};
/// 单位映射
const Map<String, int> unitMap = {
'十': 10,
'百': 100,
'千': 1000,
'万': 10000,
'亿': 100000000,
};
int result = 0;
int currentNum = 0; // 当前数字(如“三”中的 3)
int currentUnitValue = 1; // 当前单位值 (如“十”是 10)
// 遍历字符串,优先处理“万”和“亿”
for (int i = 0; i < text.length; i++) {
String char = text[i];
if (chineseToDigit.containsKey(char)) {
currentNum = chineseToDigit[char]!;
// 如果是“一十”,则直接变成 10,而不是 1*10
if (currentNum == 1 &&
(i > 0 &&
(text[i - 1] == '十' ||
text[i - 1] == '百' ||
text[i - 1] == '千'))) {
currentNum = 0; // 避免“一十”变成 10
}
} else if (unitMap.containsKey(char)) {
int unit = unitMap[char]!;
if (unit >= 10000) {
// 处理万、亿,先结算前面部分
result += currentNum * currentUnitValue;
currentNum = 0; // 重置当前数字
currentUnitValue = 1; // 重置单位
result *= unit; // 乘以万或亿
} else {
result += currentNum * currentUnitValue;
currentNum = 0; // 重置当前数字
currentUnitValue = unit; // 更新单位
}
} else {
// 遇到非数字/单位字符,先结算之前的
result += currentNum * currentUnitValue;
currentNum = 0;
currentUnitValue = 1;
}
}
// 结算最后一部分
result += currentNum * currentUnitValue;
return result;
}
String convertChineseToArabic(String text) {
// 中文数字到阿拉伯数字的映射
Map<String, int> chineseNumbers = {
'零': 0,
'一': 1,
'二': 2,
'三': 3,
'四': 4,
'五': 5,
'六': 6,
'七': 7,
'八': 8,
'九': 9,
'十': 10,
'百': 100,
'千': 1000,
'万': 10000,
'亿': 100000000
};
// 处理多位数字,例如“二十二”
RegExp regExp = RegExp(r'([零一二三四五六七八九]{1,4})(百|千|万|亿)?');
List<String?> matches =
regExp.allMatches(text).map((m) => m.group(0)).toList();
int convertChineseNum(String numStr) {
int result = 0;
int unit = 1; // 个位
for (int i = numStr.length - 1; i >= 0; i--) {
String char = numStr[i];
if (char == '十') {
unit = 10;
} else if (char == '百') {
unit = 100;
} else if (char == '千') {
unit = 1000;
} else if (char == '万') {
unit = 10000;
} else if (char == '亿') {
unit = 100000000;
} else {
result += chineseNumbers[char]! * unit;
unit = 1; // 重置单位为个位
}
}
return result;
}
String result = text;
for (String? match in matches.reversed) {
// 从后往前替换,避免索引错位
if (match != null) {
result = result.replaceAll(match, convertChineseNum(match).toString());
}
}
return result;
}
String chineseToNumber(String chinese) {
Map<String, int> map = {
"零": 0,
"一": 1,
"二": 2,
"三": 3,
"四": 4,
"五": 5,
"六": 6,
"七": 7,
"八": 8,
"九": 9,
"十": 10,
"百": 100,
"千": 1000,
"万": 10000,
"亿": 100000000
};
List<String> parts = chinese.split(RegExp(r'(\d+)')); // 分割数字和单位
int result = 0;
int temp = 1; // 临时变量,用于记录当前位的权值
for (String part in parts) {
if (map.containsKey(part)) {
if (map[part]! >= 10) {
// 是单位
temp = map[part]!;
} else {
// 是数字
result += temp * map[part]!;
temp = 1; // 重置temp为1,准备下一个数字的乘法
}
}
}
return result.toString();
}
//把字符串中的汉字数字转换成阿拉伯数字
String convertChineseNumberToArabic(String chineseNumber) {
Map<String, String> chineseToArabic = {
'零': '0',
'一': '1',
'幺': '1',
'二': '2',
'三': '3',
'四': '4',
'五': '5',
'六': '6',
'七': '7',
'八': '8',
'九': '9'
};
String result = '';
for (int i = 0; i < chineseNumber.length; i++) {
String char = chineseNumber[i];
if (chineseToArabic.containsKey(char)) {
result += chineseToArabic[char]!;
} else {
result += char; // 如果不是汉字数字,直接添加到结果中
}
}
return result;
}
_extractDatesFromText(String text) {
// 定义一个正则表达式来匹配日期
// 示例:匹配形如 "2023年3月15日" 或 "15/03/2023" 的日期
... ... @@ -399,6 +1238,7 @@ class SpeechPageState extends State<SpeechPage> {
return Center(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
height: ScreenUtil().setWidth(48),
... ... @@ -407,57 +1247,162 @@ class SpeechPageState extends State<SpeechPage> {
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_ffffff)),
color: MyColours.color_515151)),
),
VEmptyView(40),
// VEmptyView(20),
// WaveformWidget(volume: volume), // 在录音时传入音量值
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.center,
alignment: Alignment.centerLeft,
child: Text(result,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_ffffff)),
color: MyColours.color_515151)),
),
VEmptyView(40),
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.center,
child: Text('日期:${resultDate.join('、')}',
alignment: Alignment.centerLeft,
child: Text('预订日期:${resultDate.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_ffffff)),
color: MyColours.color_515151)),
),
VEmptyView(40),
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.center,
alignment: Alignment.centerLeft,
child: Text('姓名:${resultName.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_515151)),
),
VEmptyView(40),
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.centerLeft,
child: Text('性别:${resultSex.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_515151)),
),
VEmptyView(40),
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.centerLeft,
child: Text('电话:${resultPhone.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_515151)),
),
VEmptyView(40),
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.centerLeft,
child: Text('人数:${resultPnum.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_ffffff)),
color: MyColours.color_515151)),
),
VEmptyView(40),
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.centerLeft,
child: Text('餐段:${resultDsolt.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_515151)),
),
VEmptyView(40),
RoundButton(
text: '结束识别',
width:
(ScreenUtil().screenWidth - ScreenUtil().setWidth(62) * 2) /
Container(
// height: ScreenUtil().setWidth(48),
alignment: Alignment.centerLeft,
child: Text('到店时间:${resultDdate.join('、')}',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: ScreenUtil().setSp(32),
color: MyColours.color_515151)),
),
VEmptyView(50),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
RoundButton(
text: '开始识别',
width: (ScreenUtil().screenWidth -
ScreenUtil().setWidth(82) * 2) /
2,
height: ScreenUtil().setWidth(95),
radius: 30,
gradientStart: isInit && !isListening
? MyColours.color_438E93
: MyColours.color_999999,
gradientEnd: isInit && !isListening
? MyColours.color_438E93
: MyColours.color_999999,
textColor: MyColours.color_ffffff,
isBold: false,
fontSize: 30,
onPressed: () async {
_clearResultData();
setState(() {
isListening = true;
});
await SpeechToTextAlibaba.startDialog();
// _getDate('5点');
}),
RoundButton(
text: '结束识别',
width: (ScreenUtil().screenWidth -
ScreenUtil().setWidth(82) * 2) /
2,
height: ScreenUtil().setWidth(112),
// radius: ScreenUtil().setWidth(10),
gradientStart: Colors.transparent,
gradientEnd: Colors.transparent,
textColor: MyColours.color_ffffff,
isBold: true,
fontSize: 34,
onPressed: () async {
await SpeechToTextAlibaba.stopDialog();
// if (Navigator.canPop(context)) {
// Navigator.pop(context);
// }
}),
height: ScreenUtil().setWidth(95),
radius: 30,
gradientStart: isListening
? MyColours.color_438E93
: MyColours.color_999999,
gradientEnd: isListening
? MyColours.color_438E93
: MyColours.color_999999,
textColor: MyColours.color_ffffff,
isBold: false,
fontSize: 30,
onPressed: () async {
setState(() {
isListening = false; //主动结束识别
});
await SpeechToTextAlibaba.stopDialog();
// _getDate('5点');
}),
],
)
// Align(
// alignment: Alignment.center,
// child: RoundButton(
// text: '结束识别',
// width:
// (ScreenUtil().screenWidth - ScreenUtil().setWidth(62) * 2) /
// 2,
// height: ScreenUtil().setWidth(95),
// radius: 30,
// gradientStart: MyColours.color_438E93,
// gradientEnd: MyColours.color_438E93,
// textColor: MyColours.color_ffffff,
// isBold: false,
// fontSize: 30,
// onPressed: () async {
// await SpeechToTextAlibaba.stopDialog();
// // _getDate('5点');
// }),
// ),
// VEmptyView(300),
],
),
));
... ...
import 'package:flutter/material.dart';
import 'package:one_key/res/colors.dart';
/**
*
*
* WaveformWidget(volume: _volume) // 在录音时传入音量值
*
*
*/
class WaveformWidget extends StatefulWidget {
final double volume;
const WaveformWidget({Key? key, required this.volume}) : super(key: key);
@override
_WaveformWidgetState createState() => _WaveformWidgetState();
}
class _WaveformWidgetState extends State<WaveformWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
List<double> _waveData = List.generate(20, (index) => 0.0); // 存储波形数据
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: Duration(milliseconds: 100),
)..repeat();
}
@override
void didUpdateWidget(WaveformWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// 更新波形数据
_waveData.removeAt(0);
_waveData.add(widget.volume);
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return CustomPaint(
painter: WaveformPainter(_waveData),
size: Size(300, 100),
);
},
);
}
}
class WaveformPainter extends CustomPainter {
final List<double> waveData;
WaveformPainter(this.waveData);
@override
void paint(Canvas canvas, Size size) {
final paint = Paint()
..color = MyColours.color_438E93
..strokeWidth = 2
..style = PaintingStyle.stroke;
final path = Path();
final xStep = size.width / (waveData.length - 1);
for (int i = 0; i < waveData.length; i++) {
final x = i * xStep;
final y = size.height / 2 + waveData[i] * 10; // 调整振幅
if (i == 0) {
path.moveTo(x, y);
} else {
path.lineTo(x, y);
}
}
canvas.drawPath(path, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}
... ...
... ... @@ -173,7 +173,7 @@ public class Auth {
String STS_AK_ID = "STS.<服务器生成的具有时效性的临时凭证>";
String STS_AK_SECRET = "<服务器生成的具有时效性的临时凭证>";
String STS_TOKEN = "<服务器生成的具有时效性的临时凭证>";
String TOKEN ="0bbc0ead105041239e2b118b47f30cde"; //"<由STS生成的临时访问令牌>";
String TOKEN ="928034cf23c8486eaef7138e7d2cdc84"; //"<由STS生成的临时访问令牌>";
final AccessToken token = new AccessToken(STS_AK_ID, STS_AK_SECRET, STS_TOKEN);
// Thread th = new Thread() {
// @Override
... ...
... ... @@ -357,7 +357,8 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware ,
// EVENT_MIC_ERROR表示2s未传入音频数据,请检查录音相关代码、权限或录音模块是否被其他应用占用。
final String msg_text = Utils.getMsgWithErrorCode(resultCode, "start");
mStopping = false;
new Handler(Looper.getMainLooper()).post(() -> { channel.invokeMethod("onError", msg_text);
new Handler(Looper.getMainLooper()).post(() -> {
channel.invokeMethod("onError", msg_text);
channel.invokeMethod("onStopping", false);});
... ... @@ -500,7 +501,14 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware ,
@Override
public void onNuiAudioRMSChanged(float val) {
// Log.i(TAG, "onNuiAudioRMSChanged vol " + val);
Log.i(TAG, "onNuiAudioRMSChanged vol " + val);
new Handler(Looper.getMainLooper()).post(() -> {
channel.invokeMethod("onNuiAudioRMSChanged", val);});
}
@Override
... ... @@ -654,7 +662,7 @@ public class SpeechToTextAlibabaPlugin implements FlutterPlugin,ActivityAware ,
// 注意: 若需要更换appkey和token,可以直接传入参数
dialog_param.put("app_key", "88v9LR9mYYyvmSKw");
dialog_param.put("token", "0bbc0ead105041239e2b118b47f30cde");
dialog_param.put("token", "928034cf23c8486eaef7138e7d2cdc84");
params = dialog_param.toString();
} catch (JSONException e) {
e.printStackTrace();
... ...
import 'speech_to_text_alibaba_method_channel.dart';
import 'speech_to_text_alibaba_platform_interface.dart';
class SpeechToTextAlibaba {
/// 获取平台实现实例
static SpeechToTextAlibabaPlatform get _platform => SpeechToTextAlibabaPlatform.instance;
/// 获取平台实现实例
static SpeechToTextAlibabaPlatform get _platform =>
SpeechToTextAlibabaPlatform.instance;
/// 设置引擎回调函数
///
... ... @@ -28,9 +26,7 @@ class SpeechToTextAlibaba {
void Function(String)? onError,
void Function(bool)? onPermission,
void Function(bool)? onStopping,
void Function(Map<String, int>)? onVideoSizeChanged,
void Function(String)? onVoiceIdChanged,
void Function(String)? onRoleChanged,
void Function(double)? onNuiAudioRMSChanged,
void Function(String)? onEventAsrStarted,
void Function(String)? onEventAsrResult,
void Function(String)? onEventAsrPartitalResult,
... ... @@ -43,9 +39,7 @@ class SpeechToTextAlibaba {
onError: onError,
onPermission: onPermission,
onStopping: onStopping,
onVideoSizeChanged: onVideoSizeChanged,
onVoiceIdChanged: onVoiceIdChanged,
onRoleChanged: onRoleChanged,
onNuiAudioRMSChanged: onNuiAudioRMSChanged,
onEventAsrStarted: onEventAsrStarted,
onEventAsrResult: onEventAsrResult,
onEventAsrPartitalResult: onEventAsrPartitalResult,
... ... @@ -54,12 +48,14 @@ class SpeechToTextAlibaba {
}
}
/// 初始化引擎
/// 初始化引擎
/// [userId] 用户ID
static Future<void> initialize({required String appKey,required String token}) {
return _platform.initialize(appKey: appKey,token:token);
static Future<void> initialize(
{required String appKey, required String token}) {
return _platform.initialize(appKey: appKey, token: token);
}
/// 发起通话
/// 发起通话
/// [rtcToken] RTC通话令牌
/// [aiAgentInstanceId] AI代理实例ID
/// [aiAgentUserId] AI代理用户ID
... ... @@ -81,14 +77,12 @@ class SpeechToTextAlibaba {
/// 开始一句话识别
static Future<void> startDialog() => _platform.startDialog();
/// 结束识别
/// 结束识别
static Future<void> stopDialog() => _platform.stopDialog();
/// 页面dispose的时候释放
static Future<void> release() => _platform.release();
/// 切换麦克风状态
/// [on] true开启,false关闭
static Future<void> switchMicrophone(bool on) =>
... ... @@ -117,6 +111,4 @@ class SpeechToTextAlibaba {
/// [roleName] 角色名称
static Future<void> setAIRole(String roleId, String roleName) =>
_platform.setAIRole(roleId, roleName);
}
... ...
... ... @@ -30,13 +30,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
void Function(bool)? _onStopping;
/// 视频尺寸变化回调,包含宽高信息
void Function(Map<String, int>)? _onVideoSizeChanged;
/// 声音ID变化回调
void Function(String)? _onVoiceIdChanged;
/// ���色变化回调
void Function(String)? _onRoleChanged;
void Function(double)? _onNuiAudioRMSChanged;
/// AI代理状态变化回调
void Function(String)? _onAIAgentStateChanged;
... ... @@ -131,9 +125,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
void Function(String)? onEventAsrStarted,
void Function(bool)? onPermission,
void Function(bool)? onStopping,
void Function(Map<String, int>)? onVideoSizeChanged,
void Function(String)? onVoiceIdChanged,
void Function(String)? onRoleChanged,
void Function(double)? onNuiAudioRMSChanged,
void Function(String)? onAIAgentStateChanged,
void Function(String)? onEventAsrResult,
void Function(String)? onEventAsrPartitalResult,
... ... @@ -145,9 +137,7 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
_onEventAsrStarted = onEventAsrStarted;
_onPermission = onPermission;
_onStopping = onStopping;
_onVideoSizeChanged = onVideoSizeChanged;
_onVoiceIdChanged = onVoiceIdChanged;
_onRoleChanged = onRoleChanged;
_onNuiAudioRMSChanged = onNuiAudioRMSChanged;
_onAIAgentStateChanged = onAIAgentStateChanged;
_onEventAsrResult = onEventAsrResult;
_onEventAsrPartitalResult = onEventAsrPartitalResult;
... ... @@ -177,15 +167,8 @@ class MethodChannelSpeechToTextAlibaba extends SpeechToTextAlibabaPlatform {
case 'onStopping':
_onStopping?.call(call.arguments as bool);
break;
case 'onVideoSizeChanged':
_onVideoSizeChanged
?.call(Map<String, int>.from(call.arguments as Map));
break;
case 'onVoiceIdChanged':
_onVoiceIdChanged?.call(call.arguments as String);
break;
case 'onRoleChanged':
_onRoleChanged?.call(call.arguments as String);
case 'onNuiAudioRMSChanged':
_onNuiAudioRMSChanged?.call(call.arguments as double);
break;
case 'onAIAgentStateChanged':
_onAIAgentStateChanged?.call(call.arguments as String);
... ...