feat:使用本地tts

This commit is contained in:
dzc committed 2025-03-12 12:33:46 +08:00
1 parent 2490d4f9a0
commit d656ce14eb
22 files changed
+184 -185

No files matched your search

+1
View File
@@ -61,6 +61,7 @@ flutter {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation files('libs/AIKit.aar')
// rxjava
implementation 'io.reactivex.rxjava2:rxjava:2.2.9'
Binary file not shown.
+14 -11
View File
@@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@@ -7,33 +8,35 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE" />
</intent>
</queries>
<application
android:label="pj_supermarket_cloud"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="pj_supermarket_cloud"
android:requestRawExternalStorageAccess="true">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" />
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
@@ -51,8 +54,8 @@
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
<action android:name="android.intent.action.PROCESS_TEXT" />
<data android:mimeType="text/plain" />
</intent>
</queries>
</manifest>
@@ -10,8 +10,7 @@ class MainActivity : FlutterActivity() {
// 人脸识别插件
val flutterFaceDiscernPlugin = FlutterFaceDiscernPlugin()
flutterEngine.plugins.add(flutterFaceDiscernPlugin)
// tts插件
// 箭头全局键盘插件
}
}
+2
View File
@@ -31,10 +31,12 @@ class MainApp extends StatelessWidget {
iconStyle: fTheme.style.iconStyle.copyWith(
size: fTheme.typography.xl2.fontSize,
),
pagePadding: EdgeInsets.symmetric(vertical: 5.h, horizontal: 1.w),
),
);
return GetMaterialApp(
title: '收银机',
defaultTransition: Transition.noTransition,
initialRoute: RouteNames.load,
getPages: RoutePages.list,
enableLog: true,
@@ -20,6 +20,7 @@ enum PanelType implements BaseEnum {
cardActive("60", "开卡管理"),
printer("70", "打印测试"),
logout("80", "退出登录"),
test("90", "测试"),
;
@override
@@ -39,6 +39,7 @@ class AppControl extends GetxController {
serialCardManage.close();
srialSteelyardManage.close();
AppNetworkTool.closeListenNetwork();
TtsManage.dispose();
}
Future<void> initAsync() async {
@@ -52,6 +53,7 @@ class AppControl extends GetxController {
await getDatabasesPath(),
);
await AppFileTool.backupSqlFile();
await TtsManage.initialize();
Get.offAllNamed(RouteNames.login);
}
}
+1 -1
View File
@@ -27,9 +27,9 @@ export './manage/print_manage.dart';
export './manage/serial_card_manage.dart';
export './manage/serial_steelyard_manage.dart';
export './manage/scan_code_manage.dart';
export './manage/tts_manage.dart';
export './plugs/face_discern_plugs.dart';
export './plugs/local_tts_plugs.dart';
export './router/router_observer_tool.dart';
+48
View File
@@ -0,0 +1,48 @@
import 'package:flutter_tts/flutter_tts.dart';
// 语音转文字
class TtsManage {
static final FlutterTts _flutterTts = FlutterTts();
static bool _isInitialized = false;
// 初始化TTS
static Future<void> initialize() async {
if (!_isInitialized) {
// 设置语言为中文
await _flutterTts.setLanguage("zh-CN");
// 设置音量
await _flutterTts.setVolume(1.0);
// 设置语速
await _flutterTts.setSpeechRate(0.7);
// 设置音调
await _flutterTts.setPitch(1.0);
// 设置使用本地引擎
await _flutterTts.setIosAudioCategory(
IosTextToSpeechAudioCategory.ambient,
[
IosTextToSpeechAudioCategoryOptions.allowBluetooth,
IosTextToSpeechAudioCategoryOptions.allowBluetoothA2DP
],
);
_isInitialized = true;
}
}
// 播放文本
static Future<void> speak(String text) async {
await initialize();
await _flutterTts.awaitSpeakCompletion(true);
await _flutterTts.speak(text, focus: true);
}
// 停止播放
static Future<void> stop() async {
await _flutterTts.stop();
}
// 释放资源
static Future<void> dispose() async {
await _flutterTts.stop();
}
}
@@ -1,30 +0,0 @@
import 'package:flutter/services.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
abstract class LocalTtsPlugs {
static const String _tag = "本地tts插件";
static const MethodChannel _channel = MethodChannel(
Constants.localTTSPlugsMethodChannel,
);
static Future<bool> speeching({
required String appId,
required String str,
String speed = "80",
}) async {
try {
final pa = {
"appId": appId,
"str": str,
"speed": speed,
};
await _channel.invokeMethod('speeching', pa);
return true;
} catch (e) {
logger.w("|$_tag| jumpFaceDiscernPage 错误: $e");
}
return false;
}
}
-3
View File
@@ -17,9 +17,6 @@ class Constants {
/// 静态资源 url
static const ossUrl = apiUrl;
/// ttl android 插件方法管道
static const localTTSPlugsMethodChannel = 'local/tts';
/// 人脸识别 插件方法管道
static const facePlugsMethodChannel = 'local/face_discern';
+7 -28
View File
@@ -1,10 +1,12 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:common_utils/common_utils.dart';
import 'package:ducafe_ui_core/ducafe_ui_core.dart';
import 'package:flutter/material.dart';
import 'package:forui/forui.dart';
import 'package:get/get.dart';
import 'package:pj_supermarket_cloud/common/index.dart';
Widget appNetworkImg(
@@ -14,34 +16,11 @@ Widget appNetworkImg(
double imgHeight = 60,
BoxFit fit = BoxFit.cover,
}) {
return Image.network(
AppNetworkTool.ossImage(src),
width: imgWidth,
height: imgHeight,
fit: fit,
loadingBuilder: (
BuildContext context,
Widget child,
ImageChunkEvent? loadingProgress,
) {
if (loadingProgress == null) {
return child;
} else {
return Center(
child: SizedBox(
width: imgHeight,
height: imgHeight,
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
(loadingProgress.expectedTotalBytes ?? 1)
: null,
),
),
);
}
},
errorBuilder: (BuildContext context, Object error, StackTrace? stackTrace) {
return CachedNetworkImage(
imageUrl: AppNetworkTool.ossImage(src),
placeholder: (context, url) =>
CircularProgressIndicator().width(50.w).height(50.w),
errorWidget: (context, url, error) {
final theme = FTheme.of(context);
return FIcon(
FAssets.icons.globeLock,
@@ -25,9 +25,6 @@ mixin OrderMixin {
// 设备类型
SysType _sysType = SysType.canteen;
// 语音锁
bool _speechingLock = false;
/// **************** end 需要重制 end ****************
// 分类
@@ -316,18 +313,6 @@ mixin OrderMixin {
}
}
Future<void> speeching(String str) async {
if (_speechingLock) return;
try {
_speechingLock = true;
await Future.delayed(const Duration(seconds: 1));
await LocalTtsPlugs.speeching(appId: Constants.ttsId, str: str);
await Future.delayed(const Duration(seconds: 2));
} finally {
_speechingLock = false;
}
}
// 获取商品渠道id
String getOrderSourceTypeId() {
String orderSourceTypeId = "";
@@ -408,11 +393,6 @@ mixin OrderMixin {
/// **************** start 重制相关 start ****************
// 重置状态
void closeStatus() {
_speechingLock = false;
}
/// **************** end 重制相关 end ****************
/// **************** start 副屏幕相关 start ****************
+15 -14
View File
@@ -12,20 +12,21 @@ Widget orderProductCard(
num price = 0.0,
}) {
final theme = FTheme.of(context);
return <Widget>[
appImg(image, width: double.infinity, height: 140.h),
Text(name, style: theme.typography.lg),
appPrice(context, price)
]
.toColumn(crossAxisAlignment: CrossAxisAlignment.center)
.width(double.infinity)
.decorated(
border: Border.all(
color: theme.colorScheme.mutedForeground,
width: 1,
),
borderRadius: theme.style.borderRadius / 3,
);
return FCard(
style: theme.cardStyle.copyWith(
contentStyle: theme.cardStyle.contentStyle.copyWith(
padding: EdgeInsets.all(0),
),
),
image: appImg(image, width: double.infinity, height: 150.h),
subtitle: <Widget>[
Text(name, style: theme.typography.lg),
SizedBox(height: 5.h),
appPrice(context, price),
]
.toColumn(crossAxisAlignment: CrossAxisAlignment.center)
.width(double.infinity),
).width(double.infinity);
}
/// 菜品总价
@@ -58,9 +58,6 @@ class SelectPayTypeDialogController extends GetxController {
// 二维码识别结果
ServiceQRCode? globalQrCode;
// 语音锁
bool _speechingLock = false;
// 人脸识别回调锁
bool _faceCallbackLock = false;
@@ -96,7 +93,6 @@ class SelectPayTypeDialogController extends GetxController {
payUser.value = null;
isRecognition.value = false;
globalQrCode = null;
_speechingLock = false;
_faceCallbackLock = false;
}
@@ -249,7 +245,7 @@ class SelectPayTypeDialogController extends GetxController {
isRecognition.value = false;
await app.scanCodeManage.close();
Toast.success(msg: "识别二维码成功");
speeching("识别二维码成功");
TtsManage.speak("识别二维码成功");
});
isRecognition.value = true;
onDiscernUser?.call(payUser.value);
@@ -275,7 +271,7 @@ class SelectPayTypeDialogController extends GetxController {
switch (status) {
case SerialCardResStatus.success:
if (ObjectUtil.isEmpty(card)) return;
speeching("识别饭卡成功");
TtsManage.speak("识别饭卡成功");
final isNetwork = AppNetworkTool.isAutoNetwork();
if (isNetwork) {
final pa = {"card": card};
@@ -442,19 +438,6 @@ class SelectPayTypeDialogController extends GetxController {
];
return success.contains(type);
}
/// 语音
Future<void> speeching(String str) async {
if (_speechingLock) return;
try {
_speechingLock = true;
await Future.delayed(const Duration(seconds: 1));
await LocalTtsPlugs.speeching(appId: Constants.ttsId, str: str);
await Future.delayed(const Duration(seconds: 2));
} finally {
_speechingLock = false;
}
}
}
Future<void> selectPayTypeDialog(BuildContext context) async {
+2 -6
View File
@@ -48,8 +48,6 @@ class CanteenProductController extends GetxController with OrderMixin {
hourType.value = appSetting.hourType ?? ProductHourType.morning;
hourTypeC.value = {hourType.value.code};
sysType = SysType.canteen;
// 重置状态
closeStatus();
_listenPayTypeSelectCallback();
/// **************** end 组件初始化 end ****************
@@ -103,8 +101,6 @@ class CanteenProductController extends GetxController with OrderMixin {
app.scanCodeManage.close();
// 路由监听
routerBus?.cancel();
// 重置状态
closeStatus();
mqUpdateListen?.cancel();
hourTypeC.dispose();
}
@@ -233,12 +229,12 @@ class CanteenProductController extends GetxController with OrderMixin {
bool _validatePayUser() {
bool flag = true;
if (ObjectUtil.isEmpty(payUser.value?.walletBalance)) {
speeching("用户钱包不存在");
TtsManage.speak("用户钱包不存在");
Toast.error(msg: "校验购物车失败,用户钱包不存在");
return false;
}
if (payUser.value!.walletBalance! < totalPrice.value) {
speeching("用户余额不足");
TtsManage.speak("用户余额不足");
Toast.error(msg: "校验购物车失败,用户余额不足");
return false;
}
+1 -1
View File
@@ -81,7 +81,7 @@ class CanteenProductPage extends GetView<CanteenProductController> {
double maxHeight = constraints.maxHeight;
double maxWidth = constraints.maxWidth;
double itemWidth = (maxWidth - spacing * 5) / 6;
double itemHeight = (maxHeight - (runSpacing + 40) * 3) / 3;
double itemHeight = (maxHeight - (runSpacing + 30) * 3) / 3;
return Obx(
() => ListView.builder(
padding: EdgeInsets.zero,
+5 -27
View File
@@ -10,9 +10,6 @@ class CardActiveController extends GetxController {
SerialCardManage get serialCardManage => appC.serialCardManage;
// 语音锁
bool _speechingLock = false;
// table
RxInt currentPage = 1.obs;
@@ -37,7 +34,6 @@ class CardActiveController extends GetxController {
@override
void onInit() {
super.onInit();
_closeStatus();
getTableList();
}
@@ -50,7 +46,6 @@ class CardActiveController extends GetxController {
@override
void onClose() {
super.onClose();
_closeStatus();
}
Future<void> doPage({int? page}) async {
@@ -119,12 +114,12 @@ class CardActiveController extends GetxController {
case SerialCardResStatus.warning:
break;
case SerialCardResStatus.error:
_speeching("请在合适区域放置饭卡");
TtsManage.speak("请在合适区域放置饭卡");
Toast.error(msg: "请在合适区域放置饭卡");
break;
}
};
_speeching("请在合适区域放置饭卡");
TtsManage.speak("请在合适区域放置饭卡");
serialCardManage.sendWrite(v.cardNumber!);
} catch (e, stack) {
logger.d("写卡失败error:|$e|stack:|$stack|");
@@ -163,11 +158,11 @@ class CardActiveController extends GetxController {
break;
case SerialCardResStatus.error:
Toast.error(msg: "请在合适区域放置饭卡");
_speeching("请在合适区域放置饭卡");
TtsManage.speak("请在合适区域放置饭卡");
break;
}
};
_speeching("请在合适区域放置饭卡");
TtsManage.speak("请在合适区域放置饭卡");
serialCardManage.sendRead();
} catch (e, stack) {
_closeReadStatus();
@@ -183,7 +178,7 @@ class CardActiveController extends GetxController {
final data = await WalletApi.openCardSuccess(pa);
if (data == false) throw Error();
EasyLoading.dismiss();
_speeching("激活成功");
TtsManage.speak("激活成功");
Toast.success(msg: "激活成功");
} catch (e, stack) {
logger.e("激活饭卡请求失败error:|$e|stack:|$stack|");
@@ -210,25 +205,8 @@ class CardActiveController extends GetxController {
}
}
Future<void> _speeching(String str) async {
if (_speechingLock) return;
try {
_speechingLock = true;
await Future.delayed(const Duration(seconds: 1));
await LocalTtsPlugs.speeching(appId: Constants.ttsId, str: str);
await Future.delayed(const Duration(seconds: 2));
} finally {
_speechingLock = false;
}
}
void _closeReadStatus() {
readLoading.value = false;
readCardNumber.value = "未读取";
}
// 重置状态
void _closeStatus() {
_speechingLock = false;
}
}
+5
View File
@@ -16,6 +16,7 @@ class PanelController extends GetxController {
PanelItem(title: "开卡管理", visible: true, type: PanelType.cardActive),
PanelItem(title: "设置", visible: true, type: PanelType.setting),
PanelItem(title: "退出登录", visible: true, type: PanelType.logout),
PanelItem(title: "测试", visible: true, type: PanelType.test),
].obs;
final canteenMenus = [
@@ -73,6 +74,10 @@ class PanelController extends GetxController {
}
Get.offAndToNamed(RouteNames.login);
break;
case PanelType.test:
await TtsManage.initialize();
await TtsManage.speak("购买成功1");
break;
default:
break;
}
+1 -23
View File
@@ -23,9 +23,6 @@ class TakeFoodController extends GetxController {
ServiceQRCode? globalQrCode;
// 语音锁
bool _speechingLock = false;
_initData() {
update(["take_food"]);
}
@@ -42,7 +39,6 @@ class TakeFoodController extends GetxController {
@override
void onReady() {
super.onReady();
_closeStatus();
_initData();
}
@@ -51,12 +47,6 @@ class TakeFoodController extends GetxController {
super.onClose();
// 二维码读头
appC.serialCardManage.close();
_closeStatus();
}
// 重置状态
void _closeStatus() {
_speechingLock = false;
}
doCancelPay() {
@@ -170,7 +160,7 @@ class TakeFoodController extends GetxController {
if (flag == false) throw Error();
EasyLoading.dismiss();
Toast.error(msg: "核销成功");
_speeching("核销成功");
TtsManage.speak("核销成功");
doCancelPay();
} catch (e, stack) {
logger.e("获取订单详情错误error:|$e|stack:|$stack|");
@@ -182,16 +172,4 @@ class TakeFoodController extends GetxController {
void senSubScreen() {
if (ObjectUtil.isEmpty(carList)) return;
}
Future<void> _speeching(String str) async {
if (_speechingLock) return;
try {
_speechingLock = true;
await Future.delayed(const Duration(seconds: 1));
await LocalTtsPlugs.speeching(appId: Constants.ttsId, str: str);
await Future.delayed(const Duration(seconds: 2));
} finally {
_speechingLock = false;
}
}
}
+72
View File
@@ -142,6 +142,30 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.9.4"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.3.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: ff0c949e323d2a1b52be73acce5b4a7b04063e61414c8ca542dbba47281630a7
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.1.0"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "6322dde7a5ad92202e64df659241104a43db20ed594c41ca18de1014598d7996"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.3.0"
characters:
dependency: transitive
description:
@@ -411,6 +435,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: a77f77806a790eb9ba0118a5a3a936e81c4fea2b61533033b2b0c3d50bbde5ea
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.4.0"
flutter_easyloading:
dependency: "direct main"
description:
@@ -469,6 +501,14 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_tts:
dependency: "direct main"
description:
name: flutter_tts
sha256: baa3cb6b4990318460fe28bfa8c7869399e97223971532c02bd97c5e876aa3c5
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.2.2"
flutter_web_plugins:
dependency: transitive
description: flutter
@@ -794,6 +834,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.6"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
package_config:
dependency: transitive
description:
@@ -1010,6 +1058,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.3"
rxdart:
dependency: transitive
description:
name: rxdart
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.28.0"
shelf:
dependency: transitive
description:
@@ -1063,6 +1119,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.10.0"
sprintf:
dependency: transitive
description:
name: sprintf
sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23"
url: "https://pub.flutter-io.cn"
source: hosted
version: "7.0.0"
sqflite:
dependency: "direct main"
description:
@@ -1231,6 +1295,14 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.2"
uuid:
dependency: transitive
description:
name: uuid
sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.5.1"
vector_graphics:
dependency: transitive
description:
+4
View File
@@ -26,12 +26,16 @@ dependencies:
flutter_easyloading: ^3.0.5
# 图片处理
image: ^4.3.0
# 图片缓存
cached_network_image: 3.3.1
# 表格
data_table_2: ^2.5.18
# 分页
number_pagination: ^1.1.6
# 日期选择
syncfusion_flutter_datepicker: ^28.2.11
# tts转语音
flutter_tts: ^4.2.2
# 权限判断
permission_handler: ^11.4.0