Browse Source

Merge branch 'new_dev' of https://github.com/deepcloud2048/deep_voice into new_dev

weicu
lxm 1 year ago
parent
commit
7e522234a5
  1. 8
      ios/Runner/GoogleService-Info.plist
  2. 37
      lib/core/utils/synchrodata.dart
  3. 1
      lib/modules/meeting/controllers/meeting_controller.dart
  4. 30
      lib/modules/meeting/controllers/meeting_details_controller.dart
  5. 3
      lib/modules/meeting/controllers/meeting_record_controller.dart
  6. 2
      lib/modules/meeting/controllers/mind_map_controller.dart
  7. 102
      lib/modules/meeting/views/meeting_details_view.dart
  8. 96
      lib/modules/translation/controllers/translation_controller.dart
  9. 8
      lib/modules/translation/views/translation_list_view.dart
  10. 26
      lib/modules/translation/views/translation_view.dart
  11. 2
      pubspec.yaml

8
ios/Runner/GoogleService-Info.plist

@ -3,9 +3,9 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>CLIENT_ID</key> <key>CLIENT_ID</key>
<string>907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278.apps.googleusercontent.com</string> <string>907754492821-tl2blr39g82re40sttkve36ovt6qcu6c.apps.googleusercontent.com</string>
<key>REVERSED_CLIENT_ID</key> <key>REVERSED_CLIENT_ID</key>
<string>com.googleusercontent.apps.907754492821-oc9e77svgmmlgouo0op7vv00e1hb8278</string> <string>com.googleusercontent.apps.907754492821-tl2blr39g82re40sttkve36ovt6qcu6c</string>
<key>ANDROID_CLIENT_ID</key> <key>ANDROID_CLIENT_ID</key>
<string>907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com</string> <string>907754492821-9jakqvathu61e6b8g95i41ssi7o4dnka.apps.googleusercontent.com</string>
<key>API_KEY</key> <key>API_KEY</key>
@ -15,7 +15,7 @@
<key>PLIST_VERSION</key> <key>PLIST_VERSION</key>
<string>1</string> <string>1</string>
<key>BUNDLE_ID</key> <key>BUNDLE_ID</key>
<string>com.yunqiinnovation.deepsound</string> <string>com.chuangxiangyunqi.deapsound</string>
<key>PROJECT_ID</key> <key>PROJECT_ID</key>
<string>deepsound-e0e3d</string> <string>deepsound-e0e3d</string>
<key>STORAGE_BUCKET</key> <key>STORAGE_BUCKET</key>
@ -31,6 +31,6 @@
<key>IS_SIGNIN_ENABLED</key> <key>IS_SIGNIN_ENABLED</key>
<true></true> <true></true>
<key>GOOGLE_APP_ID</key> <key>GOOGLE_APP_ID</key>
<string>1:907754492821:ios:246794fc44b23174145c9c</string> <string>1:907754492821:ios:e4b4f3b37e58a7d1145c9c</string>
</dict> </dict>
</plist> </plist>

37
lib/core/utils/synchrodata.dart

