mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-18 07:44:26 +01:00
new: picker & opt. rm -r
This commit is contained in:
@@ -265,18 +265,24 @@ class _ServerPageState extends State<ServerPage>
|
||||
tooltip: 'Suspend',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _askFor(func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.shutdown.exec,
|
||||
context: context,
|
||||
), msg: 'Shutdown ${srv.spi.name}',),
|
||||
onPressed: () => _askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.shutdown.exec,
|
||||
context: context,
|
||||
),
|
||||
msg: 'Shutdown ${srv.spi.name}',
|
||||
),
|
||||
icon: const Icon(Icons.power_off),
|
||||
tooltip: 'Shutdown',
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () => _askFor(func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.reboot.exec,
|
||||
context: context,
|
||||
), msg: 'Reboot ${srv.spi.name}',),
|
||||
onPressed: () => _askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.reboot.exec,
|
||||
context: context,
|
||||
),
|
||||
msg: 'Reboot ${srv.spi.name}',
|
||||
),
|
||||
icon: const Icon(Icons.restart_alt),
|
||||
tooltip: 'Reboot',
|
||||
),
|
||||
|
||||
@@ -234,7 +234,7 @@ class _SettingPageState extends State<SettingPage> {
|
||||
// Use hardware keyboard on desktop, so there is no need to set it
|
||||
if (isMobile) _buildKeyboardType(),
|
||||
_buildSSHVirtKeys(),
|
||||
_buildSftpRmrfDir(),
|
||||
_buildSftpRmrDir(),
|
||||
].map((e) => RoundRectCard(e)).toList(),
|
||||
);
|
||||
}
|
||||
@@ -997,11 +997,11 @@ class _SettingPageState extends State<SettingPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSftpRmrfDir() {
|
||||
Widget _buildSftpRmrDir() {
|
||||
return ListTile(
|
||||
title: const Text('rm -rf'),
|
||||
subtitle: Text(l10n.sftpRmrfDirSummary, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.sftpRmrfDir),
|
||||
title: const Text('rm -r'),
|
||||
subtitle: Text(l10n.sftpRmrDirSummary, style: UIs.textGrey),
|
||||
trailing: StoreSwitch(prop: _setting.sftpRmrDir),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -127,13 +127,10 @@ class _SnippetListPageState extends State<SnippetListPage> {
|
||||
}
|
||||
|
||||
Future<void> _runSnippet(Snippet snippet) async {
|
||||
final servers = await showDialog<List<Server>>(
|
||||
context: context,
|
||||
builder: (_) => TagPicker<Server>(
|
||||
final servers = await context.showPickDialog<Server>(
|
||||
items: Pros.server.servers.toList(),
|
||||
tags: Pros.server.tags.toSet(),
|
||||
),
|
||||
);
|
||||
name: (e) => e.spi.name,
|
||||
);
|
||||
if (servers == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,9 @@ import 'package:toolbox/core/extension/context/dialog.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/core/extension/context/snackbar.dart';
|
||||
import 'package:toolbox/core/utils/platform/base.dart';
|
||||
import 'package:toolbox/data/model/server/snippet.dart';
|
||||
import 'package:toolbox/data/provider/virtual_keyboard.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:toolbox/data/res/store.dart';
|
||||
import 'package:xterm/core.dart';
|
||||
import 'package:xterm/ui.dart' hide TerminalThemes;
|
||||
@@ -253,10 +255,13 @@ class _SSHPageState extends State<SSHPage> {
|
||||
}
|
||||
break;
|
||||
case VirtualKeyFunc.snippet:
|
||||
context.showSnippetDialog((s) {
|
||||
_terminal.textInput(s.script);
|
||||
_terminal.keyInput(TerminalKey.enter);
|
||||
});
|
||||
final s = await context.showPickSingleDialog<Snippet>(
|
||||
items: Pros.snippet.snippets,
|
||||
name: (p0) => p0.name,
|
||||
);
|
||||
if (s == null) return;
|
||||
_terminal.textInput(s.script);
|
||||
_terminal.keyInput(TerminalKey.enter);
|
||||
break;
|
||||
case VirtualKeyFunc.file:
|
||||
// get $PWD from SSH session
|
||||
|
||||
@@ -5,12 +5,12 @@ import 'package:toolbox/core/extension/context/common.dart';
|
||||
import 'package:toolbox/core/extension/context/dialog.dart';
|
||||
import 'package:toolbox/core/extension/context/locale.dart';
|
||||
import 'package:toolbox/core/extension/context/snackbar.dart';
|
||||
import 'package:toolbox/data/model/server/server_private_info.dart';
|
||||
import 'package:toolbox/data/model/sftp/req.dart';
|
||||
import 'package:toolbox/data/res/misc.dart';
|
||||
import 'package:toolbox/data/res/provider.dart';
|
||||
import 'package:toolbox/view/widget/input_field.dart';
|
||||
import 'package:toolbox/view/widget/omit_start_text.dart';
|
||||
import 'package:toolbox/view/widget/picker.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
import '../../../core/extension/numx.dart';
|
||||
@@ -273,24 +273,15 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
||||
title: Text(l10n.upload),
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
final ids = Pros.server.serverOrder;
|
||||
var idx = 0;
|
||||
await context.showRoundDialog(
|
||||
title: Text(l10n.server),
|
||||
child: Picker(
|
||||
items: ids.map((e) => Text(e)).toList(),
|
||||
onSelected: (idx_) => idx = idx_,
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(), child: Text(l10n.ok)),
|
||||
],
|
||||
|
||||
final spi = await context.showPickSingleDialog<ServerPrivateInfo>(
|
||||
items: Pros.server.serverOrder
|
||||
.map((e) => Pros.server.pick(id: e)?.spi)
|
||||
.toList(),
|
||||
name: (e) => e.name,
|
||||
);
|
||||
final id = ids[idx];
|
||||
final spi = Pros.server.pick(id: id)?.spi;
|
||||
if (spi == null) {
|
||||
return;
|
||||
}
|
||||
if (spi == null) return;
|
||||
|
||||
final remotePath = await AppRoute.sftp(
|
||||
spi: spi,
|
||||
isSelect: true,
|
||||
@@ -298,6 +289,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
||||
if (remotePath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Pros.sftp.add(SftpReq(
|
||||
spi,
|
||||
'$remotePath/$fileName',
|
||||
@@ -346,7 +338,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
|
||||
final fileName = file.path.split('/').last;
|
||||
context.showRoundDialog(
|
||||
title: Text(l10n.delete),
|
||||
child: Text(l10n.sureDelete(fileName)),
|
||||
child: Text(l10n.askContinue('${l10n.delete}: $fileName')),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(),
|
||||
|
||||
@@ -409,13 +409,16 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
void _delete(SftpName file) {
|
||||
context.pop();
|
||||
final isDir = file.attr.isDirectory;
|
||||
final useRmrf = Stores.setting.sftpRmrfDir.fetch();
|
||||
final dirText = (isDir && !useRmrf) ? '\n${l10n.dirEmpty}' : '';
|
||||
final text = l10n.askContinue(
|
||||
'${l10n.delete} ${l10n.files}(${file.filename})\n$dirText');
|
||||
final child = Text(text);
|
||||
final useRmr = Stores.setting.sftpRmrDir.fetch();
|
||||
final text = () {
|
||||
if (isDir && !useRmr) {
|
||||
return l10n.askContinue(
|
||||
'${l10n.dirEmpty}\n${l10n.delete} ${l10n.files}(${file.filename})');
|
||||
}
|
||||
return l10n.askContinue('${l10n.delete} ${l10n.files}(${file.filename})');
|
||||
}();
|
||||
context.showRoundDialog(
|
||||
child: child,
|
||||
child: Text(text),
|
||||
title: Text(l10n.attention),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -428,8 +431,8 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
context.showLoadingDialog();
|
||||
final remotePath = _getRemotePath(file);
|
||||
try {
|
||||
if (useRmrf) {
|
||||
await _client!.run('rm -rf "$remotePath"');
|
||||
if (useRmr) {
|
||||
await _client!.run('rm -r "$remotePath"');
|
||||
} else if (file.attr.isDirectory) {
|
||||
await _status.client!.rmdir(remotePath);
|
||||
} else {
|
||||
|
||||
29
lib/view/widget/choice_chip.dart
Normal file
29
lib/view/widget/choice_chip.dart
Normal file
@@ -0,0 +1,29 @@
|
||||
import 'package:choice/selection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ChoiceChipX<T> extends StatelessWidget {
|
||||
const ChoiceChipX({
|
||||
Key? key,
|
||||
required this.label,
|
||||
required this.state,
|
||||
required this.value,
|
||||
}) : super(key: key);
|
||||
|
||||
final String label;
|
||||
final ChoiceController<T> state;
|
||||
final T value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(3),
|
||||
child: ChoiceChip(
|
||||
label: Text(label),
|
||||
side: BorderSide.none,
|
||||
selected: state.selected(value),
|
||||
selectedColor: Theme.of(context).colorScheme.primary,
|
||||
onSelected: state.onSelected(value),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class Picker extends StatelessWidget {
|
||||
final List<Widget> items;
|
||||
final void Function(int idx) onSelected;
|
||||
final double height;
|
||||
|
||||
const Picker({
|
||||
super.key,
|
||||
required this.items,
|
||||
required this.onSelected,
|
||||
this.height = 157,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pad = (height - 37) / 2;
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
top: pad,
|
||||
bottom: pad,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Container(
|
||||
height: 37,
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(7)),
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
),
|
||||
ListWheelScrollView.useDelegate(
|
||||
itemExtent: 37,
|
||||
diameterRatio: 2.7,
|
||||
controller: FixedExtentScrollController(initialItem: 0),
|
||||
onSelectedItemChanged: (idx) => onSelected(idx),
|
||||
physics: const FixedExtentScrollPhysics(),
|
||||
childDelegate: ListWheelChildBuilderDelegate(
|
||||
builder: (context, index) => Center(
|
||||
child: items[index],
|
||||
),
|
||||
childCount: items.length,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@ import '../../data/model/pkg/upgrade_info.dart';
|
||||
import '../../data/model/server/server_private_info.dart';
|
||||
import '../../data/model/server/snippet.dart';
|
||||
import 'popup_menu.dart';
|
||||
import 'tag.dart';
|
||||
|
||||
class ServerFuncBtnsTopRight extends StatelessWidget {
|
||||
final ServerPrivateInfo spi;
|
||||
@@ -96,13 +95,10 @@ void _onTapMoreBtns(
|
||||
);
|
||||
break;
|
||||
case ServerTabMenuType.snippet:
|
||||
final snippets = await showDialog<List<Snippet>>(
|
||||
context: context,
|
||||
builder: (_) => TagPicker<Snippet>(
|
||||
final snippets = await context.showPickDialog<Snippet>(
|
||||
items: Pros.snippet.snippets,
|
||||
tags: Pros.server.tags.toSet(),
|
||||
),
|
||||
);
|
||||
name: (e) => e.name,
|
||||
);
|
||||
if (snippets == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:toolbox/data/res/ui.dart';
|
||||
import 'package:toolbox/view/widget/input_field.dart';
|
||||
import 'package:toolbox/view/widget/round_rect_card.dart';
|
||||
|
||||
import '../../data/model/app/tag_pickable.dart';
|
||||
import '../../data/res/color.dart';
|
||||
|
||||
const _kTagBtnHeight = 31.0;
|
||||
@@ -179,119 +178,6 @@ class _TagEditorState extends State<TagEditor> {
|
||||
}
|
||||
}
|
||||
|
||||
class TagPicker<T extends TagPickable> extends StatefulWidget {
|
||||
final List<T> items;
|
||||
final Set<String> tags;
|
||||
|
||||
const TagPicker({
|
||||
Key? key,
|
||||
required this.items,
|
||||
required this.tags,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TagPickerState<T> createState() => _TagPickerState<T>();
|
||||
}
|
||||
|
||||
class _TagPickerState<T extends TagPickable> extends State<TagPicker<T>> {
|
||||
late MediaQueryData _media;
|
||||
final List<T> _selected = [];
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
_media = MediaQuery.of(context);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final children = <Widget>[];
|
||||
if (widget.tags.isNotEmpty) {
|
||||
children.add(Text(l10n.tag));
|
||||
children.add(UIs.height13);
|
||||
children.add(SizedBox(
|
||||
height: _kTagBtnHeight,
|
||||
width: _media.size.width * 0.7,
|
||||
child: _buildTags(),
|
||||
));
|
||||
}
|
||||
if (widget.items.isNotEmpty) {
|
||||
children.add(Text(l10n.all));
|
||||
children.add(UIs.height13);
|
||||
children.add(SizedBox(
|
||||
height: _kTagBtnHeight,
|
||||
width: _media.size.width * 0.7,
|
||||
child: _buildItems(),
|
||||
));
|
||||
}
|
||||
final child = widget.tags.isEmpty && widget.items.isEmpty
|
||||
? Text(l10n.noOptions)
|
||||
: Column(mainAxisSize: MainAxisSize.min, children: children);
|
||||
return AlertDialog(
|
||||
title: Text(l10n.choose),
|
||||
content: child,
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => context.pop(_selected),
|
||||
child: Text(l10n.ok),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTags() {
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: widget.tags.length,
|
||||
itemBuilder: (_, idx) {
|
||||
final item = widget.tags.elementAt(idx);
|
||||
final isEnable =
|
||||
widget.items.where((ele) => ele.containsTag(item)).every(
|
||||
(ele) => _selected.contains(ele),
|
||||
);
|
||||
return TagBtn(
|
||||
isEnable: isEnable,
|
||||
onTap: () {
|
||||
if (isEnable) {
|
||||
_selected.removeWhere(
|
||||
(ele) => ele.containsTag(item),
|
||||
);
|
||||
} else {
|
||||
_selected.addAll(widget.items.where(
|
||||
(ele) => ele.containsTag(item),
|
||||
));
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
content: item,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItems() {
|
||||
return ListView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemCount: widget.items.length,
|
||||
itemBuilder: (context, index) {
|
||||
final e = widget.items[index];
|
||||
return TagBtn(
|
||||
isEnable: _selected.contains(e),
|
||||
onTap: () {
|
||||
if (_selected.contains(e)) {
|
||||
_selected.remove(e);
|
||||
} else {
|
||||
_selected.add(e);
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
content: e.tagName,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TagSwitcher extends StatelessWidget {
|
||||
final List<String> tags;
|
||||
final double width;
|
||||
|
||||
Reference in New Issue
Block a user