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