opt: dual selected items & send/receive action icon
This commit is contained in:
parent
0598ee304c
commit
1db7fee6fb
@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||
@ -23,7 +24,8 @@ class FileManagerPage extends StatefulWidget {
|
||||
|
||||
class _FileManagerPageState extends State<FileManagerPage>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final _selectedItems = SelectedItems();
|
||||
final _localSelectedItems = SelectedItems();
|
||||
final _remoteSelectedItems = SelectedItems();
|
||||
final _breadCrumbLocalScroller = ScrollController();
|
||||
final _breadCrumbRemoteScroller = ScrollController();
|
||||
|
||||
@ -32,6 +34,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
|
||||
FileModel get model => _ffi.fileModel;
|
||||
|
||||
SelectedItems getSelectedItem(bool isLocal) {
|
||||
return isLocal ? _localSelectedItems : _remoteSelectedItems;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -83,13 +89,6 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
}));
|
||||
}
|
||||
|
||||
bool needShowCheckBox() {
|
||||
if (!model.selectMode) {
|
||||
return false;
|
||||
}
|
||||
return !_selectedItems.isOtherPage(model.isLocal);
|
||||
}
|
||||
|
||||
Widget menu({bool isLocal = false}) {
|
||||
return PopupMenuButton<String>(
|
||||
icon: Icon(Icons.more_vert),
|
||||
@ -145,7 +144,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
if (v == "refresh") {
|
||||
model.refresh();
|
||||
} else if (v == "select") {
|
||||
_selectedItems.clear();
|
||||
_localSelectedItems.clear();
|
||||
model.toggleSelectMode();
|
||||
} else if (v == "folder") {
|
||||
final name = TextEditingController();
|
||||
@ -223,8 +222,16 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
return DataRow(
|
||||
key: ValueKey(entry.name),
|
||||
onSelectChanged: (s) {
|
||||
// TODO
|
||||
if (s != null) {
|
||||
if (s) {
|
||||
getSelectedItem(isLocal).add(isLocal, entry);
|
||||
} else {
|
||||
getSelectedItem(isLocal).remove(entry);
|
||||
}
|
||||
setState((){});
|
||||
}
|
||||
},
|
||||
selected: getSelectedItem(isLocal).contains(entry),
|
||||
cells: [
|
||||
// TODO: icon
|
||||
DataCell(Icon(
|
||||
@ -240,6 +247,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
model.openDirectory(entry.path, isLocal: isLocal);
|
||||
} else {
|
||||
// Perform file-related tasks.
|
||||
final _selectedItems = getSelectedItem(isLocal);
|
||||
if (_selectedItems.contains(entry)) {
|
||||
_selectedItems.remove(entry);
|
||||
} else {
|
||||
_selectedItems.add(isLocal, entry);
|
||||
}
|
||||
setState((){});
|
||||
}
|
||||
}),
|
||||
DataCell(Text(
|
||||
@ -377,6 +391,21 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
margin: const EdgeInsets.only(top: 16.0,bottom: 16.0, right: 16.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
decoration: BoxDecoration(color: Colors.white70,border: Border.all(color: Colors.grey)),
|
||||
child: Obx(
|
||||
() => ListView.builder(
|
||||
itemExtent: 100, itemBuilder: (BuildContext context, int index) {
|
||||
final item = model.jobTable[index + 1];
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Text('${item.id}'),
|
||||
Icon(Icons.delete)
|
||||
],
|
||||
);
|
||||
},
|
||||
itemCount: model.jobTable.length,
|
||||
),
|
||||
),
|
||||
),
|
||||
preferredSize: Size(200, double.infinity));
|
||||
}
|
||||
@ -398,29 +427,46 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
Widget headTools(bool isLocal) => Container(
|
||||
child: Row(
|
||||
children: [
|
||||
Offstage(
|
||||
offstage: isLocal,
|
||||
child: TextButton.icon(
|
||||
onPressed: (){}, icon: Transform.rotate(
|
||||
angle: isLocal ? 0 : pi,
|
||||
child: Icon(
|
||||
Icons.send
|
||||
),
|
||||
), label: Text(isLocal ? translate('Send') : translate('Receive'))),
|
||||
),
|
||||
Expanded(
|
||||
child: BreadCrumb(
|
||||
items: getPathBreadCrumbItems(() => model.goHome(), (list) {
|
||||
var path = "";
|
||||
if (model.currentHome.startsWith(list[0])) {
|
||||
// absolute path
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, model.currentIsWindows);
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.black12)
|
||||
),
|
||||
child: BreadCrumb(
|
||||
items: getPathBreadCrumbItems(() => model.goHome(isLocal: isLocal), (list) {
|
||||
var path = "";
|
||||
final currentHome = model.getCurrentHome(isLocal);
|
||||
final currentIsWindows = model.getCurrentIsWindows(isLocal);
|
||||
if (currentHome.startsWith(list[0])) {
|
||||
// absolute path
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, currentIsWindows);
|
||||
}
|
||||
} else {
|
||||
path += currentHome;
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, currentIsWindows);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
path += model.currentHome;
|
||||
for (var item in list) {
|
||||
path = PathUtil.join(path, item, model.currentIsWindows);
|
||||
}
|
||||
}
|
||||
model.openDirectory(path, isLocal: isLocal);
|
||||
model.openDirectory(path, isLocal: isLocal);
|
||||
}, isLocal),
|
||||
divider: Icon(Icons.chevron_right),
|
||||
overflow: ScrollableOverflow(
|
||||
controller: isLocal
|
||||
? _breadCrumbLocalScroller
|
||||
: _breadCrumbRemoteScroller),
|
||||
)),
|
||||
controller: isLocal
|
||||
? _breadCrumbLocalScroller
|
||||
: _breadCrumbRemoteScroller),
|
||||
),
|
||||
)),
|
||||
Row(
|
||||
children: [
|
||||
IconButton(
|
||||
@ -443,8 +489,18 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
onSelected: (sort) {
|
||||
model.changeSortStyle(sort, isLocal: isLocal);
|
||||
}),
|
||||
menu(isLocal: isLocal)
|
||||
menu(isLocal: isLocal),
|
||||
],
|
||||
),
|
||||
Offstage(
|
||||
offstage: !isLocal,
|
||||
child: TextButton.icon(
|
||||
onPressed: (){}, icon: Transform.rotate(
|
||||
angle: isLocal ? 0 : pi,
|
||||
child: Icon(
|
||||
Icons.send
|
||||
),
|
||||
), label: Text(isLocal ? translate('Send') : translate('Receive'))),
|
||||
)
|
||||
],
|
||||
));
|
||||
@ -476,14 +532,14 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
|
||||
Widget? bottomSheet() {
|
||||
final state = model.jobState;
|
||||
final isOtherPage = _selectedItems.isOtherPage(model.isLocal);
|
||||
final selectedItemsLen = "${_selectedItems.length} ${translate("items")}";
|
||||
final local = _selectedItems.isLocal == null
|
||||
final isOtherPage = _localSelectedItems.isOtherPage(model.isLocal);
|
||||
final selectedItemsLen = "${_localSelectedItems.length} ${translate("items")}";
|
||||
final local = _localSelectedItems.isLocal == null
|
||||
? ""
|
||||
: " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
|
||||
: " [${_localSelectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
|
||||
|
||||
if (model.selectMode) {
|
||||
if (_selectedItems.length == 0 || !isOtherPage) {
|
||||
if (_localSelectedItems.length == 0 || !isOtherPage) {
|
||||
return BottomSheetBody(
|
||||
leading: Icon(Icons.check),
|
||||
title: translate("Selected"),
|
||||
@ -497,8 +553,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
IconButton(
|
||||
icon: Icon(Icons.delete_forever),
|
||||
onPressed: () {
|
||||
if (_selectedItems.length > 0) {
|
||||
model.removeAction(_selectedItems);
|
||||
if (_localSelectedItems.length > 0) {
|
||||
model.removeAction(_localSelectedItems);
|
||||
}
|
||||
},
|
||||
)
|
||||
@ -518,7 +574,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
||||
icon: Icon(Icons.paste),
|
||||
onPressed: () {
|
||||
model.toggleSelectMode();
|
||||
model.sendFiles(_selectedItems);
|
||||
model.sendFiles(_localSelectedItems);
|
||||
},
|
||||
)
|
||||
]);
|
||||
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_hbb/common.dart';
|
||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:path/path.dart' as Path;
|
||||
|
||||
import 'model.dart';
|
||||
@ -22,6 +23,11 @@ class FileModel extends ChangeNotifier {
|
||||
|
||||
var _jobProgress = JobProgress(); // from rust update
|
||||
|
||||
/// JobTable <jobId, JobProgress>
|
||||
final _jobTable = List<JobProgress>.empty(growable: true).obs;
|
||||
|
||||
RxList<JobProgress> get jobTable => _jobTable;
|
||||
|
||||
bool get isLocal => _isLocal;
|
||||
|
||||
bool get selectMode => _selectMode;
|
||||
@ -46,6 +52,10 @@ class FileModel extends ChangeNotifier {
|
||||
|
||||
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
|
||||
|
||||
String getCurrentHome(bool isLocal) {
|
||||
return isLocal ? _localOption.home : _remoteOption.home;
|
||||
}
|
||||
|
||||
String get currentShortPath {
|
||||
if (currentDir.path.startsWith(currentHome)) {
|
||||
var path = currentDir.path.replaceFirst(currentHome, "");
|
||||
@ -81,6 +91,10 @@ class FileModel extends ChangeNotifier {
|
||||
bool get currentIsWindows =>
|
||||
_isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
||||
|
||||
bool getCurrentIsWindows(bool isLocal) {
|
||||
return isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
||||
}
|
||||
|
||||
final _fileFetcher = FileFetcher();
|
||||
|
||||
final _jobResultListener = JobResultListener<Map<String, dynamic>>();
|
||||
@ -115,10 +129,20 @@ class FileModel extends ChangeNotifier {
|
||||
tryUpdateJobProgress(Map<String, dynamic> evt) {
|
||||
try {
|
||||
int id = int.parse(evt['id']);
|
||||
_jobProgress.id = id;
|
||||
_jobProgress.fileNum = int.parse(evt['file_num']);
|
||||
_jobProgress.speed = double.parse(evt['speed']);
|
||||
_jobProgress.finishedSize = int.parse(evt['finished_size']);
|
||||
if (!isDesktop) {
|
||||
_jobProgress.id = id;
|
||||
_jobProgress.fileNum = int.parse(evt['file_num']);
|
||||
_jobProgress.speed = double.parse(evt['speed']);
|
||||
_jobProgress.finishedSize = int.parse(evt['finished_size']);
|
||||
} else {
|
||||
// Desktop uses jobTable
|
||||
final job = _jobTable[id];
|
||||
if (job != null) {
|
||||
job.fileNum = int.parse(evt['file_num']);
|
||||
job.speed = double.parse(evt['speed']);
|
||||
job.finishedSize = int.parse(evt['finished_size']);
|
||||
}
|
||||
}
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}");
|
||||
@ -270,12 +294,12 @@ class FileModel extends ChangeNotifier {
|
||||
}
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
debugPrint("Failed to openDirectory :$e");
|
||||
debugPrint("Failed to openDirectory ${path} :$e");
|
||||
}
|
||||
}
|
||||
|
||||
goHome() {
|
||||
openDirectory(currentHome);
|
||||
goHome({bool? isLocal}) {
|
||||
openDirectory(currentHome, isLocal: isLocal);
|
||||
}
|
||||
|
||||
goToParentDirectory({bool? isLocal}) {
|
||||
@ -303,7 +327,10 @@ class FileModel extends ChangeNotifier {
|
||||
final showHidden =
|
||||
isRemote ? _localOption.showHidden : _remoteOption.showHidden ;
|
||||
items.items.forEach((from) async {
|
||||
_jobId++;
|
||||
final jobId = ++_jobId;
|
||||
_jobTable[jobId] = JobProgress()
|
||||
..state = JobState.inProgress
|
||||
..id = jobId;
|
||||
await _ffi.target?.bind.sessionSendFiles(id: '${_ffi.target?.id}', actId: _jobId, path: from.path, to: PathUtil.join(toPath, from.name, isWindows)
|
||||
,fileNum: 0, includeHidden: showHidden, isRemote: isRemote);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user