Merge pull request #1286 from Kingtous/flutter_desktop
feat: file transfer selectable navigation tools & search bar
This commit is contained in:
commit
f797125ae2
@ -1,7 +1,9 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:desktop_drop/desktop_drop.dart';
|
||||||
import 'package:flutter/material.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/mobile/pages/file_manager_page.dart';
|
||||||
import 'package:flutter_hbb/models/file_model.dart';
|
import 'package:flutter_hbb/models/file_model.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -12,6 +14,8 @@ import '../../common.dart';
|
|||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
|
||||||
|
enum LocationStatus { bread, textField }
|
||||||
|
|
||||||
class FileManagerPage extends StatefulWidget {
|
class FileManagerPage extends StatefulWidget {
|
||||||
FileManagerPage({Key? key, required this.id}) : super(key: key);
|
FileManagerPage({Key? key, required this.id}) : super(key: key);
|
||||||
final String id;
|
final String id;
|
||||||
@ -25,6 +29,23 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
final _localSelectedItems = SelectedItems();
|
final _localSelectedItems = SelectedItems();
|
||||||
final _remoteSelectedItems = 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;
|
late FFI _ffi;
|
||||||
|
|
||||||
FileModel get model => _ffi.fileModel;
|
FileModel get model => _ffi.fileModel;
|
||||||
@ -44,6 +65,9 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Wakelock.enable();
|
Wakelock.enable();
|
||||||
}
|
}
|
||||||
print("init success with id ${widget.id}");
|
print("init success with id ${widget.id}");
|
||||||
|
// register location listener
|
||||||
|
_locationNodeLocal.addListener(onLocalLocationFocusChanged);
|
||||||
|
_locationNodeRemote.addListener(onRemoteLocationFocusChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -55,6 +79,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Wakelock.disable();
|
Wakelock.disable();
|
||||||
}
|
}
|
||||||
Get.delete<FFI>(tag: 'ft_${widget.id}');
|
Get.delete<FFI>(tag: 'ft_${widget.id}');
|
||||||
|
_locationNodeLocal.removeListener(onLocalLocationFocusChanged);
|
||||||
|
_locationNodeRemote.removeListener(onRemoteLocationFocusChanged);
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +138,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget body({bool isLocal = false}) {
|
Widget body({bool isLocal = false}) {
|
||||||
final fd = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
final fd = model.getCurrentDir(isLocal);
|
||||||
final entries = fd.entries;
|
final entries = fd.entries;
|
||||||
final sortIndex = (SortBy style) {
|
final sortIndex = (SortBy style) {
|
||||||
switch (style) {
|
switch (style) {
|
||||||
@ -129,10 +155,17 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
final sortAscending =
|
final sortAscending =
|
||||||
isLocal ? model.localSortAscending : model.remoteSortAscending;
|
isLocal ? model.localSortAscending : model.remoteSortAscending;
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(border: Border.all(color: Colors.black26)),
|
||||||
color: Colors.white54, border: Border.all(color: Colors.black26)),
|
|
||||||
margin: const EdgeInsets.all(16.0),
|
margin: const EdgeInsets.all(16.0),
|
||||||
padding: const EdgeInsets.all(8.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: [
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
headTools(isLocal),
|
headTools(isLocal),
|
||||||
Expanded(
|
Expanded(
|
||||||
@ -141,7 +174,19 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: DataTable(
|
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,
|
showCheckboxColumn: true,
|
||||||
dataRowHeight: 25,
|
dataRowHeight: 25,
|
||||||
headingRowHeight: 30,
|
headingRowHeight: 30,
|
||||||
@ -174,7 +219,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
isLocal: isLocal, ascending: ascending);
|
isLocal: isLocal, ascending: ascending);
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
rows: entries.map((entry) {
|
rows: filteredEntries.map((entry) {
|
||||||
final sizeStr = entry.isFile
|
final sizeStr = entry.isFile
|
||||||
? readableFileSize(entry.size.toDouble())
|
? readableFileSize(entry.size.toDouble())
|
||||||
: "";
|
: "";
|
||||||
@ -183,28 +228,33 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
onSelectChanged: (s) {
|
onSelectChanged: (s) {
|
||||||
if (s != null) {
|
if (s != null) {
|
||||||
if (s) {
|
if (s) {
|
||||||
getSelectedItem(isLocal).add(isLocal, entry);
|
getSelectedItem(isLocal)
|
||||||
|
.add(isLocal, entry);
|
||||||
} else {
|
} else {
|
||||||
getSelectedItem(isLocal).remove(entry);
|
getSelectedItem(isLocal).remove(entry);
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selected: getSelectedItem(isLocal).contains(entry),
|
selected:
|
||||||
|
getSelectedItem(isLocal).contains(entry),
|
||||||
cells: [
|
cells: [
|
||||||
DataCell(Icon(
|
DataCell(Icon(
|
||||||
entry.isFile ? Icons.feed_outlined : Icons.folder,
|
entry.isFile
|
||||||
|
? Icons.feed_outlined
|
||||||
|
: Icons.folder,
|
||||||
size: 25)),
|
size: 25)),
|
||||||
DataCell(
|
DataCell(
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: 100),
|
constraints:
|
||||||
|
BoxConstraints(maxWidth: 100),
|
||||||
child: Tooltip(
|
child: Tooltip(
|
||||||
message: entry.name,
|
message: entry.name,
|
||||||
child: Text(entry.name,
|
child: Text(entry.name,
|
||||||
overflow: TextOverflow.ellipsis),
|
overflow: TextOverflow.ellipsis),
|
||||||
)), onTap: () {
|
)), onTap: () {
|
||||||
if (entry.isDirectory) {
|
if (entry.isDirectory) {
|
||||||
model.openDirectory(entry.path, isLocal: isLocal);
|
openDirectory(entry.path, isLocal: isLocal);
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
_localSelectedItems.clear();
|
_localSelectedItems.clear();
|
||||||
} else {
|
} else {
|
||||||
@ -212,7 +262,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Perform file-related tasks.
|
// Perform file-related tasks.
|
||||||
final _selectedItems = getSelectedItem(isLocal);
|
final _selectedItems =
|
||||||
|
getSelectedItem(isLocal);
|
||||||
if (_selectedItems.contains(entry)) {
|
if (_selectedItems.contains(entry)) {
|
||||||
_selectedItems.remove(entry);
|
_selectedItems.remove(entry);
|
||||||
} else {
|
} else {
|
||||||
@ -236,7 +287,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
fontSize: 12, color: MyTheme.darkGray),
|
fontSize: 12, color: MyTheme.darkGray),
|
||||||
)),
|
)),
|
||||||
]);
|
]);
|
||||||
}).toList(),
|
}).toList(growable: false),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
isLocal ? _searchTextLocal : _searchTextRemote,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -326,7 +380,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
// return;
|
// return;
|
||||||
// }
|
// }
|
||||||
// if (entries[index].isDirectory) {
|
// if (entries[index].isDirectory) {
|
||||||
// model.openDirectory(entries[index].path, isLocal: isLocal);
|
// openDirectory(entries[index].path, isLocal: isLocal);
|
||||||
// breadCrumbScrollToEnd(isLocal);
|
// breadCrumbScrollToEnd(isLocal);
|
||||||
// } else {
|
// } else {
|
||||||
// // Perform file-related tasks.
|
// // Perform file-related tasks.
|
||||||
@ -345,6 +399,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
// },
|
// },
|
||||||
// ))
|
// ))
|
||||||
]),
|
]),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,8 +410,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
|
margin: const EdgeInsets.only(top: 16.0, bottom: 16.0, right: 16.0),
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(border: Border.all(color: Colors.grey)),
|
||||||
color: Colors.white70, border: Border.all(color: Colors.grey)),
|
|
||||||
child: Obx(
|
child: Obx(
|
||||||
() => ListView.builder(
|
() => ListView.builder(
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
@ -383,7 +437,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
child: Text(
|
child: Text(
|
||||||
'${item.jobName}',
|
'${item.jobName}',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
style: TextStyle(color: Colors.black45),
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
)),
|
)),
|
||||||
Wrap(
|
Wrap(
|
||||||
@ -449,7 +502,12 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
model.goToParentDirectory(isLocal: isLocal);
|
model.goToParentDirectory(isLocal: isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget headTools(bool isLocal) => Container(
|
Widget headTools(bool isLocal) {
|
||||||
|
final _locationStatus =
|
||||||
|
isLocal ? _locationStatusLocal : _locationStatusRemote;
|
||||||
|
final _locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote;
|
||||||
|
final _searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
|
||||||
|
return Container(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
// symbols
|
// symbols
|
||||||
@ -501,16 +559,29 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
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(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration:
|
||||||
border: Border.all(color: Colors.black12)),
|
BoxDecoration(border: Border.all(color: Colors.black12)),
|
||||||
child: TextField(
|
child: Row(
|
||||||
decoration: InputDecoration(
|
children: [
|
||||||
border: InputBorder.none,
|
Expanded(
|
||||||
isDense: true,
|
child: Obx(() =>
|
||||||
prefix:
|
_locationStatus.value == LocationStatus.bread
|
||||||
Padding(padding: EdgeInsets.only(left: 4.0)),
|
? buildBread(isLocal)
|
||||||
suffix: DropdownButton<String>(
|
: buildPathLocation(isLocal))),
|
||||||
|
DropdownButton<String>(
|
||||||
isDense: true,
|
isDense: true,
|
||||||
underline: Offstage(),
|
underline: Offstage(),
|
||||||
items: [
|
items: [
|
||||||
@ -522,22 +593,36 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
],
|
],
|
||||||
onChanged: (path) {
|
onChanged: (path) {
|
||||||
if (path is String && path.isNotEmpty) {
|
if (path is String && path.isNotEmpty) {
|
||||||
model.openDirectory(path, isLocal: isLocal);
|
openDirectory(path, isLocal: isLocal);
|
||||||
}
|
}
|
||||||
})),
|
})
|
||||||
controller: TextEditingController(
|
],
|
||||||
text: isLocal
|
)),
|
||||||
? model.currentLocalDir.path
|
)),
|
||||||
: model.currentRemoteDir.path),
|
PopupMenuButton(
|
||||||
onSubmitted: (path) {
|
itemBuilder: (context) => [
|
||||||
model.openDirectory(path, isLocal: isLocal);
|
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(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.refresh(isLocal: isLocal);
|
model.refresh(isLocal: isLocal);
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.refresh))
|
icon: Icon(Icons.refresh)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
@ -551,8 +636,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final name = TextEditingController();
|
final name = TextEditingController();
|
||||||
_ffi.dialogManager.show((setState, close) =>
|
_ffi.dialogManager
|
||||||
CustomAlertDialog(
|
.show((setState, close) => CustomAlertDialog(
|
||||||
title: Text(translate("Create Folder")),
|
title: Text(translate("Create Folder")),
|
||||||
content: Column(
|
content: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
@ -578,7 +663,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
model.createDir(
|
model.createDir(
|
||||||
PathUtil.join(
|
PathUtil.join(
|
||||||
model
|
model
|
||||||
.getCurrentDir(isLocal)
|
.getCurrentDir(
|
||||||
|
isLocal)
|
||||||
.path,
|
.path,
|
||||||
name.value.text,
|
name.value.text,
|
||||||
model.getCurrentIsWindows(
|
model.getCurrentIsWindows(
|
||||||
@ -613,19 +699,16 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
angle: isLocal ? 0 : pi,
|
angle: isLocal ? 0 : pi,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.send,
|
Icons.send,
|
||||||
color: Colors.black54,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
label: Text(
|
label: Text(
|
||||||
isLocal ? translate('Send') : translate('Receive'),
|
isLocal ? translate('Send') : translate('Receive'),
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.black54,
|
|
||||||
),
|
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
).marginOnly(top: 8.0)
|
).marginOnly(top: 8.0)
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Widget listTail({bool isLocal = false}) {
|
Widget listTail({bool isLocal = false}) {
|
||||||
final dir = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
final dir = isLocal ? model.currentLocalDir : model.currentRemoteDir;
|
||||||
@ -663,4 +746,116 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
else if (platform != 'linux' && platform != 'android') platform = 'win';
|
else if (platform != 'linux' && platform != 'android') platform = 'win';
|
||||||
return Image.asset('assets/$platform.png', width: 25, height: 25);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -68,6 +68,22 @@ class FileModel extends ChangeNotifier {
|
|||||||
return isLocal ? currentLocalDir : currentRemoteDir;
|
return isLocal ? currentLocalDir : currentRemoteDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String getCurrentShortPath(bool isLocal) {
|
||||||
|
final currentDir = getCurrentDir(isLocal);
|
||||||
|
final currentHome = getCurrentHome(isLocal);
|
||||||
|
if (currentDir.path.startsWith(currentHome)) {
|
||||||
|
var path = currentDir.path.replaceFirst(currentHome, "");
|
||||||
|
if (path.length == 0) return "";
|
||||||
|
if (path[0] == "/" || path[0] == "\\") {
|
||||||
|
// remove more '/' or '\'
|
||||||
|
path = path.replaceFirst(path[0], "");
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
} else {
|
||||||
|
return currentDir.path.replaceFirst(currentHome, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
|
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
|
||||||
|
|
||||||
String getCurrentHome(bool isLocal) {
|
String getCurrentHome(bool isLocal) {
|
||||||
@ -716,6 +732,7 @@ class FileModel extends ChangeNotifier {
|
|||||||
job.totalSize = total_size.toInt();
|
job.totalSize = total_size.toInt();
|
||||||
}
|
}
|
||||||
debugPrint("update folder files: ${info}");
|
debugPrint("update folder files: ${info}");
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get remoteSortAscending => _remoteSortAscending;
|
bool get remoteSortAscending => _remoteSortAscending;
|
||||||
|
@ -239,6 +239,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.12"
|
version: "0.0.12"
|
||||||
|
desktop_drop:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: desktop_drop
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3"
|
||||||
desktop_multi_window:
|
desktop_multi_window:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -817,7 +824,7 @@ packages:
|
|||||||
name: qr_code_scanner
|
name: qr_code_scanner
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.0"
|
version: "1.0.1"
|
||||||
quiver:
|
quiver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -69,6 +69,7 @@ dependencies:
|
|||||||
get: ^4.6.5
|
get: ^4.6.5
|
||||||
visibility_detector: ^0.3.3
|
visibility_detector: ^0.3.3
|
||||||
contextmenu: ^3.0.0
|
contextmenu: ^3.0.0
|
||||||
|
desktop_drop: ^0.3.3
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.9.1
|
flutter_launcher_icons: ^0.9.1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user