mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-12-12 18:29:10 +08:00
a001b15335
Signed-off-by: Kingtous <kingtous@qq.com>
862 lines
33 KiB
Dart
862 lines
33 KiB
Dart
import 'dart:io';
|
|
import 'dart:math';
|
|
|
|
import 'package:desktop_drop/desktop_drop.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
|
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
|
import 'package:flutter_hbb/models/file_model.dart';
|
|
import 'package:get/get.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:wakelock/wakelock.dart';
|
|
|
|
import '../../common.dart';
|
|
import '../../models/model.dart';
|
|
import '../../models/platform_model.dart';
|
|
|
|
enum LocationStatus { bread, textField }
|
|
|
|
class FileManagerPage extends StatefulWidget {
|
|
FileManagerPage({Key? key, required this.id}) : super(key: key);
|
|
final String id;
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _FileManagerPageState();
|
|
}
|
|
|
|
class _FileManagerPageState extends State<FileManagerPage>
|
|
with AutomaticKeepAliveClientMixin {
|
|
final _localSelectedItems = SelectedItems();
|
|
final _remoteSelectedItems = SelectedItems();
|
|
|
|
final _locationStatusLocal = LocationStatus.bread.obs;
|
|
final _locationStatusRemote = LocationStatus.bread.obs;
|
|
final FocusNode _locationNodeLocal =
|
|
FocusNode(debugLabel: "locationNodeLocal");
|
|
final FocusNode _locationNodeRemote =
|
|
FocusNode(debugLabel: "locationNodeRemote");
|
|
final _searchTextLocal = "".obs;
|
|
final _searchTextRemote = "".obs;
|
|
final _breadCrumbScrollerLocal = ScrollController();
|
|
final _breadCrumbScrollerRemote = ScrollController();
|
|
|
|
final _dropMaskVisible = false.obs;
|
|
|
|
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
|
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
|
|
}
|
|
|
|
late FFI _ffi;
|
|
|
|
FileModel get model => _ffi.fileModel;
|
|
|
|
SelectedItems getSelectedItem(bool isLocal) {
|
|
return isLocal ? _localSelectedItems : _remoteSelectedItems;
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_ffi = FFI();
|
|
_ffi.connect(widget.id, isFileTransfer: true);
|
|
Get.put(_ffi, tag: 'ft_${widget.id}');
|
|
// _ffi.ffiModel.updateEventListener(widget.id);
|
|
if (!Platform.isLinux) {
|
|
Wakelock.enable();
|
|
}
|
|
print("init success with id ${widget.id}");
|
|
// register location listener
|
|
_locationNodeLocal.addListener(onLocalLocationFocusChanged);
|
|
_locationNodeRemote.addListener(onRemoteLocationFocusChanged);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
model.onClose();
|
|
_ffi.close();
|
|
_ffi.dialogManager.dismissAll();
|
|
if (!Platform.isLinux) {
|
|
Wakelock.disable();
|
|
}
|
|
Get.delete<FFI>(tag: 'ft_${widget.id}');
|
|
_locationNodeLocal.removeListener(onLocalLocationFocusChanged);
|
|
_locationNodeRemote.removeListener(onRemoteLocationFocusChanged);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
super.build(context);
|
|
return ChangeNotifierProvider.value(
|
|
value: _ffi.fileModel,
|
|
child: Consumer<FileModel>(builder: (_context, _model, _child) {
|
|
return WillPopScope(
|
|
onWillPop: () async {
|
|
if (model.selectMode) {
|
|
model.toggleSelectMode();
|
|
}
|
|
return false;
|
|
},
|
|
child: Scaffold(
|
|
body: Row(
|
|
children: [
|
|
Flexible(flex: 3, child: body(isLocal: true)),
|
|
Flexible(flex: 3, child: body(isLocal: false)),
|
|
Flexible(flex: 2, child: statusList())
|
|
],
|
|
),
|
|
));
|
|
}));
|
|
}
|
|
|
|
Widget menu({bool isLocal = false}) {
|
|
return PopupMenuButton<String>(
|
|
icon: Icon(Icons.more_vert),
|
|
itemBuilder: (context) {
|
|
return [
|
|
PopupMenuItem(
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
model.getCurrentShowHidden(isLocal)
|
|
? Icons.check_box_outlined
|
|
: Icons.check_box_outline_blank,
|
|
color: Colors.black),
|
|
SizedBox(width: 5),
|
|
Text(translate("Show Hidden Files"))
|
|
],
|
|
),
|
|
value: "hidden",
|
|
)
|
|
];
|
|
},
|
|
onSelected: (v) {
|
|
if (v == "hidden") {
|
|
model.toggleShowHidden(local: isLocal);
|
|
}
|
|
});
|
|
}
|
|
|
|
Widget body({bool isLocal = false}) {
|
|
final fd = model.getCurrentDir(isLocal);
|
|
final entries = fd.entries;
|
|
final sortIndex = (SortBy style) {
|
|
switch (style) {
|
|
case SortBy.Name:
|
|
return 1;
|
|
case SortBy.Type:
|
|
return 0;
|
|
case SortBy.Modified:
|
|
return 2;
|
|
case SortBy.Size:
|
|
return 3;
|
|
}
|
|
}(model.getSortStyle(isLocal));
|
|
final sortAscending =
|
|
isLocal ? model.localSortAscending : model.remoteSortAscending;
|
|
return Container(
|
|
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
|
margin: const EdgeInsets.all(16.0),
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: DropTarget(
|
|
onDragDone: (detail) => handleDragDone(detail, isLocal),
|
|
onDragEntered: (enter) {
|
|
_dropMaskVisible.value = true;
|
|
},
|
|
onDragExited: (exit) {
|
|
_dropMaskVisible.value = false;
|
|
},
|
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
|
headTools(isLocal),
|
|
Expanded(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: SingleChildScrollView(
|
|
child: ObxValue<RxString>(
|
|
(searchText) {
|
|
final filteredEntries = searchText.isEmpty
|
|
? entries.where((element) {
|
|
if (searchText.isEmpty) {
|
|
return true;
|
|
} else {
|
|
return element.name.contains(searchText.value);
|
|
}
|
|
}).toList(growable: false)
|
|
: entries;
|
|
return DataTable(
|
|
key: ValueKey(isLocal ? 0 : 1),
|
|
showCheckboxColumn: true,
|
|
dataRowHeight: 25,
|
|
headingRowHeight: 30,
|
|
columnSpacing: 8,
|
|
showBottomBorder: true,
|
|
sortColumnIndex: sortIndex,
|
|
sortAscending: sortAscending,
|
|
columns: [
|
|
DataColumn(label: Text(translate(" "))), // icon
|
|
DataColumn(
|
|
label: Text(
|
|
translate("Name"),
|
|
),
|
|
onSort: (columnIndex, ascending) {
|
|
model.changeSortStyle(SortBy.Name,
|
|
isLocal: isLocal, ascending: ascending);
|
|
}),
|
|
DataColumn(
|
|
label: Text(
|
|
translate("Modified"),
|
|
),
|
|
onSort: (columnIndex, ascending) {
|
|
model.changeSortStyle(SortBy.Modified,
|
|
isLocal: isLocal, ascending: ascending);
|
|
}),
|
|
DataColumn(
|
|
label: Text(translate("Size")),
|
|
onSort: (columnIndex, ascending) {
|
|
model.changeSortStyle(SortBy.Size,
|
|
isLocal: isLocal, ascending: ascending);
|
|
}),
|
|
],
|
|
rows: filteredEntries.map((entry) {
|
|
final sizeStr = entry.isFile
|
|
? readableFileSize(entry.size.toDouble())
|
|
: "";
|
|
return DataRow(
|
|
key: ValueKey(entry.name),
|
|
onSelectChanged: (s) {
|
|
if (s != null) {
|
|
if (s) {
|
|
getSelectedItem(isLocal)
|
|
.add(isLocal, entry);
|
|
} else {
|
|
getSelectedItem(isLocal).remove(entry);
|
|
}
|
|
setState(() {});
|
|
}
|
|
},
|
|
selected:
|
|
getSelectedItem(isLocal).contains(entry),
|
|
cells: [
|
|
DataCell(Icon(
|
|
entry.isFile
|
|
? Icons.feed_outlined
|
|
: Icons.folder,
|
|
size: 25)),
|
|
DataCell(
|
|
ConstrainedBox(
|
|
constraints:
|
|
BoxConstraints(maxWidth: 100),
|
|
child: Tooltip(
|
|
message: entry.name,
|
|
child: Text(entry.name,
|
|
overflow: TextOverflow.ellipsis),
|
|
)), onTap: () {
|
|
if (entry.isDirectory) {
|
|
openDirectory(entry.path, isLocal: isLocal);
|
|
if (isLocal) {
|
|
_localSelectedItems.clear();
|
|
} else {
|
|
_remoteSelectedItems.clear();
|
|
}
|
|
} else {
|
|
// Perform file-related tasks.
|
|
final _selectedItems =
|
|
getSelectedItem(isLocal);
|
|
if (_selectedItems.contains(entry)) {
|
|
_selectedItems.remove(entry);
|
|
} else {
|
|
_selectedItems.add(isLocal, entry);
|
|
}
|
|
setState(() {});
|
|
}
|
|
}),
|
|
DataCell(Text(
|
|
entry
|
|
.lastModified()
|
|
.toString()
|
|
.replaceAll(".000", "") +
|
|
" ",
|
|
style: TextStyle(
|
|
fontSize: 12, color: MyTheme.darkGray),
|
|
)),
|
|
DataCell(Text(
|
|
sizeStr,
|
|
style: TextStyle(
|
|
fontSize: 12, color: MyTheme.darkGray),
|
|
)),
|
|
]);
|
|
}).toList(growable: false),
|
|
);
|
|
},
|
|
isLocal ? _searchTextLocal : _searchTextRemote,
|
|
),
|
|
),
|
|
)
|
|
],
|
|
)),
|
|
// Center(child: listTail(isLocal: isLocal)),
|
|
// Expanded(
|
|
// child: ListView.builder(
|
|
// itemCount: entries.length + 1,
|
|
// itemBuilder: (context, index) {
|
|
// if (index >= entries.length) {
|
|
// return listTail(isLocal: isLocal);
|
|
// }
|
|
// var selected = false;
|
|
// if (model.selectMode) {
|
|
// selected = _selectedItems.contains(entries[index]);
|
|
// }
|
|
//
|
|
// final sizeStr = entries[index].isFile
|
|
// ? readableFileSize(entries[index].size.toDouble())
|
|
// : "";
|
|
// return Card(
|
|
// child: ListTile(
|
|
// leading: Icon(
|
|
// entries[index].isFile ? Icons.feed_outlined : Icons.folder,
|
|
// size: 40),
|
|
// title: Text(entries[index].name),
|
|
// selected: selected,
|
|
// subtitle: Text(
|
|
// entries[index]
|
|
// .lastModified()
|
|
// .toString()
|
|
// .replaceAll(".000", "") +
|
|
// " " +
|
|
// sizeStr,
|
|
// style: TextStyle(fontSize: 12, color: MyTheme.darkGray),
|
|
// ),
|
|
// trailing: needShowCheckBox()
|
|
// ? Checkbox(
|
|
// value: selected,
|
|
// onChanged: (v) {
|
|
// if (v == null) return;
|
|
// if (v && !selected) {
|
|
// _selectedItems.add(isLocal, entries[index]);
|
|
// } else if (!v && selected) {
|
|
// _selectedItems.remove(entries[index]);
|
|
// }
|
|
// setState(() {});
|
|
// })
|
|
// : PopupMenuButton<String>(
|
|
// icon: Icon(Icons.more_vert),
|
|
// itemBuilder: (context) {
|
|
// return [
|
|
// PopupMenuItem(
|
|
// child: Text(translate("Delete")),
|
|
// value: "delete",
|
|
// ),
|
|
// PopupMenuItem(
|
|
// child: Text(translate("Multi Select")),
|
|
// value: "multi_select",
|
|
// ),
|
|
// PopupMenuItem(
|
|
// child: Text(translate("Properties")),
|
|
// value: "properties",
|
|
// enabled: false,
|
|
// )
|
|
// ];
|
|
// },
|
|
// onSelected: (v) {
|
|
// if (v == "delete") {
|
|
// final items = SelectedItems();
|
|
// items.add(isLocal, entries[index]);
|
|
// model.removeAction(items);
|
|
// } else if (v == "multi_select") {
|
|
// _selectedItems.clear();
|
|
// model.toggleSelectMode();
|
|
// }
|
|
// }),
|
|
// onTap: () {
|
|
// if (model.selectMode && !_selectedItems.isOtherPage(isLocal)) {
|
|
// if (selected) {
|
|
// _selectedItems.remove(entries[index]);
|
|
// } else {
|
|
// _selectedItems.add(isLocal, entries[index]);
|
|
// }
|
|
// setState(() {});
|
|
// return;
|
|
// }
|
|
// if (entries[index].isDirectory) {
|
|
// openDirectory(entries[index].path, isLocal: isLocal);
|
|
// breadCrumbScrollToEnd(isLocal);
|
|
// } else {
|
|
// // Perform file-related tasks.
|
|
// }
|
|
// },
|
|
// onLongPress: () {
|
|
// _selectedItems.clear();
|
|
// model.toggleSelectMode();
|
|
// if (model.selectMode) {
|
|
// _selectedItems.add(isLocal, entries[index]);
|
|
// }
|
|
// setState(() {});
|
|
// },
|
|
// ),
|
|
// );
|
|
// },
|
|
// ))
|
|
]),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// transfer status list
|
|
/// watch transfer status
|
|
Widget statusList() {
|
|
return PreferredSize(
|
|
child: Container(
|
|
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
|
|
padding: const EdgeInsets.all(8.0),
|
|
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
|
|
child: Obx(
|
|
() => ListView.builder(
|
|
itemBuilder: (BuildContext context, int index) {
|
|
final item = model.jobTable[index];
|
|
return Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Transform.rotate(
|
|
angle: item.isRemote ? pi : 0,
|
|
child: Icon(Icons.send)),
|
|
SizedBox(
|
|
width: 16.0,
|
|
),
|
|
Expanded(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Tooltip(
|
|
message: item.jobName,
|
|
child: Text(
|
|
'${item.jobName}',
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
)),
|
|
Wrap(
|
|
children: [
|
|
Text(
|
|
'${item.state.display()} ${max(0, item.fileNum)}/${item.fileCount} '),
|
|
Text(
|
|
'${translate("files")} ${readableFileSize(item.totalSize.toDouble())} '),
|
|
Offstage(
|
|
offstage:
|
|
item.state != JobState.inProgress,
|
|
child: Text(
|
|
'${readableFileSize(item.speed) + "/s"} ')),
|
|
Offstage(
|
|
offstage: item.totalSize <= 0,
|
|
child: Text(
|
|
'${(item.finishedSize.toDouble() * 100 / item.totalSize.toDouble()).toStringAsFixed(2)}%'),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
Offstage(
|
|
offstage: item.state != JobState.paused,
|
|
child: IconButton(
|
|
onPressed: () {
|
|
model.resumeJob(item.id);
|
|
},
|
|
icon: Icon(Icons.restart_alt_rounded)),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.delete),
|
|
onPressed: () {
|
|
model.jobTable.removeAt(index);
|
|
model.cancelJob(item.id);
|
|
},
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
SizedBox(
|
|
height: 8.0,
|
|
),
|
|
Divider(
|
|
height: 2.0,
|
|
)
|
|
],
|
|
);
|
|
},
|
|
itemCount: model.jobTable.length,
|
|
),
|
|
),
|
|
),
|
|
preferredSize: Size(200, double.infinity));
|
|
}
|
|
|
|
goBack({bool? isLocal}) {
|
|
model.goToParentDirectory(isLocal: isLocal);
|
|
}
|
|
|
|
Widget headTools(bool isLocal) {
|
|
final _locationStatus =
|
|
isLocal ? _locationStatusLocal : _locationStatusRemote;
|
|
final _locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote;
|
|
final _searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
|
|
return Container(
|
|
child: Column(
|
|
children: [
|
|
// symbols
|
|
PreferredSize(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Container(
|
|
width: 50,
|
|
height: 50,
|
|
decoration: BoxDecoration(color: Colors.blue),
|
|
padding: EdgeInsets.all(8.0),
|
|
child: FutureBuilder<String>(
|
|
future: bind.sessionGetPlatform(
|
|
id: _ffi.id, isRemote: !isLocal),
|
|
builder: (context, snapshot) {
|
|
if (snapshot.hasData && snapshot.data!.isNotEmpty) {
|
|
return getPlatformImage('${snapshot.data}');
|
|
} else {
|
|
return CircularProgressIndicator(
|
|
color: Colors.white,
|
|
);
|
|
}
|
|
})),
|
|
Text(isLocal
|
|
? translate("Local Computer")
|
|
: translate("Remote Computer"))
|
|
.marginOnly(left: 8.0)
|
|
],
|
|
),
|
|
preferredSize: Size(double.infinity, 70)),
|
|
// buttons
|
|
Row(
|
|
children: [
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
model.goHome(isLocal: isLocal);
|
|
},
|
|
icon: Icon(Icons.home_outlined)),
|
|
IconButton(
|
|
icon: Icon(Icons.arrow_upward),
|
|
onPressed: () {
|
|
goBack(isLocal: isLocal);
|
|
},
|
|
),
|
|
menu(isLocal: isLocal),
|
|
],
|
|
),
|
|
Expanded(
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
_locationStatus.value =
|
|
_locationStatus.value == LocationStatus.bread
|
|
? LocationStatus.textField
|
|
: LocationStatus.bread;
|
|
Future.delayed(Duration.zero, () {
|
|
if (_locationStatus.value == LocationStatus.textField) {
|
|
_locationFocus.requestFocus();
|
|
}
|
|
});
|
|
},
|
|
child: Container(
|
|
decoration:
|
|
BoxDecoration(border: Border.all(color: Colors.black12)),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Obx(() =>
|
|
_locationStatus.value == LocationStatus.bread
|
|
? buildBread(isLocal)
|
|
: buildPathLocation(isLocal))),
|
|
DropdownButton<String>(
|
|
isDense: true,
|
|
underline: Offstage(),
|
|
items: [
|
|
// TODO: favourite
|
|
DropdownMenuItem(
|
|
child: Text('/'),
|
|
value: '/',
|
|
)
|
|
],
|
|
onChanged: (path) {
|
|
if (path is String && path.isNotEmpty) {
|
|
openDirectory(path, isLocal: isLocal);
|
|
}
|
|
})
|
|
],
|
|
)),
|
|
)),
|
|
PopupMenuButton(
|
|
itemBuilder: (context) => [
|
|
PopupMenuItem(
|
|
enabled: false,
|
|
child: ConstrainedBox(
|
|
constraints: BoxConstraints(minWidth: 200),
|
|
child: TextField(
|
|
controller:
|
|
TextEditingController(text: _searchTextObs.value),
|
|
autofocus: true,
|
|
decoration:
|
|
InputDecoration(prefixIcon: Icon(Icons.search)),
|
|
onChanged: (searchText) =>
|
|
onSearchText(searchText, isLocal),
|
|
),
|
|
))
|
|
],
|
|
child: Icon(Icons.search),
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
model.refresh(isLocal: isLocal);
|
|
},
|
|
icon: Icon(Icons.refresh)),
|
|
],
|
|
),
|
|
Row(
|
|
textDirection: isLocal ? TextDirection.ltr : TextDirection.rtl,
|
|
children: [
|
|
Expanded(
|
|
child: Row(
|
|
mainAxisAlignment:
|
|
isLocal ? MainAxisAlignment.start : MainAxisAlignment.end,
|
|
children: [
|
|
IconButton(
|
|
onPressed: () {
|
|
final name = TextEditingController();
|
|
_ffi.dialogManager
|
|
.show((setState, close) => CustomAlertDialog(
|
|
title: Text(translate("Create Folder")),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
TextFormField(
|
|
decoration: InputDecoration(
|
|
labelText: translate(
|
|
"Please enter the folder name"),
|
|
),
|
|
controller: name,
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
style: flatButtonStyle,
|
|
onPressed: () => close(false),
|
|
child: Text(translate("Cancel"))),
|
|
ElevatedButton(
|
|
style: flatButtonStyle,
|
|
onPressed: () {
|
|
if (name.value.text.isNotEmpty) {
|
|
model.createDir(
|
|
PathUtil.join(
|
|
model
|
|
.getCurrentDir(
|
|
isLocal)
|
|
.path,
|
|
name.value.text,
|
|
model.getCurrentIsWindows(
|
|
isLocal)),
|
|
isLocal: isLocal);
|
|
close();
|
|
}
|
|
},
|
|
child: Text(translate("OK")))
|
|
]));
|
|
},
|
|
icon: Icon(Icons.create_new_folder_outlined)),
|
|
IconButton(
|
|
onPressed: () async {
|
|
final items = isLocal
|
|
? _localSelectedItems
|
|
: _remoteSelectedItems;
|
|
await (model.removeAction(items, isLocal: isLocal));
|
|
items.clear();
|
|
},
|
|
icon: Icon(Icons.delete_forever_outlined)),
|
|
],
|
|
),
|
|
),
|
|
TextButton.icon(
|
|
onPressed: () {
|
|
final items = getSelectedItem(isLocal);
|
|
model.sendFiles(items, isRemote: !isLocal);
|
|
items.clear();
|
|
},
|
|
icon: Transform.rotate(
|
|
angle: isLocal ? 0 : pi,
|
|
child: Icon(
|
|
Icons.send,
|
|
),
|
|
),
|
|
label: Text(
|
|
isLocal ? translate('Send') : translate('Receive'),
|
|
)),
|
|
],
|
|
).marginOnly(top: 8.0)
|
|
],
|
|
));
|
|
}
|
|
|
|
Widget listTail({bool isLocal = false}) {
|
|
final dir = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
|
return Container(
|
|
height: 100,
|
|
child: Column(
|
|
children: [
|
|
Padding(
|
|
padding: EdgeInsets.fromLTRB(30, 5, 30, 0),
|
|
child: Text(
|
|
dir.path,
|
|
style: TextStyle(color: MyTheme.darkGray),
|
|
),
|
|
),
|
|
Padding(
|
|
padding: EdgeInsets.all(2),
|
|
child: Text(
|
|
"${translate("Total")}: ${dir.entries.length} ${translate("items")}",
|
|
style: TextStyle(color: MyTheme.darkGray),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool get wantKeepAlive => true;
|
|
|
|
/// Get the image for the current [platform].
|
|
Widget getPlatformImage(String platform) {
|
|
platform = platform.toLowerCase();
|
|
if (platform == 'mac os')
|
|
platform = 'mac';
|
|
else if (platform != 'linux' && platform != 'android') platform = 'win';
|
|
return Image.asset('assets/$platform.png', width: 25, height: 25);
|
|
}
|
|
|
|
void onLocalLocationFocusChanged() {
|
|
debugPrint("focus changed on local");
|
|
if (_locationNodeLocal.hasFocus) {
|
|
// ignore
|
|
} else {
|
|
// lost focus, change to bread
|
|
_locationStatusLocal.value = LocationStatus.bread;
|
|
}
|
|
}
|
|
|
|
void onRemoteLocationFocusChanged() {
|
|
debugPrint("focus changed on remote");
|
|
if (_locationNodeRemote.hasFocus) {
|
|
// ignore
|
|
} else {
|
|
// lost focus, change to bread
|
|
_locationStatusRemote.value = LocationStatus.bread;
|
|
}
|
|
}
|
|
|
|
Widget buildBread(bool isLocal) {
|
|
final items = getPathBreadCrumbItems(isLocal, (list) {
|
|
var path = "";
|
|
for (var item in list) {
|
|
path = PathUtil.join(path, item, model.getCurrentIsWindows(isLocal));
|
|
}
|
|
openDirectory(path, isLocal: isLocal);
|
|
});
|
|
return items.isEmpty
|
|
? Offstage()
|
|
: BreadCrumb(
|
|
items: items,
|
|
divider: Text("/").paddingSymmetric(horizontal: 4.0),
|
|
overflow: ScrollableOverflow(
|
|
controller: getBreadCrumbScrollController(isLocal)),
|
|
);
|
|
}
|
|
|
|
List<BreadCrumbItem> getPathBreadCrumbItems(
|
|
bool isLocal, void Function(List<String>) onPressed) {
|
|
final path = model.getCurrentDir(isLocal).path;
|
|
final list = PathUtil.split(path, model.getCurrentIsWindows(isLocal));
|
|
final breadCrumbList = List<BreadCrumbItem>.empty(growable: true);
|
|
breadCrumbList.addAll(list.asMap().entries.map((e) => BreadCrumbItem(
|
|
content: TextButton(
|
|
child: Text(e.value),
|
|
style:
|
|
ButtonStyle(minimumSize: MaterialStateProperty.all(Size(0, 0))),
|
|
onPressed: () => onPressed(list.sublist(0, e.key + 1))))));
|
|
return breadCrumbList;
|
|
}
|
|
|
|
breadCrumbScrollToEnd(bool isLocal) {
|
|
Future.delayed(Duration(milliseconds: 200), () {
|
|
final _breadCrumbScroller = getBreadCrumbScrollController(isLocal);
|
|
_breadCrumbScroller.animateTo(
|
|
_breadCrumbScroller.position.maxScrollExtent,
|
|
duration: Duration(milliseconds: 200),
|
|
curve: Curves.fastLinearToSlowEaseIn);
|
|
});
|
|
}
|
|
|
|
Widget buildPathLocation(bool isLocal) {
|
|
return TextField(
|
|
focusNode: isLocal ? _locationNodeLocal : _locationNodeRemote,
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
isDense: true,
|
|
prefix: Padding(padding: EdgeInsets.only(left: 4.0)),
|
|
),
|
|
controller:
|
|
TextEditingController(text: model.getCurrentDir(isLocal).path),
|
|
onSubmitted: (path) {
|
|
openDirectory(path, isLocal: isLocal);
|
|
},
|
|
);
|
|
}
|
|
|
|
onSearchText(String searchText, bool isLocal) {
|
|
if (isLocal) {
|
|
_searchTextLocal.value = searchText;
|
|
} else {
|
|
_searchTextRemote.value = searchText;
|
|
}
|
|
}
|
|
|
|
openDirectory(String path, {bool isLocal = false}) {
|
|
model.openDirectory(path, isLocal: isLocal).then((_) {
|
|
print("scroll");
|
|
breadCrumbScrollToEnd(isLocal);
|
|
});
|
|
}
|
|
|
|
void handleDragDone(DropDoneDetails details, bool isLocal) {
|
|
if (isLocal) {
|
|
// ignore local
|
|
return;
|
|
}
|
|
var items = SelectedItems();
|
|
details.files.forEach((file) {
|
|
final f = File(file.path);
|
|
items.add(
|
|
true,
|
|
Entry()
|
|
..path = file.path
|
|
..name = file.name
|
|
..size =
|
|
FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync());
|
|
});
|
|
model.sendFiles(items, isRemote: false);
|
|
}
|
|
}
|