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:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
import 'package:flutter_breadcrumb/flutter_breadcrumb.dart';
|
||||||
@ -23,7 +24,8 @@ class FileManagerPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _FileManagerPageState extends State<FileManagerPage>
|
class _FileManagerPageState extends State<FileManagerPage>
|
||||||
with AutomaticKeepAliveClientMixin {
|
with AutomaticKeepAliveClientMixin {
|
||||||
final _selectedItems = SelectedItems();
|
final _localSelectedItems = SelectedItems();
|
||||||
|
final _remoteSelectedItems = SelectedItems();
|
||||||
final _breadCrumbLocalScroller = ScrollController();
|
final _breadCrumbLocalScroller = ScrollController();
|
||||||
final _breadCrumbRemoteScroller = ScrollController();
|
final _breadCrumbRemoteScroller = ScrollController();
|
||||||
|
|
||||||
@ -32,6 +34,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
|
|
||||||
FileModel get model => _ffi.fileModel;
|
FileModel get model => _ffi.fileModel;
|
||||||
|
|
||||||
|
SelectedItems getSelectedItem(bool isLocal) {
|
||||||
|
return isLocal ? _localSelectedItems : _remoteSelectedItems;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.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}) {
|
Widget menu({bool isLocal = false}) {
|
||||||
return PopupMenuButton<String>(
|
return PopupMenuButton<String>(
|
||||||
icon: Icon(Icons.more_vert),
|
icon: Icon(Icons.more_vert),
|
||||||
@ -145,7 +144,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
if (v == "refresh") {
|
if (v == "refresh") {
|
||||||
model.refresh();
|
model.refresh();
|
||||||
} else if (v == "select") {
|
} else if (v == "select") {
|
||||||
_selectedItems.clear();
|
_localSelectedItems.clear();
|
||||||
model.toggleSelectMode();
|
model.toggleSelectMode();
|
||||||
} else if (v == "folder") {
|
} else if (v == "folder") {
|
||||||
final name = TextEditingController();
|
final name = TextEditingController();
|
||||||
@ -223,8 +222,16 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return DataRow(
|
return DataRow(
|
||||||
key: ValueKey(entry.name),
|
key: ValueKey(entry.name),
|
||||||
onSelectChanged: (s) {
|
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: [
|
cells: [
|
||||||
// TODO: icon
|
// TODO: icon
|
||||||
DataCell(Icon(
|
DataCell(Icon(
|
||||||
@ -240,6 +247,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
model.openDirectory(entry.path, isLocal: isLocal);
|
model.openDirectory(entry.path, isLocal: isLocal);
|
||||||
} else {
|
} else {
|
||||||
// Perform file-related tasks.
|
// Perform file-related tasks.
|
||||||
|
final _selectedItems = getSelectedItem(isLocal);
|
||||||
|
if (_selectedItems.contains(entry)) {
|
||||||
|
_selectedItems.remove(entry);
|
||||||
|
} else {
|
||||||
|
_selectedItems.add(isLocal, entry);
|
||||||
|
}
|
||||||
|
setState((){});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
DataCell(Text(
|
DataCell(Text(
|
||||||
@ -377,6 +391,21 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
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(color: Colors.white70,border: Border.all(color: Colors.grey)),
|
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));
|
preferredSize: Size(200, double.infinity));
|
||||||
}
|
}
|
||||||
@ -398,29 +427,46 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Widget headTools(bool isLocal) => Container(
|
Widget headTools(bool isLocal) => Container(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
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(
|
Expanded(
|
||||||
child: BreadCrumb(
|
child: Container(
|
||||||
items: getPathBreadCrumbItems(() => model.goHome(), (list) {
|
decoration: BoxDecoration(
|
||||||
var path = "";
|
border: Border.all(color: Colors.black12)
|
||||||
if (model.currentHome.startsWith(list[0])) {
|
),
|
||||||
// absolute path
|
child: BreadCrumb(
|
||||||
for (var item in list) {
|
items: getPathBreadCrumbItems(() => model.goHome(isLocal: isLocal), (list) {
|
||||||
path = PathUtil.join(path, item, model.currentIsWindows);
|
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 {
|
model.openDirectory(path, isLocal: isLocal);
|
||||||
path += model.currentHome;
|
|
||||||
for (var item in list) {
|
|
||||||
path = PathUtil.join(path, item, model.currentIsWindows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
model.openDirectory(path, isLocal: isLocal);
|
|
||||||
}, isLocal),
|
}, isLocal),
|
||||||
divider: Icon(Icons.chevron_right),
|
divider: Icon(Icons.chevron_right),
|
||||||
overflow: ScrollableOverflow(
|
overflow: ScrollableOverflow(
|
||||||
controller: isLocal
|
controller: isLocal
|
||||||
? _breadCrumbLocalScroller
|
? _breadCrumbLocalScroller
|
||||||
: _breadCrumbRemoteScroller),
|
: _breadCrumbRemoteScroller),
|
||||||
)),
|
),
|
||||||
|
)),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
@ -443,8 +489,18 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
onSelected: (sort) {
|
onSelected: (sort) {
|
||||||
model.changeSortStyle(sort, isLocal: isLocal);
|
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() {
|
Widget? bottomSheet() {
|
||||||
final state = model.jobState;
|
final state = model.jobState;
|
||||||
final isOtherPage = _selectedItems.isOtherPage(model.isLocal);
|
final isOtherPage = _localSelectedItems.isOtherPage(model.isLocal);
|
||||||
final selectedItemsLen = "${_selectedItems.length} ${translate("items")}";
|
final selectedItemsLen = "${_localSelectedItems.length} ${translate("items")}";
|
||||||
final local = _selectedItems.isLocal == null
|
final local = _localSelectedItems.isLocal == null
|
||||||
? ""
|
? ""
|
||||||
: " [${_selectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
|
: " [${_localSelectedItems.isLocal! ? translate("Local") : translate("Remote")}]";
|
||||||
|
|
||||||
if (model.selectMode) {
|
if (model.selectMode) {
|
||||||
if (_selectedItems.length == 0 || !isOtherPage) {
|
if (_localSelectedItems.length == 0 || !isOtherPage) {
|
||||||
return BottomSheetBody(
|
return BottomSheetBody(
|
||||||
leading: Icon(Icons.check),
|
leading: Icon(Icons.check),
|
||||||
title: translate("Selected"),
|
title: translate("Selected"),
|
||||||
@ -497,8 +553,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.delete_forever),
|
icon: Icon(Icons.delete_forever),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_selectedItems.length > 0) {
|
if (_localSelectedItems.length > 0) {
|
||||||
model.removeAction(_selectedItems);
|
model.removeAction(_localSelectedItems);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -518,7 +574,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
icon: Icon(Icons.paste),
|
icon: Icon(Icons.paste),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.toggleSelectMode();
|
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/common.dart';
|
||||||
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
import 'package:flutter_hbb/mobile/pages/file_manager_page.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:path/path.dart' as Path;
|
import 'package:path/path.dart' as Path;
|
||||||
|
|
||||||
import 'model.dart';
|
import 'model.dart';
|
||||||
@ -22,6 +23,11 @@ class FileModel extends ChangeNotifier {
|
|||||||
|
|
||||||
var _jobProgress = JobProgress(); // from rust update
|
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 isLocal => _isLocal;
|
||||||
|
|
||||||
bool get selectMode => _selectMode;
|
bool get selectMode => _selectMode;
|
||||||
@ -46,6 +52,10 @@ class FileModel extends ChangeNotifier {
|
|||||||
|
|
||||||
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
|
String get currentHome => _isLocal ? _localOption.home : _remoteOption.home;
|
||||||
|
|
||||||
|
String getCurrentHome(bool isLocal) {
|
||||||
|
return isLocal ? _localOption.home : _remoteOption.home;
|
||||||
|
}
|
||||||
|
|
||||||
String get currentShortPath {
|
String get currentShortPath {
|
||||||
if (currentDir.path.startsWith(currentHome)) {
|
if (currentDir.path.startsWith(currentHome)) {
|
||||||
var path = currentDir.path.replaceFirst(currentHome, "");
|
var path = currentDir.path.replaceFirst(currentHome, "");
|
||||||
@ -81,6 +91,10 @@ class FileModel extends ChangeNotifier {
|
|||||||
bool get currentIsWindows =>
|
bool get currentIsWindows =>
|
||||||
_isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
_isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
||||||
|
|
||||||
|
bool getCurrentIsWindows(bool isLocal) {
|
||||||
|
return isLocal ? _localOption.isWindows : _remoteOption.isWindows;
|
||||||
|
}
|
||||||
|
|
||||||
final _fileFetcher = FileFetcher();
|
final _fileFetcher = FileFetcher();
|
||||||
|
|
||||||
final _jobResultListener = JobResultListener<Map<String, dynamic>>();
|
final _jobResultListener = JobResultListener<Map<String, dynamic>>();
|
||||||
@ -115,10 +129,20 @@ class FileModel extends ChangeNotifier {
|
|||||||
tryUpdateJobProgress(Map<String, dynamic> evt) {
|
tryUpdateJobProgress(Map<String, dynamic> evt) {
|
||||||
try {
|
try {
|
||||||
int id = int.parse(evt['id']);
|
int id = int.parse(evt['id']);
|
||||||
_jobProgress.id = id;
|
if (!isDesktop) {
|
||||||
_jobProgress.fileNum = int.parse(evt['file_num']);
|
_jobProgress.id = id;
|
||||||
_jobProgress.speed = double.parse(evt['speed']);
|
_jobProgress.fileNum = int.parse(evt['file_num']);
|
||||||
_jobProgress.finishedSize = int.parse(evt['finished_size']);
|
_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();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}");
|
debugPrint("Failed to tryUpdateJobProgress,evt:${evt.toString()}");
|
||||||
@ -270,12 +294,12 @@ class FileModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugPrint("Failed to openDirectory :$e");
|
debugPrint("Failed to openDirectory ${path} :$e");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goHome() {
|
goHome({bool? isLocal}) {
|
||||||
openDirectory(currentHome);
|
openDirectory(currentHome, isLocal: isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
goToParentDirectory({bool? isLocal}) {
|
goToParentDirectory({bool? isLocal}) {
|
||||||
@ -303,7 +327,10 @@ class FileModel extends ChangeNotifier {
|
|||||||
final showHidden =
|
final showHidden =
|
||||||
isRemote ? _localOption.showHidden : _remoteOption.showHidden ;
|
isRemote ? _localOption.showHidden : _remoteOption.showHidden ;
|
||||||
items.items.forEach((from) async {
|
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)
|
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);
|
,fileNum: 0, includeHidden: showHidden, isRemote: isRemote);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user