@ -128,7 +128,6 @@ class Synchrodata {
'title': element['title'] ?? '', 'title': element['title'] ?? '',
'type': element['rtype'] ?? '', 'type': element['rtype'] ?? '',
'seconds': element['seconds'] ?? 0, 'seconds': element['seconds'] ?? 0,
'filepath': '',
'audiourl': element['audiourl'] ?? '', 'audiourl': element['audiourl'] ?? '',
'tasktype': element['tasktype'] ?? 0, 'tasktype': element['tasktype'] ?? 0,
'creationtime': element['creationtime'] ?? 0, 'creationtime': element['creationtime'] ?? 0,
@ -157,7 +156,10 @@ class Synchrodata {
whereArgs: [element['number']], whereArgs: [element['number']],
); );
} else { } else {
await txn.insert('meeting', meetingData); await txn.insert('meeting', {
...meetingData,
'filepath': '',
});
await txn.insert('meetingdetails', meetingDetailsData); await txn.insert('meetingdetails', meetingDetailsData);
} }
if (element['original'].isNotEmpty) { if (element['original'].isNotEmpty) {
@ -169,19 +171,34 @@ class Synchrodata {
List speakerList = jsonDecode(element['original']); List speakerList = jsonDecode(element['original']);
if (existSpeakerList.isNotEmpty) { if (existSpeakerList.isNotEmpty) {
final batch = txn.batch(); final batch = txn.batch();
for (var item in speakerList) { for (var i = 0; i < existSpeakerList.length; i++) {
batch.update( Map item = speakerList[i] ?? {};
'meetingspeaker', if (item.isNotEmpty) {
item, batch.update(
where: 'id = ?', 'meetingspeaker',
whereArgs: [item['id']], {
); 'meetingid': item['meetingid'],
'content': item['content'],
'starttime': item['starttime'],
'endtime': item['endtime'],
'speaker': item['speaker'],
},
where: 'id = ?',
whereArgs: [existSpeakerList[i]['id']],
);
}
} }
await batch.commit(); await batch.commit();
} else { } else {
final batch = txn.batch(); final batch = txn.batch();
for (var item in speakerList) { for (var item in speakerList) {
batch.insert('meetingspeaker', item); batch.insert('meetingspeaker', {
'meetingid': item['meetingid'],
'content': item['content'],
'starttime': item['starttime'],
'endtime': item['endtime'],
'speaker': item['speaker'],
});
} }
await batch.commit(); await batch.commit();
} }

1
lib/modules/meeting/controllers/meeting_controller.dart

@ -322,6 +322,7 @@ class MeetingController extends GetxController {
} else { } else {
FilePickerResult? result = await FilePicker.platform.pickFiles( FilePickerResult? result = await FilePicker.platform.pickFiles(
withData: true, withData: true,
type: FileType.audio,
); );
if (result != null) { if (result != null) {
PlatformFile file = result.files.first; PlatformFile file = result.files.first;

30
lib/modules/meeting/controllers/meeting_details_controller.dart

@ -1,7 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:audio_waveforms/audio_waveforms.dart'; import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle; import 'package:flutter/services.dart' show rootBundle;
import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart';
@ -9,6 +11,7 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart'; import 'package:get_storage/get_storage.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import '../../../core/utils/logger.dart'; import '../../../core/utils/logger.dart';
import '../../../core/utils/synchrodata.dart'; import '../../../core/utils/synchrodata.dart';
@ -58,6 +61,9 @@ class MeetingDetailsController extends GetxController
String markmapToolbarJs = ''; String markmapToolbarJs = '';
String saveSvgAsPngJs = ''; String saveSvgAsPngJs = '';
RxBool isDownload = false.obs;
RxDouble downloadProgress = 0.0.obs;
String _systemPrompt = ''; String _systemPrompt = '';
String _userId = ''; String _userId = '';
@ -205,6 +211,30 @@ class MeetingDetailsController extends GetxController
await rootBundle.loadString('assets/js/saveSvgAsPng.min.js'); await rootBundle.loadString('assets/js/saveSvgAsPng.min.js');
} }
void downloadAudio() async {
if (isDownload.value) return;
isDownload.value = true;
final directory = await getApplicationDocumentsDirectory();
final directoryDir = Directory('${directory.path}/DownloadAudio');
if (!await directoryDir.exists()) {
await directoryDir.create(recursive: true);
}
final fileName = meetingData.value.audiourl.split('/').last;
final String filePath = '${directory.path}/DownloadAudio/$fileName';
await Dio().download(
meetingData.value.audiourl,
filePath,
onReceiveProgress: (int received, int total) {
downloadProgress.value = total > 0 ? received / total : 0.0;
},
);
meetingData.value.filepath = filePath;
meetingData.refresh();
_loadPlayerController();
SqfliteApi.editMeetingTitle(id, {'filePath': filePath});
isDownload.value = false;
}
void editSpeakerText() async { void editSpeakerText() async {
if (editSpeakerTextId.value != 0 && editSpeakerTextChange.isNotEmpty) { if (editSpeakerTextId.value != 0 && editSpeakerTextChange.isNotEmpty) {
Map? foundElement = editSpeakerTextList.firstWhere( Map? foundElement = editSpeakerTextList.firstWhere(

3
lib/modules/meeting/controllers/meeting_record_controller.dart

@ -138,6 +138,7 @@ class MeetingRecordController extends GetxController
_bleManager.closeCodec(); _bleManager.closeCodec();
} }
_asrService.stopRecord(true); _asrService.stopRecord(true);
_asrService.setPause();
_asrService.stopContinuousRecognition(); _asrService.stopContinuousRecognition();
isRecording.value = false; isRecording.value = false;
} }
@ -214,7 +215,7 @@ class MeetingRecordController extends GetxController
_recognitionSubscription?.cancel(); _recognitionSubscription?.cancel();
_recognitionSubscription = _recognitionSubscription =
recognitionStream.listen(_handleRecognitionEvent); recognitionStream.listen(_handleRecognitionEvent);
_asrService.setContinue();
// Prepare storage directory // Prepare storage directory
final appDir = await appDocDir; final appDir = await appDocDir;
final dir = Directory("${appDir.path}/MeetingAudio/"); final dir = Directory("${appDir.path}/MeetingAudio/");

2
lib/modules/meeting/controllers/mind_map_controller.dart

@ -27,7 +27,7 @@ class MindMapController extends GetxController {
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>MindMap</title> <title>MindMap</title>
<script>${_meetingDetailsController.markmapLibJs}</script> <script>${_meetingDetailsController.markmapLibJs}</script>
<script>${_meetingDetailsController.d3Js}</script> <script>${_meetingDetailsController.d3Js}</script>

102
lib/modules/meeting/views/meeting_details_view.dart

@ -81,20 +81,50 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
children: [ children: [
GestureDetector( GestureDetector(
onTap: () { onTap: () {
if (controller.isPlay.value) { if (controller.meetingData.value.filepath.isEmpty) {
controller.playerController.pausePlayer(); controller.downloadAudio();
} else { } else {
controller.playerController.startPlayer(); if (controller.isPlay.value) {
controller.playerController.pausePlayer();
} else {
controller.playerController.startPlayer();
}
} }
}, },
child: Obx( child: Obx(
() => Icon( () => !controller.isLoading.value &&
controller.isPlay.value controller.meetingData.value.filepath.isEmpty
? Icons.pause_circle ? Obx(
: Icons.play_circle, () => Stack(
color: Colors.black, alignment: Alignment.center,
size: 30.w, children: [
), if (controller.isDownload.value)
SizedBox(
width: 26.w,
height: 26.w,
child: CircularProgressIndicator(
value: controller.downloadProgress.value,
valueColor:
const AlwaysStoppedAnimation<Color>(
Colors.red),
strokeWidth: 2.w,
),
),
Icon(
Icons.download_for_offline,
color: Colors.black,
size: 30.w,
),
],
),
)
: Icon(
controller.isPlay.value
? Icons.pause_circle
: Icons.play_circle,
color: Colors.black,
size: 30.w,
),
), ),
), ),
4.horizontalSpace, 4.horizontalSpace,
@ -446,20 +476,52 @@ class MeetingDetailsView extends GetView<MeetingDetailsController> {
), ),
GestureDetector( GestureDetector(
onTap: () { onTap: () {
if (controller.isPlay.value) { if (controller.meetingData.value.filepath.isEmpty) {
controller.playerController.pausePlayer(); controller.downloadAudio();
} else { } else {
controller.playerController.startPlayer(); if (controller.isPlay.value) {
controller.playerController.pausePlayer();
} else {
controller.playerController.startPlayer();
}
} }
}, },
child: Obx( child: Obx(
() => Icon( () => !controller.isLoading.value &&
controller.isPlay.value controller
? Icons.pause_circle .meetingData.value.filepath.isEmpty
: Icons.play_circle, ? Obx(
color: Colors.black, () => Stack(
size: 48.w, alignment: Alignment.center,
), children: [
if (controller.isDownload.value)
SizedBox(
width: 42.w,
height: 42.w,
child: CircularProgressIndicator(
value: controller
.downloadProgress.value,
valueColor:
const AlwaysStoppedAnimation<
Color>(Colors.red),
strokeWidth: 3.w,
),
),
Icon(
Icons.download_for_offline,
color: Colors.black,
size: 48.w,
),
],
),
)
: Icon(
controller.isPlay.value
? Icons.pause_circle
: Icons.play_circle,
color: Colors.black,
size: 48.w,
),
), ),
), ),
GestureDetector( GestureDetector(

96
lib/modules/translation/controllers/translation_controller.dart

@ -106,13 +106,16 @@ class TranslationController extends GetxController {
_asrService.setContinue(); _asrService.setContinue();
lastActiveSpeaker.value = id; lastActiveSpeaker.value = id;
} else { } else {
_asrService.setPause();
if (fTFTranslationResult != '' && !isPlayback) { if (fTFTranslationResult != '' && !isPlayback) {
isPlayback = true; isPlayback = true;
await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value); await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
await Future.delayed(const Duration(milliseconds: 1300)); await Future.delayed(const Duration(milliseconds: 1300));
await playTranslatedText(fTFTranslationResult!); await playTranslatedText(fTFTranslationResult!);
fTFTranslationResult = ''; fTFTranslationResult = '';
} else if ((translationHistory.isEmpty ||
!translationHistory.last.isIntermediate) &&
currentMode.value == 'faceToFace') {
_asrService.setPause();
} }
} }
} }
@ -165,7 +168,7 @@ class TranslationController extends GetxController {
} }
// 加载历史记录 // 加载历史记录
//loadTranslationHistory(); loadTranslationHistory();
// 初始化服务 // 初始化服务
_initServices(); _initServices();
@ -462,6 +465,7 @@ class TranslationController extends GetxController {
if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) { if (translationHistory.isEmpty || !translationHistory.last.isIntermediate) {
currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString(); currentSessionId ??= DateTime.now().millisecondsSinceEpoch.toString();
final newItem = TranslationItem( final newItem = TranslationItem(
sourceText: text, sourceText: text,
translatedText: '', translatedText: '',
@ -474,10 +478,15 @@ class TranslationController extends GetxController {
isIntermediate: true, isIntermediate: true,
); );
// 添加新项后滚动到底部 // 添加新项后滚动到底部
translationHistory.add(newItem); if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
_asrService.setPause();
} else {
translationHistory.add(newItem);
}
_scrollToBottom(); _scrollToBottom();
} else { } else {
// 更新当前项 // 更新当前项
translationHistory.last.sourceText = text; translationHistory.last.sourceText = text;
translationHistory.refresh(); translationHistory.refresh();
// 更新后滚动到底部 // 更新后滚动到底部
@ -509,7 +518,9 @@ class TranslationController extends GetxController {
// 更新后滚动到底部 // 更新后滚动到底部
_scrollToBottom(); _scrollToBottom();
saveTranslationHistory(); saveTranslationHistory();
if (activeSpeaker.value == 0 && currentMode.value == 'faceToFace') {
_asrService.setPause();
}
// 使用项目的时间戳进行翻译 // 使用项目的时间戳进行翻译
translateText(text, translationHistory.last.timestamp, isFinal: true); translateText(text, translationHistory.last.timestamp, isFinal: true);
} else { } else {
@ -526,14 +537,16 @@ class TranslationController extends GetxController {
translationHistory.last.sessionId != currentSessionId, translationHistory.last.sessionId != currentSessionId,
isIntermediate: false, isIntermediate: false,
); );
if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) {
_asrService.setPause();
} //else {
translationHistory.add(newItem); translationHistory.add(newItem);
translationHistory.refresh(); translationHistory.refresh();
_scrollToBottom(); _scrollToBottom();
saveTranslationHistory(); saveTranslationHistory();
// 使用新项目的时间戳进行翻译 // 使用新项目的时间戳进行翻译
translateText(text, newItem.timestamp, isFinal: true); translateText(text, newItem.timestamp, isFinal: true);
// }
} }
} }
@ -546,13 +559,33 @@ class TranslationController extends GetxController {
String? translationResult; String? translationResult;
try { try {
// 根据检测到的语言确定源语言和目标语言 var detectedSourceLanguageCode;
final bool shouldSwap = detectedLanguageCode == sourceLanguageCode.value; var detectedTargetLanguageCode;
final detectedSourceLanguageCode = if ((currentMode.value == 'faceToFace' && activeSpeaker.value == 1) ||
shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value; (currentMode.value == 'faceToFace' &&
final detectedTargetLanguageCode = activeSpeaker.value == 0 &&
shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value; lastActiveSpeaker.value == 1)) {
detectedLanguageCode = sourceLanguageCode.value;
detectedSourceLanguageCode = sourceLanguageCode.value;
detectedTargetLanguageCode = targetLanguageCode.value;
} else if ((currentMode.value == 'faceToFace' &&
activeSpeaker.value == 2) ||
(currentMode.value == 'faceToFace' &&
activeSpeaker.value == 0 &&
lastActiveSpeaker.value == 2)) {
// 根据检测到的语言确定源语言和目标语言
detectedLanguageCode = targetLanguageCode.value;
detectedSourceLanguageCode = targetLanguageCode.value;
detectedTargetLanguageCode = sourceLanguageCode.value;
} else {
// 根据检测到的语言确定源语言和目标语言
final bool shouldSwap =
detectedLanguageCode == sourceLanguageCode.value;
detectedSourceLanguageCode =
shouldSwap ? sourceLanguageCode.value : targetLanguageCode.value;
detectedTargetLanguageCode =
shouldSwap ? targetLanguageCode.value : sourceLanguageCode.value;
}
// 调用翻译服务 // 调用翻译服务
translationResult = await _translationService.translateText( translationResult = await _translationService.translateText(
text: sourceText, text: sourceText,
@ -560,6 +593,7 @@ class TranslationController extends GetxController {
targetLanguageCode: detectedTargetLanguageCode, targetLanguageCode: detectedTargetLanguageCode,
); );
/// }
if (translationResult != null) { if (translationResult != null) {
// 查找匹配的项目 - 优先使用timestamp查找 // 查找匹配的项目 - 优先使用timestamp查找
int index = -1; int index = -1;
@ -577,26 +611,22 @@ class TranslationController extends GetxController {
saveTranslationHistory(); saveTranslationHistory();
// 更新后滚动到底部 // 更新后滚动到底部
_scrollToBottom(); _scrollToBottom();
if (isFinal && currentMode.value == 'faceToFace') {
print("音频输出文本2$translationResult");
fTFTranslationResult =
(fTFTranslationResult ?? '') + translationResult;
if (activeSpeaker.value == 0 &&
!isPlayback &&
currentMode.value == 'faceToFace') {
isPlayback = true;
await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
await Future.delayed(const Duration(milliseconds: 1300));
await playTranslatedText(fTFTranslationResult!);
fTFTranslationResult = '';
}
}
// 播放TTS - 只在最终结果时播放 // 播放TTS - 只在最终结果时播放
if (isTtsEnabled.value && if (isFinal) {
isFinal && if (isTtsEnabled.value && currentMode.value != 'faceToFace') {
currentMode.value != 'faceToFace') { await playTranslatedText(translationResult);
await playTranslatedText(translationResult); } else {
fTFTranslationResult =
(fTFTranslationResult ?? '') + translationResult;
print("translationResult=$translationResult");
if (activeSpeaker.value == 0 && !isPlayback) {
isPlayback = true;
await _ttsService.setAudioOutputDevice(lastActiveSpeaker.value);
await Future.delayed(const Duration(milliseconds: 1300));
await playTranslatedText(fTFTranslationResult!);
fTFTranslationResult = '';
}
}
} }
} else { } else {
Logger.info( Logger.info(
@ -623,7 +653,7 @@ class TranslationController extends GetxController {
final voiceName = _languageManager final voiceName = _languageManager
.getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ?? .getTtsVoiceNameByAsrCode(detectedTargetLanguageCode) ??
'en-US-AriaNeural'; 'en-US-AriaNeural';
print("voiceName=$voiceName,text$text");
await _ttsService.setVoice(voiceName); await _ttsService.setVoice(voiceName);
await _ttsService.speakOnce(text); await _ttsService.speakOnce(text);
} catch (e) { } catch (e) {

8
lib/modules/translation/views/translation_list_view.dart

@ -340,7 +340,11 @@ class TranslationListView extends GetView<TranslationListController> {
FileSystemEntity file, FileSystemEntity file,
) { ) {
final TextEditingController _renameController = TextEditingController( final TextEditingController _renameController = TextEditingController(
text: file.path.split('/').last, text: () {
final fileName = file.path.split('/').last;
final dotIndex = fileName.lastIndexOf('.');
return dotIndex != -1 ? fileName.substring(0, dotIndex) : fileName;
}(),
); );
Get.bottomSheet( Get.bottomSheet(
@ -432,7 +436,7 @@ class TranslationListView extends GetView<TranslationListController> {
if (newName.isEmpty) return; if (newName.isEmpty) return;
final dir = file.parent.path; final dir = file.parent.path;
final newPath = '$dir/$newName'; final newPath = '$dir/$newName.wav';
try { try {
await file.rename(newPath); await file.rename(newPath);

26
lib/modules/translation/views/translation_view.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -528,27 +530,29 @@ class TranslationView extends GetView<TranslationController> {
}) { }) {
bool isActive = activeSpeaker == speakerId; bool isActive = activeSpeaker == speakerId;
bool isDisabled = activeSpeaker != 0 && activeSpeaker != speakerId; bool isDisabled = activeSpeaker != 0 && activeSpeaker != speakerId;
Timer? _holdTimer; // 增加延迟触发的计时器
return Listener( return Listener(
onPointerDown: (_) { onPointerDown: (_) {
if (activeSpeaker == 0) { // 设置300ms延迟防止误触
// 没有激活的按钮时才能激活 _holdTimer = Timer(const Duration(milliseconds: 200), () {
if (speakerId == 1) { if (activeSpeaker == 0) {
// 耳机收音 - 恢复原始音频状态 if (speakerId == 1) {
controller.restoreOriginalAudioState(); controller.restoreOriginalAudioState();
} else { } else {
// 手机收音 - 禁用蓝牙音频 controller.disableBluetoothAudio();
controller.disableBluetoothAudio(); }
controller.setActiveSpeaker(speakerId);
} }
controller.setActiveSpeaker(speakerId); });
}
}, },
onPointerUp: (_) { onPointerUp: (_) {
_holdTimer?.cancel();
if (isActive) { if (isActive) {
controller.setActiveSpeaker(0); controller.setActiveSpeaker(0);
} }
}, },
onPointerCancel: (_) { onPointerCancel: (_) {
_holdTimer?.cancel();
if (isActive) { if (isActive) {
controller.setActiveSpeaker(0); controller.setActiveSpeaker(0);
} }

2
pubspec.yaml

@ -36,7 +36,7 @@ dependencies:
webview_flutter: ^4.13.0 webview_flutter: ^4.13.0
flutter_easyloading: ^3.0.5 flutter_easyloading: ^3.0.5
package_info_plus: ^8.3.0 package_info_plus: ^8.3.0
file_picker: ^10.1.5 file_picker: ^10.2.0
haptic_feedback: ^0.5.1+1 haptic_feedback: ^0.5.1+1
tencentcloud_cos_sdk_plugin: ^1.2.3 tencentcloud_cos_sdk_plugin: ^1.2.3
photo_view: ^0.15.0 photo_view: ^0.15.0

Loading…
Cancel
Save