Merge pull request #1719 from Heap-Hop/opt/file_transfer
opt file transfer
This commit is contained in:
commit
85b4758d93
@ -12,13 +12,6 @@ import '../../models/platform_model.dart';
|
|||||||
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
||||||
import '../../desktop/widgets/popup_menu.dart';
|
import '../../desktop/widgets/popup_menu.dart';
|
||||||
|
|
||||||
class CustomPopupMenuTheme {
|
|
||||||
static const Color commonColor = MyTheme.accent;
|
|
||||||
// kMinInteractiveDimension
|
|
||||||
static const double height = 20.0;
|
|
||||||
static const double dividerHeight = 3.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
typedef PopupMenuEntryBuilder = Future<List<mod_menu.PopupMenuEntry<String>>>
|
||||||
Function(BuildContext);
|
Function(BuildContext);
|
||||||
|
|
||||||
|
@ -232,11 +232,11 @@ class _GeneralState extends State<_General> {
|
|||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
children: [
|
children: [
|
||||||
theme(),
|
theme(),
|
||||||
abr(),
|
|
||||||
hwcodec(),
|
hwcodec(),
|
||||||
audio(context),
|
audio(context),
|
||||||
record(context),
|
record(context),
|
||||||
_Card(title: 'Language', children: [language()]),
|
_Card(title: 'Language', children: [language()]),
|
||||||
|
other()
|
||||||
],
|
],
|
||||||
).marginOnly(bottom: _kListViewBottomMargin));
|
).marginOnly(bottom: _kListViewBottomMargin));
|
||||||
}
|
}
|
||||||
@ -267,8 +267,10 @@ class _GeneralState extends State<_General> {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget abr() {
|
Widget other() {
|
||||||
return _Card(title: 'Adaptive Bitrate', children: [
|
return _Card(title: 'Other', children: [
|
||||||
|
_OptionCheckBox(context, 'Confirm before closing multiple tabs',
|
||||||
|
'enable-confirm-closing-tabs'),
|
||||||
_OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'),
|
_OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,25 @@ import 'package:flutter_hbb/models/file_model.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
import '../../consts.dart';
|
||||||
|
import '../../desktop/widgets/material_mod_popup_menu.dart' as mod_menu;
|
||||||
|
|
||||||
import '../../common.dart';
|
import '../../common.dart';
|
||||||
import '../../models/model.dart';
|
import '../../models/model.dart';
|
||||||
import '../../models/platform_model.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
import '../widgets/popup_menu.dart';
|
||||||
|
|
||||||
enum LocationStatus { bread, textField }
|
/// status of location bar
|
||||||
|
enum LocationStatus {
|
||||||
|
/// normal bread crumb bar
|
||||||
|
bread,
|
||||||
|
|
||||||
|
/// show path text field
|
||||||
|
pathLocation,
|
||||||
|
|
||||||
|
/// show file search bar text field
|
||||||
|
fileSearchBar
|
||||||
|
}
|
||||||
|
|
||||||
class FileManagerPage extends StatefulWidget {
|
class FileManagerPage extends StatefulWidget {
|
||||||
const FileManagerPage({Key? key, required this.id}) : super(key: key);
|
const FileManagerPage({Key? key, required this.id}) : super(key: key);
|
||||||
@ -40,7 +53,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
final _breadCrumbScrollerLocal = ScrollController();
|
final _breadCrumbScrollerLocal = ScrollController();
|
||||||
final _breadCrumbScrollerRemote = ScrollController();
|
final _breadCrumbScrollerRemote = ScrollController();
|
||||||
|
|
||||||
final _dropMaskVisible = false.obs;
|
final _dropMaskVisible = false.obs; // TODO impl drop mask
|
||||||
|
|
||||||
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
ScrollController getBreadCrumbScrollController(bool isLocal) {
|
||||||
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
|
return isLocal ? _breadCrumbScrollerLocal : _breadCrumbScrollerRemote;
|
||||||
@ -84,6 +97,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Get.delete<FFI>(tag: 'ft_${widget.id}');
|
Get.delete<FFI>(tag: 'ft_${widget.id}');
|
||||||
_locationNodeLocal.removeListener(onLocalLocationFocusChanged);
|
_locationNodeLocal.removeListener(onLocalLocationFocusChanged);
|
||||||
_locationNodeRemote.removeListener(onRemoteLocationFocusChanged);
|
_locationNodeRemote.removeListener(onRemoteLocationFocusChanged);
|
||||||
|
_locationNodeLocal.dispose();
|
||||||
|
_locationNodeRemote.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,15 +110,8 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
_ffi.dialogManager.setOverlayState(Overlay.of(context));
|
_ffi.dialogManager.setOverlayState(Overlay.of(context));
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: _ffi.fileModel,
|
value: _ffi.fileModel,
|
||||||
child: Consumer<FileModel>(builder: (_context, _model, _child) {
|
child: Consumer<FileModel>(builder: (context, model, child) {
|
||||||
return WillPopScope(
|
return Scaffold(
|
||||||
onWillPop: () async {
|
|
||||||
if (model.selectMode) {
|
|
||||||
model.toggleSelectMode();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
child: Scaffold(
|
|
||||||
backgroundColor: Theme.of(context).backgroundColor,
|
backgroundColor: Theme.of(context).backgroundColor,
|
||||||
body: Row(
|
body: Row(
|
||||||
children: [
|
children: [
|
||||||
@ -112,39 +120,54 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
Flexible(flex: 2, child: statusList())
|
Flexible(flex: 2, child: statusList())
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
);
|
||||||
}));
|
}));
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget menu({bool isLocal = false}) {
|
Widget menu({bool isLocal = false}) {
|
||||||
return PopupMenuButton<String>(
|
var menuPos = RelativeRect.fill;
|
||||||
|
|
||||||
|
final items = [
|
||||||
|
MenuEntrySwitch<String>(
|
||||||
|
switchType: SwitchType.scheckbox,
|
||||||
|
text: translate("Show Hidden Files"),
|
||||||
|
getter: () async {
|
||||||
|
return model.getCurrentShowHidden(isLocal);
|
||||||
|
},
|
||||||
|
setter: (bool v) async {
|
||||||
|
model.toggleShowHidden(local: isLocal);
|
||||||
|
},
|
||||||
|
padding: kDesktopMenuPadding,
|
||||||
|
dismissOnClicked: true,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Listener(
|
||||||
|
onPointerDown: (e) {
|
||||||
|
final x = e.position.dx;
|
||||||
|
final y = e.position.dy;
|
||||||
|
menuPos = RelativeRect.fromLTRB(x, y, x, y);
|
||||||
|
},
|
||||||
|
child: IconButton(
|
||||||
icon: const Icon(Icons.more_vert),
|
icon: const Icon(Icons.more_vert),
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
itemBuilder: (context) {
|
onPressed: () => mod_menu.showMenu(
|
||||||
return [
|
context: context,
|
||||||
PopupMenuItem(
|
position: menuPos,
|
||||||
child: Row(
|
items: items
|
||||||
children: [
|
.map((e) => e.build(
|
||||||
Icon(
|
context,
|
||||||
model.getCurrentShowHidden(isLocal)
|
MenuConfig(
|
||||||
? Icons.check_box_outlined
|
commonColor: CustomPopupMenuTheme.commonColor,
|
||||||
: Icons.check_box_outline_blank,
|
height: CustomPopupMenuTheme.height,
|
||||||
color: Colors.black),
|
dividerHeight: CustomPopupMenuTheme.dividerHeight)))
|
||||||
SizedBox(width: 5),
|
.expand((i) => i)
|
||||||
Text(translate("Show Hidden Files"))
|
.toList(),
|
||||||
],
|
elevation: 8,
|
||||||
),
|
),
|
||||||
value: "hidden",
|
));
|
||||||
)
|
|
||||||
];
|
|
||||||
},
|
|
||||||
onSelected: (v) {
|
|
||||||
if (v == "hidden") {
|
|
||||||
model.toggleShowHidden(local: isLocal);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget body({bool isLocal = false}) {
|
Widget body({bool isLocal = false}) {
|
||||||
@ -153,13 +176,13 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
final sortIndex = (SortBy style) {
|
final sortIndex = (SortBy style) {
|
||||||
switch (style) {
|
switch (style) {
|
||||||
case SortBy.Name:
|
case SortBy.Name:
|
||||||
return 1;
|
return 0;
|
||||||
case SortBy.Type:
|
case SortBy.Type:
|
||||||
return 0;
|
return 0;
|
||||||
case SortBy.Modified:
|
case SortBy.Modified:
|
||||||
return 2;
|
return 1;
|
||||||
case SortBy.Size:
|
case SortBy.Size:
|
||||||
return 3;
|
return 2;
|
||||||
}
|
}
|
||||||
}(model.getSortStyle(isLocal));
|
}(model.getSortStyle(isLocal));
|
||||||
final sortAscending =
|
final sortAscending =
|
||||||
@ -187,13 +210,9 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
controller: ScrollController(),
|
controller: ScrollController(),
|
||||||
child: ObxValue<RxString>(
|
child: ObxValue<RxString>(
|
||||||
(searchText) {
|
(searchText) {
|
||||||
final filteredEntries = searchText.isEmpty
|
final filteredEntries = searchText.isNotEmpty
|
||||||
? entries.where((element) {
|
? entries.where((element) {
|
||||||
if (searchText.isEmpty) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return element.name.contains(searchText.value);
|
return element.name.contains(searchText.value);
|
||||||
}
|
|
||||||
}).toList(growable: false)
|
}).toList(growable: false)
|
||||||
: entries;
|
: entries;
|
||||||
return DataTable(
|
return DataTable(
|
||||||
@ -201,16 +220,16 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
showCheckboxColumn: true,
|
showCheckboxColumn: true,
|
||||||
dataRowHeight: 25,
|
dataRowHeight: 25,
|
||||||
headingRowHeight: 30,
|
headingRowHeight: 30,
|
||||||
|
horizontalMargin: 8,
|
||||||
columnSpacing: 8,
|
columnSpacing: 8,
|
||||||
showBottomBorder: true,
|
showBottomBorder: true,
|
||||||
sortColumnIndex: sortIndex,
|
sortColumnIndex: sortIndex,
|
||||||
sortAscending: sortAscending,
|
sortAscending: sortAscending,
|
||||||
columns: [
|
columns: [
|
||||||
DataColumn(label: Text(translate(" "))), // icon
|
|
||||||
DataColumn(
|
DataColumn(
|
||||||
label: Text(
|
label: Text(
|
||||||
translate("Name"),
|
translate("Name"),
|
||||||
),
|
).marginSymmetric(horizontal: 4),
|
||||||
onSort: (columnIndex, ascending) {
|
onSort: (columnIndex, ascending) {
|
||||||
model.changeSortStyle(SortBy.Name,
|
model.changeSortStyle(SortBy.Name,
|
||||||
isLocal: isLocal, ascending: ascending);
|
isLocal: isLocal, ascending: ascending);
|
||||||
@ -250,19 +269,27 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
selected:
|
selected:
|
||||||
getSelectedItem(isLocal).contains(entry),
|
getSelectedItem(isLocal).contains(entry),
|
||||||
cells: [
|
cells: [
|
||||||
DataCell(Icon(
|
DataCell(
|
||||||
|
Container(
|
||||||
|
width: 180,
|
||||||
|
child: Tooltip(
|
||||||
|
message: entry.name,
|
||||||
|
child: Row(children: [
|
||||||
|
Icon(
|
||||||
entry.isFile
|
entry.isFile
|
||||||
? Icons.feed_outlined
|
? Icons.feed_outlined
|
||||||
: Icons.folder,
|
: Icons.folder,
|
||||||
size: 25)),
|
size: 20,
|
||||||
DataCell(
|
color: Theme.of(context)
|
||||||
ConstrainedBox(
|
.iconTheme
|
||||||
constraints:
|
.color
|
||||||
BoxConstraints(maxWidth: 100),
|
?.withOpacity(0.7),
|
||||||
child: Tooltip(
|
).marginSymmetric(horizontal: 2),
|
||||||
message: entry.name,
|
Expanded(
|
||||||
child: Text(entry.name,
|
child: Text(entry.name,
|
||||||
overflow: TextOverflow.ellipsis),
|
overflow:
|
||||||
|
TextOverflow.ellipsis))
|
||||||
|
]),
|
||||||
)), onTap: () {
|
)), onTap: () {
|
||||||
if (entry.isDirectory) {
|
if (entry.isDirectory) {
|
||||||
openDirectory(entry.path, isLocal: isLocal);
|
openDirectory(entry.path, isLocal: isLocal);
|
||||||
@ -273,29 +300,27 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Perform file-related tasks.
|
// Perform file-related tasks.
|
||||||
final _selectedItems =
|
final selectedItems =
|
||||||
getSelectedItem(isLocal);
|
getSelectedItem(isLocal);
|
||||||
if (_selectedItems.contains(entry)) {
|
if (selectedItems.contains(entry)) {
|
||||||
_selectedItems.remove(entry);
|
selectedItems.remove(entry);
|
||||||
} else {
|
} else {
|
||||||
_selectedItems.add(isLocal, entry);
|
selectedItems.add(isLocal, entry);
|
||||||
}
|
}
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
DataCell(Text(
|
DataCell(FittedBox(
|
||||||
entry
|
child: Text(
|
||||||
.lastModified()
|
"${entry.lastModified().toString().replaceAll(".000", "")} ",
|
||||||
.toString()
|
|
||||||
.replaceAll(".000", "") +
|
|
||||||
" ",
|
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12, color: MyTheme.darkGray),
|
fontSize: 12, color: MyTheme.darkGray),
|
||||||
)),
|
))),
|
||||||
DataCell(Text(
|
DataCell(Text(
|
||||||
sizeStr,
|
sizeStr,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12, color: MyTheme.darkGray),
|
fontSize: 10, color: MyTheme.darkGray),
|
||||||
)),
|
)),
|
||||||
]);
|
]);
|
||||||
}).toList(growable: false),
|
}).toList(growable: false),
|
||||||
@ -410,15 +435,10 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
goBack({bool? isLocal}) {
|
|
||||||
model.goToParentDirectory(isLocal: isLocal);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget headTools(bool isLocal) {
|
Widget headTools(bool isLocal) {
|
||||||
final _locationStatus =
|
final locationStatus =
|
||||||
isLocal ? _locationStatusLocal : _locationStatusRemote;
|
isLocal ? _locationStatusLocal : _locationStatusRemote;
|
||||||
final _locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote;
|
final locationFocus = isLocal ? _locationNodeLocal : _locationNodeRemote;
|
||||||
final _searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
|
|
||||||
return Container(
|
return Container(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@ -463,77 +483,83 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
icon: const Icon(Icons.home_outlined),
|
icon: const Icon(Icons.home_outlined),
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
splashRadius: 20,
|
||||||
|
onPressed: () {
|
||||||
|
model.goBack(isLocal: isLocal);
|
||||||
|
},
|
||||||
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.arrow_upward),
|
icon: const Icon(Icons.arrow_upward),
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
goBack(isLocal: isLocal);
|
model.goToParentDirectory(isLocal: isLocal);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
menu(isLocal: isLocal),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_locationStatus.value =
|
locationStatus.value =
|
||||||
_locationStatus.value == LocationStatus.bread
|
locationStatus.value == LocationStatus.bread
|
||||||
? LocationStatus.textField
|
? LocationStatus.pathLocation
|
||||||
: LocationStatus.bread;
|
: LocationStatus.bread;
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
if (_locationStatus.value == LocationStatus.textField) {
|
if (locationStatus.value == LocationStatus.pathLocation) {
|
||||||
_locationFocus.requestFocus();
|
locationFocus.requestFocus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Obx(() => Container(
|
||||||
decoration:
|
decoration: BoxDecoration(
|
||||||
BoxDecoration(border: Border.all(color: Colors.black12)),
|
border: Border.all(
|
||||||
|
color: locationStatus.value == LocationStatus.bread
|
||||||
|
? Colors.black12
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
.withOpacity(0.5))),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Obx(() =>
|
child: locationStatus.value == LocationStatus.bread
|
||||||
_locationStatus.value == LocationStatus.bread
|
|
||||||
? buildBread(isLocal)
|
? buildBread(isLocal)
|
||||||
: buildPathLocation(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);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
],
|
],
|
||||||
|
))),
|
||||||
)),
|
)),
|
||||||
)),
|
Obx(() {
|
||||||
PopupMenuButton(
|
switch (locationStatus.value) {
|
||||||
itemBuilder: (context) => [
|
case LocationStatus.bread:
|
||||||
PopupMenuItem(
|
return IconButton(
|
||||||
enabled: false,
|
onPressed: () {
|
||||||
child: ConstrainedBox(
|
locationStatus.value = LocationStatus.fileSearchBar;
|
||||||
constraints: BoxConstraints(minWidth: 200),
|
final focusNode =
|
||||||
child: TextField(
|
isLocal ? _locationNodeLocal : _locationNodeRemote;
|
||||||
controller:
|
Future.delayed(
|
||||||
TextEditingController(text: _searchTextObs.value),
|
Duration.zero, () => focusNode.requestFocus());
|
||||||
autofocus: true,
|
},
|
||||||
decoration:
|
|
||||||
InputDecoration(prefixIcon: Icon(Icons.search)),
|
|
||||||
onChanged: (searchText) =>
|
|
||||||
onSearchText(searchText, isLocal),
|
|
||||||
),
|
|
||||||
))
|
|
||||||
],
|
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
child: const Icon(Icons.search),
|
icon: Icon(Icons.search));
|
||||||
),
|
case LocationStatus.pathLocation:
|
||||||
|
return IconButton(
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
onPressed: null,
|
||||||
|
splashRadius: 20,
|
||||||
|
icon: Icon(Icons.close));
|
||||||
|
case LocationStatus.fileSearchBar:
|
||||||
|
return IconButton(
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
onPressed: () {
|
||||||
|
onSearchText("", isLocal);
|
||||||
|
locationStatus.value = LocationStatus.bread;
|
||||||
|
},
|
||||||
|
splashRadius: 1,
|
||||||
|
icon: Icon(Icons.close));
|
||||||
|
}
|
||||||
|
}),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.refresh(isLocal: isLocal);
|
model.refresh(isLocal: isLocal);
|
||||||
@ -609,6 +635,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
},
|
},
|
||||||
splashRadius: 20,
|
splashRadius: 20,
|
||||||
icon: const Icon(Icons.delete_forever_outlined)),
|
icon: const Icon(Icons.delete_forever_outlined)),
|
||||||
|
menu(isLocal: isLocal),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -642,9 +669,11 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
// ignore
|
// ignore
|
||||||
} else {
|
} else {
|
||||||
// lost focus, change to bread
|
// lost focus, change to bread
|
||||||
|
if (_locationStatusLocal.value != LocationStatus.fileSearchBar) {
|
||||||
_locationStatusLocal.value = LocationStatus.bread;
|
_locationStatusLocal.value = LocationStatus.bread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onRemoteLocationFocusChanged() {
|
void onRemoteLocationFocusChanged() {
|
||||||
debugPrint("focus changed on remote");
|
debugPrint("focus changed on remote");
|
||||||
@ -652,9 +681,11 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
// ignore
|
// ignore
|
||||||
} else {
|
} else {
|
||||||
// lost focus, change to bread
|
// lost focus, change to bread
|
||||||
|
if (_locationStatusRemote.value != LocationStatus.fileSearchBar) {
|
||||||
_locationStatusRemote.value = LocationStatus.bread;
|
_locationStatusRemote.value = LocationStatus.bread;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Widget buildBread(bool isLocal) {
|
Widget buildBread(bool isLocal) {
|
||||||
final items = getPathBreadCrumbItems(isLocal, (list) {
|
final items = getPathBreadCrumbItems(isLocal, (list) {
|
||||||
@ -664,14 +695,33 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
}
|
}
|
||||||
openDirectory(path, isLocal: isLocal);
|
openDirectory(path, isLocal: isLocal);
|
||||||
});
|
});
|
||||||
|
breadCrumbScrollToEnd(isLocal);
|
||||||
return items.isEmpty
|
return items.isEmpty
|
||||||
? Offstage()
|
? Offstage()
|
||||||
: BreadCrumb(
|
: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||||
|
Expanded(
|
||||||
|
child: BreadCrumb(
|
||||||
items: items,
|
items: items,
|
||||||
divider: Text("/").paddingSymmetric(horizontal: 4.0),
|
divider: Text("/").paddingSymmetric(horizontal: 4.0),
|
||||||
overflow: ScrollableOverflow(
|
overflow: ScrollableOverflow(
|
||||||
controller: getBreadCrumbScrollController(isLocal)),
|
controller: getBreadCrumbScrollController(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);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<BreadCrumbItem> getPathBreadCrumbItems(
|
List<BreadCrumbItem> getPathBreadCrumbItems(
|
||||||
@ -690,28 +740,49 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
|
|
||||||
breadCrumbScrollToEnd(bool isLocal) {
|
breadCrumbScrollToEnd(bool isLocal) {
|
||||||
Future.delayed(Duration(milliseconds: 200), () {
|
Future.delayed(Duration(milliseconds: 200), () {
|
||||||
final _breadCrumbScroller = getBreadCrumbScrollController(isLocal);
|
final breadCrumbScroller = getBreadCrumbScrollController(isLocal);
|
||||||
_breadCrumbScroller.animateTo(
|
if (breadCrumbScroller.hasClients) {
|
||||||
_breadCrumbScroller.position.maxScrollExtent,
|
breadCrumbScroller.animateTo(
|
||||||
|
breadCrumbScroller.position.maxScrollExtent,
|
||||||
duration: Duration(milliseconds: 200),
|
duration: Duration(milliseconds: 200),
|
||||||
curve: Curves.fastLinearToSlowEaseIn);
|
curve: Curves.fastLinearToSlowEaseIn);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget buildPathLocation(bool isLocal) {
|
Widget buildPathLocation(bool isLocal) {
|
||||||
return TextField(
|
final searchTextObs = isLocal ? _searchTextLocal : _searchTextRemote;
|
||||||
focusNode: isLocal ? _locationNodeLocal : _locationNodeRemote,
|
final locationStatus =
|
||||||
|
isLocal ? _locationStatusLocal : _locationStatusRemote;
|
||||||
|
final focusNode = isLocal ? _locationNodeLocal : _locationNodeRemote;
|
||||||
|
final text = locationStatus.value == LocationStatus.pathLocation
|
||||||
|
? model.getCurrentDir(isLocal).path
|
||||||
|
: searchTextObs.value;
|
||||||
|
final textController = TextEditingController(text: text)
|
||||||
|
..selection = TextSelection.collapsed(offset: text.length);
|
||||||
|
return Row(children: [
|
||||||
|
Icon(
|
||||||
|
locationStatus.value == LocationStatus.pathLocation
|
||||||
|
? Icons.folder
|
||||||
|
: Icons.search,
|
||||||
|
color: Theme.of(context).hintColor,
|
||||||
|
).paddingSymmetric(horizontal: 2),
|
||||||
|
Expanded(
|
||||||
|
child: TextField(
|
||||||
|
focusNode: focusNode,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
prefix: Padding(padding: EdgeInsets.only(left: 4.0)),
|
prefix: Padding(padding: EdgeInsets.only(left: 4.0))),
|
||||||
),
|
controller: textController,
|
||||||
controller:
|
|
||||||
TextEditingController(text: model.getCurrentDir(isLocal).path),
|
|
||||||
onSubmitted: (path) {
|
onSubmitted: (path) {
|
||||||
openDirectory(path, isLocal: isLocal);
|
openDirectory(path, isLocal: isLocal);
|
||||||
},
|
},
|
||||||
);
|
onChanged: locationStatus.value == LocationStatus.fileSearchBar
|
||||||
|
? (searchText) => onSearchText(searchText, isLocal)
|
||||||
|
: null,
|
||||||
|
))
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSearchText(String searchText, bool isLocal) {
|
onSearchText(String searchText, bool isLocal) {
|
||||||
@ -734,7 +805,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var items = SelectedItems();
|
var items = SelectedItems();
|
||||||
details.files.forEach((file) {
|
for (var file in details.files) {
|
||||||
final f = File(file.path);
|
final f = File(file.path);
|
||||||
items.add(
|
items.add(
|
||||||
true,
|
true,
|
||||||
@ -743,7 +814,7 @@ class _FileManagerPageState extends State<FileManagerPage>
|
|||||||
..name = file.name
|
..name = file.name
|
||||||
..size =
|
..size =
|
||||||
FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync());
|
FileSystemEntity.isDirectorySync(f.path) ? 0 : f.lengthSync());
|
||||||
});
|
}
|
||||||
model.sendFiles(items, isRemote: false);
|
model.sendFiles(items, isRemote: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import 'package:flutter_hbb/desktop/widgets/tabbar_widget.dart';
|
|||||||
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
import 'package:flutter_hbb/utils/multi_window_manager.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
import '../../mobile/widgets/dialog.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
|
||||||
/// File Transfer for multi tabs
|
/// File Transfer for multi tabs
|
||||||
class FileManagerTabPage extends StatefulWidget {
|
class FileManagerTabPage extends StatefulWidget {
|
||||||
@ -35,7 +35,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
label: params['id'],
|
label: params['id'],
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon,
|
unselectedIcon: unselectedIcon,
|
||||||
onTabCloseButton: () => handleTabCloseButton(params['id']),
|
onTabCloseButton: () => () => tabController.closeBy(params['id']),
|
||||||
page: FileManagerPage(key: ValueKey(params['id']), id: params['id'])));
|
page: FileManagerPage(key: ValueKey(params['id']), id: params['id'])));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +58,7 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
label: id,
|
label: id,
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon,
|
unselectedIcon: unselectedIcon,
|
||||||
onTabCloseButton: () => handleTabCloseButton(id),
|
onTabCloseButton: () => tabController.closeBy(id),
|
||||||
page: FileManagerPage(key: ValueKey(id), id: id)));
|
page: FileManagerPage(key: ValueKey(id), id: id)));
|
||||||
} else if (call.method == "onDestroy") {
|
} else if (call.method == "onDestroy") {
|
||||||
tabController.clear();
|
tabController.clear();
|
||||||
@ -98,26 +98,19 @@ class _FileManagerTabPageState extends State<FileManagerTabPage> {
|
|||||||
return widget.params["windowId"];
|
return widget.params["windowId"];
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTabCloseButton(String peerId) {
|
|
||||||
final session = ffi('ft_$peerId');
|
|
||||||
if (session.ffiModel.pi.hostname.isNotEmpty) {
|
|
||||||
tabController.jumpBy(peerId);
|
|
||||||
clientClose(session.dialogManager);
|
|
||||||
} else {
|
|
||||||
tabController.closeBy(peerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> handleWindowCloseButton() async {
|
Future<bool> handleWindowCloseButton() async {
|
||||||
final connLength = tabController.state.value.tabs.length;
|
final connLength = tabController.state.value.tabs.length;
|
||||||
if (connLength < 1) {
|
if (connLength <= 1) {
|
||||||
|
tabController.clear();
|
||||||
return true;
|
return true;
|
||||||
} else if (connLength == 1) {
|
|
||||||
final currentConn = tabController.state.value.tabs[0];
|
|
||||||
handleTabCloseButton(currentConn.key);
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
final res = await closeConfirmDialog();
|
final opt = "enable-confirm-closing-tabs";
|
||||||
|
final bool res;
|
||||||
|
if (!option2bool(opt, await bind.mainGetOption(key: opt))) {
|
||||||
|
res = true;
|
||||||
|
} else {
|
||||||
|
res = await closeConfirmDialog();
|
||||||
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
tabController.clear();
|
tabController.clear();
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import 'package:flutter_hbb/utils/multi_window_manager.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 '../../mobile/widgets/dialog.dart';
|
import '../../models/platform_model.dart';
|
||||||
|
|
||||||
class ConnectionTabPage extends StatefulWidget {
|
class ConnectionTabPage extends StatefulWidget {
|
||||||
final Map<String, dynamic> params;
|
final Map<String, dynamic> params;
|
||||||
@ -42,7 +42,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
label: peerId,
|
label: peerId,
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon,
|
unselectedIcon: unselectedIcon,
|
||||||
onTabCloseButton: () => handleTabCloseButton(peerId),
|
onTabCloseButton: () => tabController.closeBy(peerId),
|
||||||
page: Obx(() => RemotePage(
|
page: Obx(() => RemotePage(
|
||||||
key: ValueKey(peerId),
|
key: ValueKey(peerId),
|
||||||
id: peerId,
|
id: peerId,
|
||||||
@ -78,7 +78,7 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
label: id,
|
label: id,
|
||||||
selectedIcon: selectedIcon,
|
selectedIcon: selectedIcon,
|
||||||
unselectedIcon: unselectedIcon,
|
unselectedIcon: unselectedIcon,
|
||||||
onTabCloseButton: () => handleTabCloseButton(id),
|
onTabCloseButton: () => tabController.closeBy(id),
|
||||||
page: Obx(() => RemotePage(
|
page: Obx(() => RemotePage(
|
||||||
key: ValueKey(id),
|
key: ValueKey(id),
|
||||||
id: id,
|
id: id,
|
||||||
@ -173,29 +173,21 @@ class _ConnectionTabPageState extends State<ConnectionTabPage> {
|
|||||||
return widget.params["windowId"];
|
return widget.params["windowId"];
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleTabCloseButton(String peerId) {
|
|
||||||
final session = ffi(peerId);
|
|
||||||
if (session.ffiModel.pi.hostname.isNotEmpty) {
|
|
||||||
tabController.jumpBy(peerId);
|
|
||||||
clientClose(session.dialogManager);
|
|
||||||
} else {
|
|
||||||
tabController.closeBy(peerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> handleWindowCloseButton() async {
|
Future<bool> handleWindowCloseButton() async {
|
||||||
final connLength = tabController.length;
|
final connLength = tabController.length;
|
||||||
if (connLength < 1) {
|
if (connLength <= 1) {
|
||||||
|
tabController.clear();
|
||||||
return true;
|
return true;
|
||||||
} else if (connLength == 1) {
|
|
||||||
final currentConn = tabController.state.value.tabs[0];
|
|
||||||
handleTabCloseButton(currentConn.key);
|
|
||||||
return false;
|
|
||||||
} else {
|
} else {
|
||||||
final res = await closeConfirmDialog();
|
final opt = "enable-confirm-closing-tabs";
|
||||||
|
final bool res;
|
||||||
|
if (!option2bool(opt, await bind.mainGetOption(key: opt))) {
|
||||||
|
res = true;
|
||||||
|
} else {
|
||||||
|
res = await closeConfirmDialog();
|
||||||
|
}
|
||||||
if (res) {
|
if (res) {
|
||||||
tabController.clear();
|
tabController.clear();
|
||||||
_update_remote_count();
|
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -407,7 +407,7 @@ class _PrivilegeBoardState extends State<_PrivilegeBoard> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
client.recording = enabled;
|
client.recording = enabled;
|
||||||
});
|
});
|
||||||
}, translate('Allow reco)rding session'))
|
}, translate('Allow recording session'))
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:core';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
import '../../common.dart';
|
||||||
import './material_mod_popup_menu.dart' as mod_menu;
|
import './material_mod_popup_menu.dart' as mod_menu;
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
|
// https://stackoverflow.com/questions/68318314/flutter-popup-menu-inside-popup-menu
|
||||||
@ -637,3 +638,10 @@ class MenuEntryButton<T> extends MenuEntryBase<T> {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CustomPopupMenuTheme {
|
||||||
|
static const Color commonColor = MyTheme.accent;
|
||||||
|
// kMinInteractiveDimension
|
||||||
|
static const double height = 20.0;
|
||||||
|
static const double dividerHeight = 3.0;
|
||||||
|
}
|
||||||
|
@ -456,8 +456,15 @@ class WindowActionPanel extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> closeConfirmDialog() async {
|
Future<bool> closeConfirmDialog() async {
|
||||||
|
var confirm = true;
|
||||||
final res = await gFFI.dialogManager.show<bool>((setState, close) {
|
final res = await gFFI.dialogManager.show<bool>((setState, close) {
|
||||||
submit() => close(true);
|
submit() {
|
||||||
|
final opt = "enable-confirm-closing-tabs";
|
||||||
|
String value = bool2option(opt, confirm);
|
||||||
|
bind.mainSetOption(key: opt, value: value);
|
||||||
|
close(true);
|
||||||
|
}
|
||||||
|
|
||||||
return CustomAlertDialog(
|
return CustomAlertDialog(
|
||||||
title: Row(children: [
|
title: Row(children: [
|
||||||
const Icon(Icons.warning_amber_sharp,
|
const Icon(Icons.warning_amber_sharp,
|
||||||
@ -465,7 +472,25 @@ Future<bool> closeConfirmDialog() async {
|
|||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
Text(translate("Warning")),
|
Text(translate("Warning")),
|
||||||
]),
|
]),
|
||||||
content: Text(translate("Disconnect all devices?")),
|
content: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(translate("Disconnect all devices?")),
|
||||||
|
CheckboxListTile(
|
||||||
|
contentPadding: const EdgeInsets.all(0),
|
||||||
|
dense: true,
|
||||||
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
|
title: Text(
|
||||||
|
translate("Confirm before closing multiple tabs"),
|
||||||
|
),
|
||||||
|
value: confirm,
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v == null) return;
|
||||||
|
setState(() => confirm = v);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
]), // confirm checkbox
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(onPressed: close, child: Text(translate("Cancel"))),
|
TextButton(onPressed: close, child: Text(translate("Cancel"))),
|
||||||
ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
|
ElevatedButton(onPressed: submit, child: Text(translate("OK"))),
|
||||||
|
@ -16,12 +16,15 @@ class FileModel extends ChangeNotifier {
|
|||||||
var _isLocal = false;
|
var _isLocal = false;
|
||||||
var _selectMode = false;
|
var _selectMode = false;
|
||||||
|
|
||||||
var _localOption = DirectoryOption();
|
final _localOption = DirectoryOption();
|
||||||
var _remoteOption = DirectoryOption();
|
final _remoteOption = DirectoryOption();
|
||||||
|
|
||||||
|
List<String> localHistory = [];
|
||||||
|
List<String> remoteHistory = [];
|
||||||
|
|
||||||
var _jobId = 0;
|
var _jobId = 0;
|
||||||
|
|
||||||
var _jobProgress = JobProgress(); // from rust update
|
final _jobProgress = JobProgress(); // from rust update
|
||||||
|
|
||||||
/// JobTable <jobId, JobProgress>
|
/// JobTable <jobId, JobProgress>
|
||||||
final _jobTable = List<JobProgress>.empty(growable: true).obs;
|
final _jobTable = List<JobProgress>.empty(growable: true).obs;
|
||||||
@ -368,8 +371,11 @@ class FileModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
openDirectory(String path, {bool? isLocal}) async {
|
openDirectory(String path, {bool? isLocal, bool isBack = false}) async {
|
||||||
isLocal = isLocal ?? _isLocal;
|
isLocal = isLocal ?? _isLocal;
|
||||||
|
if (!isBack) {
|
||||||
|
pushHistory(isLocal);
|
||||||
|
}
|
||||||
final showHidden =
|
final showHidden =
|
||||||
isLocal ? _localOption.showHidden : _remoteOption.showHidden;
|
isLocal ? _localOption.showHidden : _remoteOption.showHidden;
|
||||||
final isWindows =
|
final isWindows =
|
||||||
@ -397,11 +403,34 @@ class FileModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pushHistory(bool isLocal) {
|
||||||
|
final history = isLocal ? localHistory : remoteHistory;
|
||||||
|
final currPath = isLocal ? currentLocalDir.path : currentRemoteDir.path;
|
||||||
|
if (history.isNotEmpty && history.last == currPath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
history.add(currPath);
|
||||||
|
}
|
||||||
|
|
||||||
goHome({bool? isLocal}) {
|
goHome({bool? isLocal}) {
|
||||||
isLocal = isLocal ?? _isLocal;
|
isLocal = isLocal ?? _isLocal;
|
||||||
openDirectory(getCurrentHome(isLocal), isLocal: isLocal);
|
openDirectory(getCurrentHome(isLocal), isLocal: isLocal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
goBack({bool? isLocal}) {
|
||||||
|
isLocal = isLocal ?? _isLocal;
|
||||||
|
final history = isLocal ? localHistory : remoteHistory;
|
||||||
|
if (history.isEmpty) return;
|
||||||
|
final path = history.removeAt(history.length - 1);
|
||||||
|
if (path.isEmpty) return;
|
||||||
|
final currPath = isLocal ? currentLocalDir.path : currentRemoteDir.path;
|
||||||
|
if (currPath == path) {
|
||||||
|
goBack(isLocal: isLocal);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
openDirectory(path, isLocal: isLocal, isBack: true);
|
||||||
|
}
|
||||||
|
|
||||||
goToParentDirectory({bool? isLocal}) {
|
goToParentDirectory({bool? isLocal}) {
|
||||||
isLocal = isLocal ?? _isLocal;
|
isLocal = isLocal ?? _isLocal;
|
||||||
final isWindows =
|
final isWindows =
|
||||||
@ -685,6 +714,8 @@ class FileModel extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendRemoveEmptyDir(String path, int fileNum, bool isLocal) {
|
sendRemoveEmptyDir(String path, int fileNum, bool isLocal) {
|
||||||
|
final history = isLocal ? localHistory : remoteHistory;
|
||||||
|
history.removeWhere((element) => element.contains(path));
|
||||||
bind.sessionRemoveAllEmptyDirs(
|
bind.sessionRemoveAllEmptyDirs(
|
||||||
id: '${parent.target?.id}',
|
id: '${parent.target?.id}',
|
||||||
actId: _jobId,
|
actId: _jobId,
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", "暂时无法访问远端设备,因为远端设备正在请求用户账户权限,请等待对方关闭UAC窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
|
("uac_warning", "暂时无法访问远端设备,因为远端设备正在请求用户账户权限,请等待对方关闭UAC窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
|
||||||
("elevated_foreground_window_warning", "暂时无法使用鼠标键盘,因为远端桌面的当前窗口需要更高的权限才能操作, 可以请求对方最小化当前窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
|
("elevated_foreground_window_warning", "暂时无法使用鼠标键盘,因为远端桌面的当前窗口需要更高的权限才能操作, 可以请求对方最小化当前窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
|
||||||
("Disconnected", "会话已结束"),
|
("Disconnected", "会话已结束"),
|
||||||
|
("Other", "其他"),
|
||||||
|
("Confirm before closing multiple tabs", "关闭多个标签页时向您确认"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", "Afbrudt"),
|
("Disconnected", "Afbrudt"),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -193,7 +193,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Reboot required", "Redémarrage pour prendre effet"),
|
("Reboot required", "Redémarrage pour prendre effet"),
|
||||||
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
|
("Unsupported display server ", "Le serveur d'affichage actuel n'est pas pris en charge"),
|
||||||
("x11 expected", "Veuillez passer à x11"),
|
("x11 expected", "Veuillez passer à x11"),
|
||||||
("Port", ""),
|
("Port", "Port"),
|
||||||
("Settings", "Paramètres"),
|
("Settings", "Paramètres"),
|
||||||
("Username", " Nom d'utilisateur"),
|
("Username", " Nom d'utilisateur"),
|
||||||
("Invalid port", "Port invalide"),
|
("Invalid port", "Port invalide"),
|
||||||
@ -274,7 +274,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."),
|
("android_stop_service_tip", "La fermeture du service fermera automatiquement toutes les connexions établies."),
|
||||||
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
|
("android_version_audio_tip", "La version actuelle d'Android ne prend pas en charge la capture audio, veuillez passer à Android 10 ou supérieur."),
|
||||||
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."),
|
("android_start_service_tip", "Appuyez sur [Démarrer le service] ou sur l'autorisation OUVRIR [Capture d'écran] pour démarrer le service de partage d'écran."),
|
||||||
("Account", ""),
|
("Account", "Compte"),
|
||||||
("Overwrite", "Écraser"),
|
("Overwrite", "Écraser"),
|
||||||
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
|
("This file exists, skip or overwrite this file?", "Ce fichier existe, ignorer ou écraser ce fichier ?"),
|
||||||
("Quit", "Quitter"),
|
("Quit", "Quitter"),
|
||||||
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", "他の"),
|
||||||
|
("Confirm before closing multiple tabs", "同時に複数のタブを閉じる前に確認する"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", "Ostrzeżenie UAC"),
|
("uac_warning", "Ostrzeżenie UAC"),
|
||||||
("elevated_foreground_window_warning", "Pierwszoplanowe okno ostrzeżenia o podwyższeniu uprawnień"),
|
("elevated_foreground_window_warning", "Pierwszoplanowe okno ostrzeżenia o podwyższeniu uprawnień"),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", "暂时无法访问远端设备,因为远端设备正在请求用户账户权限,请等待对方关闭UAC窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
|
("uac_warning", "暂时无法访问远端设备,因为远端设备正在请求用户账户权限,请等待对方关闭UAC窗口。为避免这个问题,建议在远端设备上安装或者以管理员权限运行本软件。"),
|
||||||
("elevated_foreground_window_warning", "暫時無法使用鼠標鍵盤,因為遠端桌面的當前窗口需要更高的權限才能操作, 可以請求對方最小化當前窗口。為避免這個問題,建議在遠端設備上安裝或者以管理員權限運行本軟件。"),
|
("elevated_foreground_window_warning", "暫時無法使用鼠標鍵盤,因為遠端桌面的當前窗口需要更高的權限才能操作, 可以請求對方最小化當前窗口。為避免這個問題,建議在遠端設備上安裝或者以管理員權限運行本軟件。"),
|
||||||
("Disconnected", "會話已結束"),
|
("Disconnected", "會話已結束"),
|
||||||
|
("Other", "其他"),
|
||||||
|
("Confirm before closing multiple tabs", "關閉多個分頁前跟我確認"),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -191,9 +191,9 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Warning", "Попередження"),
|
("Warning", "Попередження"),
|
||||||
("Login screen using Wayland is not supported", "Вхід у систему з використанням Wayland не підтримується"),
|
("Login screen using Wayland is not supported", "Вхід у систему з використанням Wayland не підтримується"),
|
||||||
("Reboot required", "Потрібне перезавантаження"),
|
("Reboot required", "Потрібне перезавантаження"),
|
||||||
("Unsupported display server", "Непідтримуваний сервер дисплея"),
|
("Unsupported display server ", ""),
|
||||||
("x11 expected", "Очікується X11"),
|
("x11 expected", "Очікується X11"),
|
||||||
("Port", ""),
|
("Port", "Порт"),
|
||||||
("Settings", "Налаштування"),
|
("Settings", "Налаштування"),
|
||||||
("Username", "Ім'я користувача"),
|
("Username", "Ім'я користувача"),
|
||||||
("Invalid port", "Неправильний порт"),
|
("Invalid port", "Неправильний порт"),
|
||||||
@ -274,7 +274,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("android_stop_service_tip", "Закриття служби автоматично закриє всі встановлені з'єднання."),
|
("android_stop_service_tip", "Закриття служби автоматично закриє всі встановлені з'єднання."),
|
||||||
("android_version_audio_tip", "Поточна версія Android не підтримує захоплення звуку, оновіть її до Android 10 або вище."),
|
("android_version_audio_tip", "Поточна версія Android не підтримує захоплення звуку, оновіть її до Android 10 або вище."),
|
||||||
("android_start_service_tip", "Натисніть [Запуск проміжного сервера] або ВІДКРИТИ роздільну здатність [Захоплення екрана], щоб запустити службу демонстрації екрана."),
|
("android_start_service_tip", "Натисніть [Запуск проміжного сервера] або ВІДКРИТИ роздільну здатність [Захоплення екрана], щоб запустити службу демонстрації екрана."),
|
||||||
("Account", ""),
|
("Account", "Акаунт"),
|
||||||
("Overwrite", "Перезаписати"),
|
("Overwrite", "Перезаписати"),
|
||||||
("This file exists, skip or overwrite this file?", "Цей файл існує, пропустити або перезаписати файл?"),
|
("This file exists, skip or overwrite this file?", "Цей файл існує, пропустити або перезаписати файл?"),
|
||||||
("Quit", "Вийти"),
|
("Quit", "Вийти"),
|
||||||
@ -298,7 +298,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("Connection not allowed", "Підключення не дозволено"),
|
("Connection not allowed", "Підключення не дозволено"),
|
||||||
("Legacy mode", ""),
|
("Legacy mode", ""),
|
||||||
("Map mode", ""),
|
("Map mode", ""),
|
||||||
("Режим перекладу", ""),
|
("Translate mode", ""),
|
||||||
("Use temporary password", "Використовувати тимчасовий пароль"),
|
("Use temporary password", "Використовувати тимчасовий пароль"),
|
||||||
("Use permanent password", "Використовувати постійний пароль"),
|
("Use permanent password", "Використовувати постійний пароль"),
|
||||||
("Use both passwords", "Використовувати обидва паролі"),
|
("Use both passwords", "Використовувати обидва паролі"),
|
||||||
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
@ -371,5 +371,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
|
|||||||
("uac_warning", ""),
|
("uac_warning", ""),
|
||||||
("elevated_foreground_window_warning", ""),
|
("elevated_foreground_window_warning", ""),
|
||||||
("Disconnected", ""),
|
("Disconnected", ""),
|
||||||
|
("Other", ""),
|
||||||
|
("Confirm before closing multiple tabs", ""),
|
||||||
].iter().cloned().collect();
|
].iter().cloned().collect();
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user