diff --git a/lib/core/utils/ftpconnect/src/ftp_entry.dart b/lib/core/utils/ftpconnect/src/ftp_entry.dart
index bdf1a3623..fd241b4f4 100644
--- a/lib/core/utils/ftpconnect/src/ftp_entry.dart
+++ b/lib/core/utils/ftpconnect/src/ftp_entry.dart
@@ -27,11 +27,8 @@ class FTPEntry {
r"(.+)$" //file/dir name[8]
);
- static final regexpLISTSiiServers = RegExp(r"^(.{8}\s+.{7})\s+" //date[1]
- r"(.{0,5})\s+" //type file or dir [2]
- r"(\d{0,24})\s+" //size [3]
- r"(.+)$" //file/ dir name [4]
- );
+ static final regexpLISTSiiServers = RegExp(
+ r"^(\d{2}-\d{2}-\d{4}\s+\d{2}:\d{2}[AP]M)\s+(\
)?\s*(\d*)\s+(.+)$");
// Hide constructor
FTPEntry._(
@@ -218,35 +215,22 @@ class FTPEntry {
int _uid = -1;
Iterable matches = regexpLISTSiiServers.allMatches(responseLine);
for (Match match in matches) {
- //date
- String date =
- match.group(1)!.split(" ").fold('', (previousValue, element) {
- //keep only one space and add fullyear if only last 2 digits in year
- if (element.isEmpty) return previousValue;
- if (previousValue.isEmpty) {
- return element.length <= 8
- ? element.substring(0, 6) +
- DateTime.now().year.toString().substring(0, 2) +
- element.substring(6, 8)
- : element;
- }
- return '$previousValue $element';
- });
+ // 日期,合并多余空格
+ String date = match.group(1)!.replaceAll(RegExp(r'\s+'), ' ');
_modifyTime = DateFormat('MM-dd-yyyy hh:mma').parse(date);
- //type
- if (match.group(2)!.trim().isEmpty) {
- _type = FTPEntryType.FILE;
- } else if (match.group(2)!.toLowerCase().contains("dir")) {
+ // 类型
+ if ((match.group(2) ?? '').trim().isNotEmpty) {
_type = FTPEntryType.DIR;
} else {
- _type = FTPEntryType.LINK;
+ _type = FTPEntryType.FILE;
}
- //size
- _size = int.tryParse(match.group(3)!) ?? 0;
- //file/dir name
- _name = match.group(4)!;
+ // 大小
+ _size = int.tryParse(match.group(3) ?? '') ?? 0;
+
+ // 文件/目录名
+ _name = match.group(4)!.trim();
}
return FTPEntry._(_name, _modifyTime, _persmission, _type, _size, _unique,
_group, _gid, _mode, _owner, _uid, {});
diff --git a/lib/modules/meeting/controllers/meeting_connect_controller.dart b/lib/modules/meeting/controllers/meeting_connect_controller.dart
index af101e1cb..31a1b47fc 100644
--- a/lib/modules/meeting/controllers/meeting_connect_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_connect_controller.dart
@@ -1,13 +1,19 @@
import 'dart:async';
+import 'dart:io';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';
+import 'package:path_provider/path_provider.dart';
import 'package:wifi_iot/wifi_iot.dart';
+import '../../../core/utils/ftpconnect/ftpconnect.dart';
+import 'meeting_controller.dart';
+
class MeetingConnectController extends GetxController {
+ final _meetingController = Get.find();
+
RxString stateStr = '搜索中...'.obs;
RxBool isRescan = false.obs;
- RxBool isImportedFiles = false.obs;
RxString importedFilesStr = ''.obs;
late StreamSubscription> _scanResultsSubscription;
@@ -16,8 +22,8 @@ class MeetingConnectController extends GetxController {
bool isScanFailed = false;
- late BluetoothCharacteristic? _writeCharacteristic;
- late StreamSubscription>? _valueReceivedSubscription;
+ BluetoothCharacteristic? _writeCharacteristic;
+ StreamSubscription>? _valueReceivedSubscription;
String _wifiSSID = '';
String _wifiPassword = '';
@@ -26,6 +32,13 @@ class MeetingConnectController extends GetxController {
String _ftpAccount = '';
String _ftpPassword = '';
+ FTPConnect? _ftpConnect;
+
+ RxBool isDownload = false.obs;
+ RxDouble downloadProgress = 0.0.obs;
+ RxInt totalReceived = 0.obs;
+ RxInt fileSize = 0.obs;
+
@override
void onInit() {
super.onInit();
@@ -56,6 +69,7 @@ class MeetingConnectController extends GetxController {
_scanResultsSubscription.cancel();
_isScanningSubscription.cancel();
_valueReceivedSubscription?.cancel();
+ _ftpConnect?.disconnect();
_stopScan();
_disconnect();
super.onClose();
@@ -86,6 +100,7 @@ class MeetingConnectController extends GetxController {
}
Future _disconnect() async {
+ await WiFiForIoTPlugin.forceWifiUsage(false);
await WiFiForIoTPlugin.disconnect();
await _device?.disconnect();
}
@@ -131,26 +146,25 @@ class MeetingConnectController extends GetxController {
}
void _listenData(List value) {
- print('----$value--------');
int command = value[2];
switch (command) {
case 0x0d: // 是否有导入文件
if (value[3] == 0x01) {
- isImportedFiles.value = true;
- importedFilesStr.value = '检查到有新文件,是否导入';
+ stateStr.value = '正在开启Wi-Fi...';
+ importedFilesStr.value = '检查到有新文件,正在准备导入';
+ sendData([0x5a, 0x02, 0x87, 0x01]);
} else {
- isImportedFiles.value = false;
importedFilesStr.value = '没有新文件需要导入';
}
break;
case 0x87: // Wi-Fi 开启/关闭
if (value[3] == 0x01 && value[4] == 0x01) {
- _getWifi();
+ sendData([0x5a, 0x01, 0x0b]);
}
break;
case 0x0b: // WiFi 热点 SSID
_wifiSSID = String.fromCharCodes(value.sublist(3, value.length - 2));
- _connectWifi();
+ sendData([0x5a, 0x01, 0x0c]);
break;
case 0x0c: // WiFi 热点密码
_wifiPassword = _decodePassword(value);
@@ -176,11 +190,6 @@ class MeetingConnectController extends GetxController {
}
}
- void _getWifi() async {
- await sendData([0x5a, 0x01, 0x0b]);
- await sendData([0x5a, 0x01, 0x0c]);
- }
-
void _connectWifi() async {
if (_wifiSSID.isNotEmpty && _wifiPassword.isNotEmpty) {
stateStr.value = '正在连接 Wi-Fi...';
@@ -189,16 +198,71 @@ class MeetingConnectController extends GetxController {
password: _wifiPassword,
security: NetworkSecurity.WPA,
);
+ await WiFiForIoTPlugin.forceWifiUsage(true);
stateStr.value = 'Wi-Fi 连接成功';
sendData([0x5a, 0x01, 0x03]);
}
}
- void _connectFtp() {
+ void _connectFtp() async {
if (_ftpAddress.isNotEmpty &&
_ftpPort.isNotEmpty &&
_ftpAccount.isNotEmpty &&
- _ftpPassword.isNotEmpty) {}
+ _ftpPassword.isNotEmpty) {
+ _ftpConnect = FTPConnect(
+ _ftpAddress,
+ port: int.tryParse(_ftpPort) ?? 21,
+ user: _ftpAccount,
+ pass: _ftpPassword,
+ );
+ await _ftpConnect?.connect();
+ _ftpConnect?.listCommand = ListCommand.LIST;
+ List? listDirectory = await _ftpConnect?.listDirectoryContent();
+ if (listDirectory != null && listDirectory.isNotEmpty) {
+ stateStr.value = '正在下载文件...';
+ isDownload.value = true;
+ int totalSize = listDirectory.fold(
+ 0,
+ (sum, entry) => sum + (entry.size ?? 0),
+ );
+ for (var i = 0; i < listDirectory.length; i++) {
+ importedFilesStr.value =
+ '共${listDirectory.length}个文件,正在下载第${i + 1}个文件';
+ int previousSize = i > 0 ? listDirectory[i - 1].size ?? 0 : 0;
+ FTPEntry element = listDirectory[i];
+ await _downloadFile(totalSize, previousSize, element);
+ await _ftpConnect?.deleteFile(element.name);
+ }
+ stateStr.value = '下载完成';
+ importedFilesStr.value = '下载完成,共${listDirectory.length}个文件,2秒后自动返回';
+ _disconnect();
+ Future.delayed(const Duration(seconds: 2), () {
+ Get.back();
+ });
+ }
+ }
+ }
+
+ Future _downloadFile(
+ int totalSize, int previousSize, FTPEntry entry) async {
+ final directory = await getApplicationDocumentsDirectory();
+ final directoryDir = Directory('${directory.path}/RecordingPen');
+ if (!await directoryDir.exists()) {
+ await directoryDir.create(recursive: true);
+ }
+ final String filePath = '${directory.path}/RecordingPen/${entry.name}';
+ final File localFile = File(filePath);
+ await _ftpConnect?.downloadFileWithRetry(
+ entry.name,
+ localFile,
+ pRetryCount: 3,
+ onProgress: (double progress, int received, int size) {
+ downloadProgress.value = (previousSize + received) / totalSize;
+ totalReceived.value = received;
+ fileSize.value = size;
+ },
+ );
+ await _meetingController.insertMeetingAudio(entry.name, filePath, '');
}
String _decodePassword(List data) {
diff --git a/lib/modules/meeting/controllers/meeting_controller.dart b/lib/modules/meeting/controllers/meeting_controller.dart
index b9767ee74..de9a05a63 100644
--- a/lib/modules/meeting/controllers/meeting_controller.dart
+++ b/lib/modules/meeting/controllers/meeting_controller.dart
@@ -190,13 +190,11 @@ class MeetingController extends GetxController {
int addUp = 0;
final appDir = await appDocDir;
-// 定义源目录列表
final sourceDirs = [
Directory("${appDir.path}/MeetingAudio/"),
Directory("${appDir.path}/DestAudio/"),
];
-// 创建目标目录(如果不存在)
final destDir = Directory("${appDir.path}/LocalAudio/");
if (!await destDir.exists()) {
await destDir.create(recursive: true);
@@ -212,8 +210,6 @@ class MeetingController extends GetxController {
}
}
-// 此时 allFiles 包含 MeetingAudio 和 DestAudio 目录下所有的文件
-
for (final fileEntity in allFiles) {
try {
final file = File(fileEntity.path);
@@ -334,9 +330,21 @@ class MeetingController extends GetxController {
}
}
- await _audioPlayer.setAudioSource(AudioSource.file(filePath!));
var audiourl =
await UploadOss.upload(filepath: filePath, rootDir: 'ExternalAudio');
+ final meetingId = await insertMeetingAudio(fileName, filePath, audiourl);
+
+ if (isParamProvided) {
+ Get.toNamed(Routes.meetingDetails, arguments: {
+ 'id': meetingId,
+ });
+ }
+ EasyLoading.dismiss();
+ }
+
+ Future insertMeetingAudio(
+ String fileName, String filePath, String audiourl) async {
+ await _audioPlayer.setAudioSource(AudioSource.file(filePath));
int seconds = _audioPlayer.duration?.inSeconds ?? 0;
int creationTime = DateTime.now().millisecondsSinceEpoch;
Map data = {
@@ -357,13 +365,7 @@ class MeetingController extends GetxController {
});
addQuantity++;
}
-
- if (isParamProvided) {
- Get.toNamed(Routes.meetingDetails, arguments: {
- 'id': meetingId,
- });
- }
- EasyLoading.dismiss();
+ return meetingId;
}
Future _saveFile(PlatformFile file) async {
diff --git a/lib/modules/meeting/views/meeting_connect_view.dart b/lib/modules/meeting/views/meeting_connect_view.dart
index e8e86fad4..82c3e3d97 100644
--- a/lib/modules/meeting/views/meeting_connect_view.dart
+++ b/lib/modules/meeting/views/meeting_connect_view.dart
@@ -76,19 +76,29 @@ class MeetingConnectView extends GetView {
),
),
),
- Obx(
- () => controller.isImportedFiles.value
- ? Padding(
- padding: EdgeInsets.symmetric(vertical: 10.w),
- child: _button('导入文件', Icons.file_download, onTap: () {
- controller.stateStr.value = '正在开启Wi-Fi...';
- controller.importedFilesStr.value = '';
- controller.isImportedFiles.value = false;
- controller.sendData([0x5a, 0x02, 0x87, 0x01]);
- }),
- )
- : const SizedBox(),
- ),
+ Obx(() => controller.isDownload.value
+ ? Column(
+ children: [
+ Container(
+ width: 300.w,
+ height: 3.w,
+ margin: EdgeInsets.symmetric(vertical: 10.w),
+ child: LinearProgressIndicator(
+ value: controller.downloadProgress.value,
+ valueColor:
+ const AlwaysStoppedAnimation(Colors.red),
+ ),
+ ),
+ Text(
+ '${controller.totalReceived.value} / ${controller.fileSize.value} bytes',
+ style: TextStyle(
+ fontSize: 14.sp,
+ color: isDarkMode ? Colors.white70 : Colors.black54,
+ ),
+ ),
+ ],
+ )
+ : const SizedBox()),
],
),
);
diff --git a/lib/modules/meeting/views/meeting_view.dart b/lib/modules/meeting/views/meeting_view.dart
index db4fe3ba8..33ee92176 100644
--- a/lib/modules/meeting/views/meeting_view.dart
+++ b/lib/modules/meeting/views/meeting_view.dart
@@ -423,6 +423,7 @@ class MeetingView extends GetView {
PreferredSizeWidget _buildAppBar(BuildContext context, bool isDarkMode) {
return AppBar(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
+ surfaceTintColor: Colors.transparent,
elevation: 0.5,
leading: IconButton(
icon: Icon(
@@ -459,7 +460,7 @@ class MeetingView extends GetView {
() => ListView.builder(
controller: controller.listController,
itemCount: controller.dataList.length,
- padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 5.w),
+ padding: EdgeInsets.fromLTRB(12.w, 5.w, 12.w, 90.w),
itemBuilder: (BuildContext context, int index) {
MeetingModel data = MeetingModel.fromJson(controller.dataList[index]);
return _item(context, isDarkMode, data);