mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-11-24 04:12:20 +08:00
parent
29b01e9cef
commit
eb1ef0969c
@ -30,6 +30,7 @@ import 'common/widgets/overlay.dart';
|
|||||||
import 'mobile/pages/file_manager_page.dart';
|
import 'mobile/pages/file_manager_page.dart';
|
||||||
import 'mobile/pages/remote_page.dart';
|
import 'mobile/pages/remote_page.dart';
|
||||||
import 'desktop/pages/remote_page.dart' as desktop_remote;
|
import 'desktop/pages/remote_page.dart' as desktop_remote;
|
||||||
|
import 'desktop/pages/file_manager_page.dart' as desktop_file_manager;
|
||||||
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
import 'package:flutter_hbb/desktop/widgets/remote_toolbar.dart';
|
||||||
import 'models/model.dart';
|
import 'models/model.dart';
|
||||||
import 'models/platform_model.dart';
|
import 'models/platform_model.dart';
|
||||||
@ -2370,18 +2371,33 @@ connect(BuildContext context, String id,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isFileTransfer) {
|
if (isFileTransfer) {
|
||||||
if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
if (isAndroid) {
|
||||||
if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
|
if (!await AndroidPermissionManager.check(kManageExternalStorage)) {
|
||||||
return;
|
if (!await AndroidPermissionManager.request(kManageExternalStorage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Navigator.push(
|
if (isWeb) {
|
||||||
context,
|
Navigator.push(
|
||||||
MaterialPageRoute(
|
context,
|
||||||
builder: (BuildContext context) => FileManagerPage(
|
MaterialPageRoute(
|
||||||
id: id, password: password, isSharedPassword: isSharedPassword),
|
builder: (BuildContext context) =>
|
||||||
),
|
desktop_file_manager.FileManagerPage(
|
||||||
);
|
id: id,
|
||||||
|
password: password,
|
||||||
|
isSharedPassword: isSharedPassword),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (BuildContext context) => FileManagerPage(
|
||||||
|
id: id, password: password, isSharedPassword: isSharedPassword),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isWeb) {
|
if (isWeb) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
|
@ -879,7 +879,7 @@ class RecentPeerCard extends BasePeerCard {
|
|||||||
BuildContext context) async {
|
BuildContext context) async {
|
||||||
final List<MenuEntryBase<String>> menuItems = [
|
final List<MenuEntryBase<String>> menuItems = [
|
||||||
_connectAction(context),
|
_connectAction(context),
|
||||||
if (!isWeb) _transferFileAction(context),
|
_transferFileAction(context),
|
||||||
];
|
];
|
||||||
|
|
||||||
final List favs = (await bind.mainGetFav()).toList();
|
final List favs = (await bind.mainGetFav()).toList();
|
||||||
|
@ -17,6 +17,8 @@ import 'package:flutter_hbb/models/file_model.dart';
|
|||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
|
import 'package:flutter_hbb/web/dummy.dart'
|
||||||
|
if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart';
|
||||||
|
|
||||||
import '../../consts.dart';
|
import '../../consts.dart';
|
||||||
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
||||||
@ -55,14 +57,14 @@ class FileManagerPage extends StatefulWidget {
|
|||||||
required this.id,
|
required this.id,
|
||||||
required this.password,
|
required this.password,
|
||||||
required this.isSharedPassword,
|
required this.isSharedPassword,
|
||||||
required this.tabController,
|
this.tabController,
|
||||||
this.forceRelay})
|
this.forceRelay})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
final String id;
|
final String id;
|
||||||
final String? password;
|
final String? password;
|
||||||
final bool? isSharedPassword;
|
final bool? isSharedPassword;
|
||||||
final bool? forceRelay;
|
final bool? forceRelay;
|
||||||
final DesktopTabController tabController;
|
final DesktopTabController? tabController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => _FileManagerPageState();
|
State<StatefulWidget> createState() => _FileManagerPageState();
|
||||||
@ -97,11 +99,14 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
if (!isLinux) {
|
if (!isLinux) {
|
||||||
WakelockPlus.enable();
|
WakelockPlus.enable();
|
||||||
}
|
}
|
||||||
|
if (isWeb) {
|
||||||
|
_ffi.ffiModel.updateEventListener(_ffi.sessionId, widget.id);
|
||||||
|
}
|
||||||
debugPrint("File manager page init success with id ${widget.id}");
|
debugPrint("File manager page init success with id ${widget.id}");
|
||||||
_ffi.dialogManager.setOverlayState(_overlayKeyState);
|
_ffi.dialogManager.setOverlayState(_overlayKeyState);
|
||||||
// Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState.
|
// Call onSelected in post frame callback, since we cannot guarantee that the callback will not call setState.
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
widget.tabController.onSelected?.call(widget.id);
|
widget.tabController?.onSelected?.call(widget.id);
|
||||||
});
|
});
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
}
|
}
|
||||||
@ -140,10 +145,11 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
if (!isWeb)
|
||||||
flex: 3,
|
Flexible(
|
||||||
child: dropArea(FileManagerView(
|
flex: 3,
|
||||||
model.localController, _ffi, _mouseFocusScope))),
|
child: dropArea(FileManagerView(
|
||||||
|
model.localController, _ffi, _mouseFocusScope))),
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 3,
|
flex: 3,
|
||||||
child: dropArea(FileManagerView(
|
child: dropArea(FileManagerView(
|
||||||
@ -192,7 +198,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return Icon(Icons.delete_outline, color: color);
|
return Icon(Icons.delete_outline, color: color);
|
||||||
default:
|
default:
|
||||||
return Transform.rotate(
|
return Transform.rotate(
|
||||||
angle: job.isRemoteToLocal ? pi : 0,
|
angle: isWeb
|
||||||
|
? job.isRemoteToLocal
|
||||||
|
? pi / 2
|
||||||
|
: pi / 2 * 3
|
||||||
|
: job.isRemoteToLocal
|
||||||
|
? pi
|
||||||
|
: 0,
|
||||||
child: Icon(Icons.arrow_forward_ios, color: color),
|
child: Icon(Icons.arrow_forward_ios, color: color),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -800,6 +812,50 @@ class _FileManagerViewState extends State<FileManagerView> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
if (isWeb)
|
||||||
|
Obx(() => ElevatedButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
|
||||||
|
isLocal
|
||||||
|
? EdgeInsets.only(left: 10)
|
||||||
|
: EdgeInsets.only(right: 10)),
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
selectedItems.items.isEmpty
|
||||||
|
? MyTheme.accent80
|
||||||
|
: MyTheme.accent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => {webselectFiles(is_folder: true)},
|
||||||
|
icon: Offstage(),
|
||||||
|
label: Text(
|
||||||
|
translate('Upload folder'),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
))).marginOnly(left: 16),
|
||||||
|
if (isWeb)
|
||||||
|
Obx(() => ElevatedButton.icon(
|
||||||
|
style: ButtonStyle(
|
||||||
|
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
|
||||||
|
isLocal
|
||||||
|
? EdgeInsets.only(left: 10)
|
||||||
|
: EdgeInsets.only(right: 10)),
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
selectedItems.items.isEmpty
|
||||||
|
? MyTheme.accent80
|
||||||
|
: MyTheme.accent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () => {webselectFiles(is_folder: false)},
|
||||||
|
icon: Offstage(),
|
||||||
|
label: Text(
|
||||||
|
translate('Upload files'),
|
||||||
|
textAlign: TextAlign.right,
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
))).marginOnly(left: 16),
|
||||||
Obx(() => ElevatedButton.icon(
|
Obx(() => ElevatedButton.icon(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
|
padding: MaterialStateProperty.all<EdgeInsetsGeometry>(
|
||||||
@ -833,19 +889,22 @@ class _FileManagerViewState extends State<FileManagerView> {
|
|||||||
: Colors.white,
|
: Colors.white,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: RotatedBox(
|
: isWeb
|
||||||
quarterTurns: 2,
|
? Offstage()
|
||||||
child: SvgPicture.asset(
|
: RotatedBox(
|
||||||
"assets/arrow.svg",
|
quarterTurns: 2,
|
||||||
colorFilter: svgColor(selectedItems.items.isEmpty
|
child: SvgPicture.asset(
|
||||||
? Theme.of(context).brightness ==
|
"assets/arrow.svg",
|
||||||
Brightness.light
|
colorFilter: svgColor(
|
||||||
? MyTheme.grayBg
|
selectedItems.items.isEmpty
|
||||||
: MyTheme.darkGray
|
? Theme.of(context).brightness ==
|
||||||
: Colors.white),
|
Brightness.light
|
||||||
alignment: Alignment.bottomRight,
|
? MyTheme.grayBg
|
||||||
),
|
: MyTheme.darkGray
|
||||||
),
|
: Colors.white),
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
),
|
||||||
label: isLocal
|
label: isLocal
|
||||||
? SvgPicture.asset(
|
? SvgPicture.asset(
|
||||||
"assets/arrow.svg",
|
"assets/arrow.svg",
|
||||||
@ -857,7 +916,7 @@ class _FileManagerViewState extends State<FileManagerView> {
|
|||||||
: Colors.white),
|
: Colors.white),
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
translate('Receive'),
|
translate(isWeb ? 'Download' : 'Receive'),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: selectedItems.items.isEmpty
|
color: selectedItems.items.isEmpty
|
||||||
? Theme.of(context).brightness ==
|
? Theme.of(context).brightness ==
|
||||||
|
@ -7,6 +7,8 @@ import 'package:flutter_hbb/common/widgets/dialog.dart';
|
|||||||
import 'package:flutter_hbb/utils/event_loop.dart';
|
import 'package:flutter_hbb/utils/event_loop.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:flutter_hbb/web/dummy.dart'
|
||||||
|
if (dart.library.html) 'package:flutter_hbb/web/web_unique.dart';
|
||||||
|
|
||||||
import '../consts.dart';
|
import '../consts.dart';
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
@ -74,7 +76,7 @@ class FileModel {
|
|||||||
|
|
||||||
Future<void> onReady() async {
|
Future<void> onReady() async {
|
||||||
await evtLoop.onReady();
|
await evtLoop.onReady();
|
||||||
await localController.onReady();
|
if (!isWeb) await localController.onReady();
|
||||||
await remoteController.onReady();
|
await remoteController.onReady();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +88,7 @@ class FileModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refreshAll() async {
|
Future<void> refreshAll() async {
|
||||||
await localController.refresh();
|
if (!isWeb) await localController.refresh();
|
||||||
await remoteController.refresh();
|
await remoteController.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,6 +230,33 @@ class FileModel {
|
|||||||
);
|
);
|
||||||
}, useAnimation: false);
|
}, useAnimation: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onSelectedFiles(dynamic obj) {
|
||||||
|
localController.selectedItems.clear();
|
||||||
|
|
||||||
|
try {
|
||||||
|
int handleIndex = int.parse(obj['handleIndex']);
|
||||||
|
final file = jsonDecode(obj['file']);
|
||||||
|
var entry = Entry.fromJson(file);
|
||||||
|
entry.path = entry.name;
|
||||||
|
final otherSideData = remoteController.directoryData();
|
||||||
|
final toPath = otherSideData.directory.path;
|
||||||
|
final isWindows = otherSideData.options.isWindows;
|
||||||
|
final showHidden = otherSideData.options.showHidden;
|
||||||
|
final jobID = jobController.addTransferJob(entry, false);
|
||||||
|
webSendLocalFiles(
|
||||||
|
handleIndex: handleIndex,
|
||||||
|
actId: jobID,
|
||||||
|
path: entry.path,
|
||||||
|
to: PathUtil.join(toPath, entry.name, isWindows),
|
||||||
|
fileNum: 0,
|
||||||
|
includeHidden: showHidden,
|
||||||
|
isRemote: false,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint("Failed to decode onSelectedFiles: $e");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DirectoryData {
|
class DirectoryData {
|
||||||
@ -462,7 +491,8 @@ class FileController {
|
|||||||
to: PathUtil.join(toPath, from.name, isWindows),
|
to: PathUtil.join(toPath, from.name, isWindows),
|
||||||
fileNum: 0,
|
fileNum: 0,
|
||||||
includeHidden: showHidden,
|
includeHidden: showHidden,
|
||||||
isRemote: isRemoteToLocal);
|
isRemote: isRemoteToLocal,
|
||||||
|
isDir: from.isDirectory);
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
|
"path: ${from.path}, toPath: $toPath, to: ${PathUtil.join(toPath, from.name, isWindows)}");
|
||||||
}
|
}
|
||||||
@ -489,7 +519,7 @@ class FileController {
|
|||||||
} else if (item.isDirectory) {
|
} else if (item.isDirectory) {
|
||||||
title = translate("Not an empty directory");
|
title = translate("Not an empty directory");
|
||||||
dialogManager?.showLoading(translate("Waiting"));
|
dialogManager?.showLoading(translate("Waiting"));
|
||||||
final fd = await fileFetcher.fetchDirectoryRecursive(
|
final fd = await fileFetcher.fetchDirectoryRecursiveToRemove(
|
||||||
jobID, item.path, items.isLocal, true);
|
jobID, item.path, items.isLocal, true);
|
||||||
if (fd.path.isEmpty) {
|
if (fd.path.isEmpty) {
|
||||||
fd.path = item.path;
|
fd.path = item.path;
|
||||||
@ -809,7 +839,6 @@ class JobController {
|
|||||||
job.speed = double.parse(evt['speed']);
|
job.speed = double.parse(evt['speed']);
|
||||||
job.finishedSize = int.parse(evt['finished_size']);
|
job.finishedSize = int.parse(evt['finished_size']);
|
||||||
job.recvJobRes = true;
|
job.recvJobRes = true;
|
||||||
debugPrint("update job $id with $evt");
|
|
||||||
jobTable.refresh();
|
jobTable.refresh();
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -1116,11 +1145,11 @@ class FileFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<FileDirectory> fetchDirectoryRecursive(
|
Future<FileDirectory> fetchDirectoryRecursiveToRemove(
|
||||||
int actID, String path, bool isLocal, bool showHidden) async {
|
int actID, String path, bool isLocal, bool showHidden) async {
|
||||||
// TODO test Recursive is show hidden default?
|
// TODO test Recursive is show hidden default?
|
||||||
try {
|
try {
|
||||||
await bind.sessionReadDirRecursive(
|
await bind.sessionReadDirToRemoveRecursive(
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
actId: actID,
|
actId: actID,
|
||||||
path: path,
|
path: path,
|
||||||
|
@ -390,6 +390,10 @@ class FfiModel with ChangeNotifier {
|
|||||||
handleFollowCurrentDisplay(evt, sessionId, peerId);
|
handleFollowCurrentDisplay(evt, sessionId, peerId);
|
||||||
} else if (name == 'use_texture_render') {
|
} else if (name == 'use_texture_render') {
|
||||||
_handleUseTextureRender(evt, sessionId, peerId);
|
_handleUseTextureRender(evt, sessionId, peerId);
|
||||||
|
} else if (name == "selected_files") {
|
||||||
|
if (isWeb) {
|
||||||
|
parent.target?.fileModel.onSelectedFiles(evt);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
debugPrint('Event is not handled in the fixed branch: $name');
|
debugPrint('Event is not handled in the fixed branch: $name');
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
14
flutter/lib/web/dummy.dart
Normal file
14
flutter/lib/web/dummy.dart
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
Future<void> webselectFiles({required bool is_folder}) async {
|
||||||
|
throw UnimplementedError("webselectFiles");
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> webSendLocalFiles(
|
||||||
|
{required int handleIndex,
|
||||||
|
required int actId,
|
||||||
|
required String path,
|
||||||
|
required String to,
|
||||||
|
required int fileNum,
|
||||||
|
required bool includeHidden,
|
||||||
|
required bool isRemote}) {
|
||||||
|
throw UnimplementedError("webSendLocalFiles");
|
||||||
|
}
|
30
flutter/lib/web/web_unique.dart
Normal file
30
flutter/lib/web/web_unique.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:js' as js;
|
||||||
|
|
||||||
|
Future<void> webselectFiles({required bool is_folder}) async {
|
||||||
|
return Future(
|
||||||
|
() => js.context.callMethod('setByName', ['select_files', is_folder]));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> webSendLocalFiles(
|
||||||
|
{required int handleIndex,
|
||||||
|
required int actId,
|
||||||
|
required String path,
|
||||||
|
required String to,
|
||||||
|
required int fileNum,
|
||||||
|
required bool includeHidden,
|
||||||
|
required bool isRemote}) {
|
||||||
|
return Future(() => js.context.callMethod('setByName', [
|
||||||
|
'send_local_files',
|
||||||
|
jsonEncode({
|
||||||
|
'id': actId,
|
||||||
|
'handle_index': handleIndex,
|
||||||
|
'path': path,
|
||||||
|
'to': to,
|
||||||
|
'file_num': fileNum,
|
||||||
|
'include_hidden': includeHidden,
|
||||||
|
'is_remote': isRemote,
|
||||||
|
})
|
||||||
|
]));
|
||||||
|
}
|
@ -602,6 +602,7 @@ pub fn session_send_files(
|
|||||||
file_num: i32,
|
file_num: i32,
|
||||||
include_hidden: bool,
|
include_hidden: bool,
|
||||||
is_remote: bool,
|
is_remote: bool,
|
||||||
|
_is_dir: bool,
|
||||||
) {
|
) {
|
||||||
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
|
||||||
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
|
||||||
@ -633,7 +634,7 @@ pub fn session_remove_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_read_dir_recursive(
|
pub fn session_read_dir_to_remove_recursive(
|
||||||
session_id: SessionID,
|
session_id: SessionID,
|
||||||
act_id: i32,
|
act_id: i32,
|
||||||
path: String,
|
path: String,
|
||||||
|
Loading…
Reference in New Issue
Block a user