diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n.dart b/.dart_tool/flutter_gen/gen_l10n/l10n.dart index 5f6fe09d..8c3a4835 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n.dart @@ -300,6 +300,12 @@ abstract class S { /// **'Delete'** String get delete; + /// No description provided for @disabled. + /// + /// In en, this message translates to: + /// **'Disabled'** + String get disabled; + /// No description provided for @disconnected. /// /// In en, this message translates to: @@ -372,6 +378,12 @@ abstract class S { /// **'Edit'** String get edit; + /// No description provided for @editVirtKeys. + /// + /// In en, this message translates to: + /// **'Edit virtual keys'** + String get editVirtKeys; + /// No description provided for @editor. /// /// In en, this message translates to: diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart index 6249d5f3..ecf988dd 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_de.dart @@ -106,6 +106,9 @@ class SDe extends S { @override String get delete => 'Löschen'; + @override + String get disabled => 'Disabled'; + @override String get disconnected => 'Disconnected'; @@ -152,6 +155,9 @@ class SDe extends S { @override String get edit => 'Bearbeiten'; + @override + String get editVirtKeys => 'Edit virtual keys'; + @override String get editor => 'Editor'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart index 8b3da348..925c4609 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_en.dart @@ -106,6 +106,9 @@ class SEn extends S { @override String get delete => 'Delete'; + @override + String get disabled => 'Disabled'; + @override String get disconnected => 'Disconnected'; @@ -152,6 +155,9 @@ class SEn extends S { @override String get edit => 'Edit'; + @override + String get editVirtKeys => 'Edit virtual keys'; + @override String get editor => 'Editor'; diff --git a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart index e1e9714d..af1c473b 100644 --- a/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart +++ b/.dart_tool/flutter_gen/gen_l10n/l10n_zh.dart @@ -106,6 +106,9 @@ class SZh extends S { @override String get delete => '删除'; + @override + String get disabled => '已禁用'; + @override String get disconnected => '连接断开'; @@ -152,6 +155,9 @@ class SZh extends S { @override String get edit => '编辑'; + @override + String get editVirtKeys => '编辑虚拟按键'; + @override String get editor => '编辑器'; @@ -743,6 +749,9 @@ class SZhTw extends SZh { @override String get delete => '刪除'; + @override + String get disabled => '已禁用'; + @override String get disconnected => '連接斷開'; @@ -789,6 +798,9 @@ class SZhTw extends SZh { @override String get edit => '編輯'; + @override + String get editVirtKeys => '編輯虛擬按鍵'; + @override String get editor => '編輯器'; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index ec1c6228..5d27faa3 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,6 +6,8 @@ PODS: - Flutter (1.0.0) - flutter_native_splash (0.0.1): - Flutter + - flutter_volume_controller (0.0.1): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -23,6 +25,7 @@ DEPENDENCIES: - file_picker (from `.symlinks/plugins/file_picker/ios`) - Flutter (from `Flutter`) - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) + - flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - plain_notification_token (from `.symlinks/plugins/plain_notification_token/ios`) - r_upgrade (from `.symlinks/plugins/r_upgrade/ios`) @@ -38,6 +41,8 @@ EXTERNAL SOURCES: :path: Flutter flutter_native_splash: :path: ".symlinks/plugins/flutter_native_splash/ios" + flutter_volume_controller: + :path: ".symlinks/plugins/flutter_volume_controller/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" plain_notification_token: @@ -54,6 +59,7 @@ SPEC CHECKSUMS: file_picker: 1d63c4949e05e386da864365f8c13e1e64787675 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef + flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529 path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 plain_notification_token: b36467dc91939a7b6754267c701bbaca14996ee1 r_upgrade: 44d715c61914cce3d01ea225abffe894fd51c114 @@ -62,4 +68,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 7fb15c416f8685fca4966867a8da218ec592ec2e -COCOAPODS: 1.11.1 +COCOAPODS: 1.12.1 diff --git a/lib/core/extension/order.dart b/lib/core/extension/order.dart index 582c336a..4a33a2f0 100644 --- a/lib/core/extension/order.dart +++ b/lib/core/extension/order.dart @@ -1,9 +1,9 @@ import 'package:toolbox/core/persistant_store.dart'; -typedef StringOrder = List; +typedef Order = List; -extension StringOrderX on StringOrder { - void move(int oldIndex, int newIndex, StoreProperty property) { +extension OrderX on Order { + void move(int oldIndex, int newIndex, StoreProperty> property) { if (oldIndex == newIndex) return; if (oldIndex < newIndex) { newIndex -= 1; @@ -14,17 +14,17 @@ extension StringOrderX on StringOrder { property.put(this); } - void update(String id, String newId) { + void update(T id, T newId) { final index = indexOf(id); if (index == -1) return; this[index] = newId; } - int index(String id) { + int index(T id) { return indexOf(id); } - void moveById(String oid, String nid, StoreProperty property) { + void moveById(T oid, T nid, StoreProperty> property) { final index = indexOf(oid); if (index == -1) return; final newIndex = indexOf(nid); diff --git a/lib/data/model/ssh/virtual_key.dart b/lib/data/model/ssh/virtual_key.dart index fe61cbc8..00b560c5 100644 --- a/lib/data/model/ssh/virtual_key.dart +++ b/lib/data/model/ssh/virtual_key.dart @@ -1,20 +1,151 @@ import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; import 'package:xterm/core.dart'; -class VirtualKey { - final String text; - final bool toggleable; - final TerminalKey? key; - final IconData? icon; - final VirtualKeyFunc? func; +part 'virtual_key.g.dart'; - VirtualKey( - this.text, { - this.key, - this.toggleable = false, - this.icon, - this.func, - }); +@HiveType(typeId: 4) +enum VirtKey { + @HiveField(0) + esc, + @HiveField(1) + alt, + @HiveField(2) + home, + @HiveField(3) + up, + @HiveField(4) + end, + @HiveField(5) + file, + @HiveField(6) + snippet, + @HiveField(7) + tab, + @HiveField(8) + ctrl, + @HiveField(9) + left, + @HiveField(10) + down, + @HiveField(11) + right, + @HiveField(12) + paste, + @HiveField(13) + ime, + @HiveField(14) + pgup, + @HiveField(15) + pgdn; + + String get text { + switch (this) { + case VirtKey.pgdn: + return 'PgDn'; + case VirtKey.pgup: + return 'PgUp'; + default: + if (name.length > 1) { + return name.substring(0, 1).toUpperCase() + name.substring(1); + } + return name; + } + } + + TerminalKey? get key { + switch (this) { + case VirtKey.esc: + return TerminalKey.escape; + case VirtKey.alt: + return TerminalKey.alt; + case VirtKey.home: + return TerminalKey.home; + case VirtKey.up: + return TerminalKey.arrowUp; + case VirtKey.end: + return TerminalKey.end; + case VirtKey.tab: + return TerminalKey.tab; + case VirtKey.ctrl: + return TerminalKey.control; + case VirtKey.left: + return TerminalKey.arrowLeft; + case VirtKey.down: + return TerminalKey.arrowDown; + case VirtKey.right: + return TerminalKey.arrowRight; + case VirtKey.pgup: + return TerminalKey.pageUp; + case VirtKey.pgdn: + return TerminalKey.pageDown; + default: + return null; + } + } + + IconData? get icon { + switch (this) { + case VirtKey.up: + return Icons.arrow_upward; + case VirtKey.left: + return Icons.arrow_back; + case VirtKey.down: + return Icons.arrow_downward; + case VirtKey.right: + return Icons.arrow_forward; + case VirtKey.file: + return Icons.file_open; + case VirtKey.snippet: + return Icons.code; + case VirtKey.paste: + return Icons.paste; + case VirtKey.ime: + return Icons.keyboard_hide; + default: + return null; + } + } + + // Use [VirtualKeyFunc] instead of [VirtKey] + // This can help linter to enum all [VirtualKeyFunc] + // and make sure all [VirtualKeyFunc] are handled + VirtualKeyFunc? get func { + switch (this) { + case VirtKey.file: + return VirtualKeyFunc.file; + case VirtKey.snippet: + return VirtualKeyFunc.snippet; + case VirtKey.paste: + return VirtualKeyFunc.paste; + case VirtKey.ime: + return VirtualKeyFunc.toggleIME; + default: + return null; + } + } + + bool get toggleable { + switch (this) { + case VirtKey.alt: + case VirtKey.ctrl: + return true; + default: + return false; + } + } + + bool get canLongPress { + switch (this) { + case VirtKey.up: + case VirtKey.left: + case VirtKey.down: + case VirtKey.right: + return true; + default: + return false; + } + } } enum VirtualKeyFunc { toggleIME, backspace, copy, paste, snippet, file } diff --git a/lib/data/model/ssh/virtual_key.g.dart b/lib/data/model/ssh/virtual_key.g.dart new file mode 100644 index 00000000..3bac7a60 --- /dev/null +++ b/lib/data/model/ssh/virtual_key.g.dart @@ -0,0 +1,116 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'virtual_key.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class VirtKeyAdapter extends TypeAdapter { + @override + final int typeId = 4; + + @override + VirtKey read(BinaryReader reader) { + switch (reader.readByte()) { + case 0: + return VirtKey.esc; + case 1: + return VirtKey.alt; + case 2: + return VirtKey.home; + case 3: + return VirtKey.up; + case 4: + return VirtKey.end; + case 5: + return VirtKey.file; + case 6: + return VirtKey.snippet; + case 7: + return VirtKey.tab; + case 8: + return VirtKey.ctrl; + case 9: + return VirtKey.left; + case 10: + return VirtKey.down; + case 11: + return VirtKey.right; + case 12: + return VirtKey.paste; + case 13: + return VirtKey.ime; + case 14: + return VirtKey.pgup; + case 15: + return VirtKey.pgdn; + default: + return VirtKey.esc; + } + } + + @override + void write(BinaryWriter writer, VirtKey obj) { + switch (obj) { + case VirtKey.esc: + writer.writeByte(0); + break; + case VirtKey.alt: + writer.writeByte(1); + break; + case VirtKey.home: + writer.writeByte(2); + break; + case VirtKey.up: + writer.writeByte(3); + break; + case VirtKey.end: + writer.writeByte(4); + break; + case VirtKey.file: + writer.writeByte(5); + break; + case VirtKey.snippet: + writer.writeByte(6); + break; + case VirtKey.tab: + writer.writeByte(7); + break; + case VirtKey.ctrl: + writer.writeByte(8); + break; + case VirtKey.left: + writer.writeByte(9); + break; + case VirtKey.down: + writer.writeByte(10); + break; + case VirtKey.right: + writer.writeByte(11); + break; + case VirtKey.paste: + writer.writeByte(12); + break; + case VirtKey.ime: + writer.writeByte(13); + break; + case VirtKey.pgup: + writer.writeByte(14); + break; + case VirtKey.pgdn: + writer.writeByte(15); + break; + } + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is VirtKeyAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index eb85c4b9..79b100c2 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -23,8 +23,8 @@ typedef ServersMap = Map; class ServerProvider extends BusyProvider { final ServersMap _servers = {}; ServersMap get servers => _servers; - final StringOrder _serverOrder = []; - StringOrder get serverOrder => _serverOrder; + final Order _serverOrder = []; + Order get serverOrder => _serverOrder; final List _tags = []; List get tags => _tags; diff --git a/lib/data/res/default.dart b/lib/data/res/default.dart index 9e4325ef..daba4e5e 100644 --- a/lib/data/res/default.dart +++ b/lib/data/res/default.dart @@ -1,5 +1,7 @@ import 'dart:ui'; +import 'package:toolbox/data/model/ssh/virtual_key.dart'; + // default server details page cards order const defaultDetailCardOrder = [ 'uptime', @@ -20,6 +22,23 @@ const defaultDiskIgnorePath = [ 'none', ]; +const defaultSSHVirtKeys = [ + VirtKey.esc, + VirtKey.alt, + VirtKey.home, + VirtKey.up, + VirtKey.end, + VirtKey.file, + VirtKey.snippet, + VirtKey.tab, + VirtKey.ctrl, + VirtKey.left, + VirtKey.down, + VirtKey.right, + VirtKey.paste, + VirtKey.ime, +]; + const defaultPrimaryColor = Color.fromARGB(255, 145, 58, 31); const defaultLaunchPageIdx = 0; diff --git a/lib/data/res/virtual_key.dart b/lib/data/res/virtual_key.dart deleted file mode 100644 index 62f3dcad..00000000 --- a/lib/data/res/virtual_key.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:xterm/core.dart'; - -import '../model/ssh/virtual_key.dart'; - -final virtualKeys = [ - VirtualKey('Esc', key: TerminalKey.escape), - VirtualKey('Alt', key: TerminalKey.alt, toggleable: true), - VirtualKey('Home', key: TerminalKey.home), - VirtualKey('Up', key: TerminalKey.arrowUp, icon: Icons.arrow_upward), - VirtualKey('End', key: TerminalKey.end), - VirtualKey( - 'File', - func: VirtualKeyFunc.file, - icon: Icons.file_open, - ), - VirtualKey('Snippet', func: VirtualKeyFunc.snippet, icon: Icons.code), - VirtualKey('Tab', key: TerminalKey.tab), - VirtualKey('Ctrl', key: TerminalKey.control, toggleable: true), - VirtualKey('Left', key: TerminalKey.arrowLeft, icon: Icons.arrow_back), - VirtualKey('Down', key: TerminalKey.arrowDown, icon: Icons.arrow_downward), - VirtualKey('Right', key: TerminalKey.arrowRight, icon: Icons.arrow_forward), - VirtualKey('Paste', func: VirtualKeyFunc.paste, icon: Icons.paste), - VirtualKey( - 'IME', - func: VirtualKeyFunc.toggleIME, - icon: Icons.keyboard_hide, - ), -]; diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 4169a03f..1e962af2 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; import 'package:toolbox/core/persistant_store.dart'; import 'package:toolbox/core/utils/platform.dart'; +import 'package:toolbox/data/model/ssh/virtual_key.dart'; import '../res/default.dart'; class SettingStore extends PersistentStore { - StoreProperty get primaryColor => property( + StoreProperty get primaryColor => property( 'primaryColor', defaultValue: 4287106639, ); @@ -76,4 +77,7 @@ class SettingStore extends PersistentStore { StoreProperty get keyboardType => property('keyboardType', defaultValue: TextInputType.text.index); + + StoreProperty> get sshVirtKeys => + property('sshVirtKeys', defaultValue: defaultSSHVirtKeys); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index cc3385fd..85e218b9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -34,6 +34,7 @@ "debug": "Debug", "decode": "Decode", "delete": "Delete", + "disabled": "Disabled", "disconnected": "Disconnected", "diskIgnorePath": "Ignore path for disk", "dl2Local": "Download {fileName} to local?", @@ -46,6 +47,7 @@ "download": "Download", "downloadStatus": "{percent}% of {size}", "edit": "Edit", + "editVirtKeys": "Edit virtual keys", "editor": "Editor", "encode": "Encode", "error": "Error", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 13fc64f2..55d180a7 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -34,6 +34,7 @@ "debug": "调试", "decode": "解码", "delete": "删除", + "disabled": "已禁用", "disconnected": "连接断开", "diskIgnorePath": "忽略的磁盘路径", "dl2Local": "下载 {fileName} 到本地?", @@ -46,6 +47,7 @@ "download": "下载", "downloadStatus": "{size} 的 {percent}%", "edit": "编辑", + "editVirtKeys": "编辑虚拟按键", "editor": "编辑器", "encode": "编码", "error": "错误", diff --git a/lib/l10n/app_zh_tw.arb b/lib/l10n/app_zh_tw.arb index 8e56f3e7..79d54c81 100644 --- a/lib/l10n/app_zh_tw.arb +++ b/lib/l10n/app_zh_tw.arb @@ -34,6 +34,7 @@ "debug": "調試", "decode": "解碼", "delete": "刪除", + "disabled": "已禁用", "disconnected": "連接斷開", "diskIgnorePath": "忽略的磁盤路徑", "dl2Local": "下載 {fileName} 到本地?", @@ -46,6 +47,7 @@ "download": "下載", "downloadStatus": "{size} 的 {percent}%", "edit": "編輯", + "editVirtKeys": "編輯虛擬按鍵", "editor": "編輯器", "encode": "編碼", "error": "錯誤", diff --git a/lib/main.dart b/lib/main.dart index aea7fc54..9eb24266 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:logging/logging.dart'; import 'package:provider/provider.dart'; +import 'package:toolbox/data/model/ssh/virtual_key.dart'; import 'app.dart'; import 'core/analysis.dart'; @@ -47,6 +48,7 @@ Future initApp() async { Future initHive() async { await Hive.initFlutter(); Hive.registerAdapter(SnippetAdapter()); + Hive.registerAdapter(VirtKeyAdapter()); Hive.registerAdapter(PrivateKeyInfoAdapter()); Hive.registerAdapter(ServerPrivateInfoAdapter()); } diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index 5eab7add..b3f45a41 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -3,7 +3,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/navigator.dart'; import 'package:toolbox/core/route.dart'; -import 'package:toolbox/view/page/ssh.dart'; +import 'package:toolbox/view/page/ssh/term.dart'; import 'package:toolbox/view/widget/input_field.dart'; import '../../core/utils/ui.dart'; diff --git a/lib/view/page/server/detail.dart b/lib/view/page/server/detail.dart index b9474a1e..76f52ed4 100644 --- a/lib/view/page/server/detail.dart +++ b/lib/view/page/server/detail.dart @@ -32,7 +32,7 @@ class _ServerDetailPageState extends State with SingleTickerProviderStateMixin { late MediaQueryData _media; late S _s; - final StringOrder _cardsOrder = []; + final Order _cardsOrder = []; final _setting = locator(); @override diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 2aa87e98..03b56200 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -25,7 +25,7 @@ import '../../widget/round_rect_card.dart'; import '../docker.dart'; import '../pkg.dart'; import '../sftp/remote.dart'; -import '../ssh.dart'; +import '../ssh/term.dart'; import 'detail.dart'; import 'edit.dart'; diff --git a/lib/view/page/setting.dart b/lib/view/page/setting.dart index 2aae1040..50b0e2f9 100644 --- a/lib/view/page/setting.dart +++ b/lib/view/page/setting.dart @@ -8,7 +8,9 @@ import 'package:flutter_material_color_picker/flutter_material_color_picker.dart import 'package:provider/provider.dart'; import 'package:toolbox/core/extension/locale.dart'; import 'package:toolbox/core/extension/navigator.dart'; +import 'package:toolbox/core/route.dart'; import 'package:toolbox/data/model/app/tab.dart'; +import 'package:toolbox/view/page/ssh/virt_key_setting.dart'; import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/value_notifier.dart'; @@ -165,6 +167,7 @@ class _SettingPageState extends State { _buildTermFontSize(), _buildSSHVirtualKeyAutoOff(), _buildKeyboardType(), + _buildSSHVirtKeys(), ].map((e) => RoundRectCard(e)).toList(), ); } @@ -739,4 +742,15 @@ class _SettingPageState extends State { }, ); } + + Widget _buildSSHVirtKeys() { + return ListTile( + title: Text(_s.editVirtKeys), + trailing: const Icon(Icons.arrow_forward_ios, size: 13), + onTap: () => AppRoute( + const SSHVirtKeySettingPage(), + 'ssh virt key edit', + ).go(context), + ); + } } diff --git a/lib/view/page/ssh.dart b/lib/view/page/ssh/term.dart similarity index 81% rename from lib/view/page/ssh.dart rename to lib/view/page/ssh/term.dart index 6fc35dda..a37ed445 100644 --- a/lib/view/page/ssh.dart +++ b/lib/view/page/ssh/term.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:convert'; import 'package:dartssh2/dartssh2.dart'; @@ -10,20 +11,19 @@ import 'package:toolbox/core/extension/navigator.dart'; import 'package:toolbox/data/res/server_cmd.dart'; import 'package:xterm/xterm.dart'; -import '../../core/route.dart'; -import '../../core/utils/platform.dart'; -import '../../core/utils/misc.dart'; -import '../../core/utils/ui.dart'; -import '../../core/utils/server.dart'; -import '../../data/model/server/server_private_info.dart'; -import '../../data/model/ssh/virtual_key.dart'; -import '../../data/provider/virtual_keyboard.dart'; -import '../../data/res/color.dart'; -import '../../data/res/terminal.dart'; -import '../../data/res/virtual_key.dart'; -import '../../data/store/setting.dart'; -import '../../locator.dart'; -import 'sftp/remote.dart'; +import '../../../core/route.dart'; +import '../../../core/utils/platform.dart'; +import '../../../core/utils/misc.dart'; +import '../../../core/utils/ui.dart'; +import '../../../core/utils/server.dart'; +import '../../../data/model/server/server_private_info.dart'; +import '../../../data/model/ssh/virtual_key.dart'; +import '../../../data/provider/virtual_keyboard.dart'; +import '../../../data/res/color.dart'; +import '../../../data/res/terminal.dart'; +import '../../../data/store/setting.dart'; +import '../../../locator.dart'; +import '../sftp/remote.dart'; class SSHPage extends StatefulWidget { final ServerPrivateInfo spi; @@ -35,22 +35,26 @@ class SSHPage extends StatefulWidget { } class _SSHPageState extends State { - late final _terminal = Terminal(inputHandler: _keyboard); - SSHClient? _client; - late SSHSession _session; final _keyboard = locator(); final _setting = locator(); - late MediaQueryData _media; - final _virtualKeyboardHeight = 57.0; + late final _terminal = Terminal(inputHandler: _keyboard); final TerminalController _terminalController = TerminalController(); final ContextMenuController _menuController = ContextMenuController(); + final List> _virtKeysList = []; + + late MediaQueryData _media; late TextStyle _menuTextStyle; late S _s; late TerminalStyle _terminalStyle; late TerminalTheme _terminalTheme; late TextInputType _keyboardType; + late SSHSession _session; + late double _virtKeyWidth; + late double _virtKeysHeight; - var _isDark = false; + bool _isDark = false; + Timer? _virtKeyLongPressTimer; + SSHClient? _client; @override void initState() { @@ -62,7 +66,8 @@ class _SSHPageState extends State { ); _terminalStyle = TerminalStyle.fromTextStyle(textStyle); _keyboardType = TextInputType.values[_setting.keyboardType.fetch()!]; - initTerminal(); + _initTerminal(); + _initVirtKeys(); } @override @@ -73,6 +78,9 @@ class _SSHPageState extends State { _menuTextStyle = TextStyle(color: contentColor.resolve(context)); _s = S.of(context)!; _terminalTheme = _isDark ? termDarkTheme : termLightTheme; + // Calculate virtkey width / height + _virtKeyWidth = _media.size.width / 7; + _virtKeysHeight = _media.size.height * 0.047 * _virtKeysList.length; } @override @@ -100,7 +108,7 @@ class _SSHPageState extends State { Widget _buildBody() { return SizedBox( height: _media.size.height - - _virtualKeyboardHeight - + _virtKeysHeight - _media.padding.bottom - _media.padding.top, child: TerminalView( @@ -125,7 +133,7 @@ class _SSHPageState extends State { curve: Curves.fastOutSlowIn, child: Container( color: _terminalTheme.background, - height: _virtualKeyboardHeight, + height: _virtKeysHeight, child: Consumer( builder: (_, __, ___) => _buildVirtualKey(), ), @@ -135,23 +143,18 @@ class _SSHPageState extends State { } Widget _buildVirtualKey() { - final half = virtualKeys.length ~/ 2; - final top = virtualKeys.sublist(0, half); - final bottom = virtualKeys.sublist(half); + final rows = _virtKeysList + .map((e) => Row( + children: e.map((ee) => _buildVirtualKeyItem(ee)).toList(), + )) + .toList(); return Column( mainAxisSize: MainAxisSize.min, - children: [ - Row( - children: top.map((e) => _buildVirtualKeyItem(e)).toList(), - ), - Row( - children: bottom.map((e) => _buildVirtualKeyItem(e)).toList(), - ) - ], + children: rows, ); } - Widget _buildVirtualKeyItem(VirtualKey item) { + Widget _buildVirtualKeyItem(VirtKey item) { var selected = false; switch (item.key) { case TerminalKey.control: @@ -176,9 +179,19 @@ class _SSHPageState extends State { return InkWell( onTap: () => _doVirtualKey(item), + onTapDown: (details) { + if (item.canLongPress) { + _virtKeyLongPressTimer = Timer.periodic( + const Duration(milliseconds: 137), + (_) => _doVirtualKey(item), + ); + } + }, + onTapCancel: () => _virtKeyLongPressTimer?.cancel(), + onTapUp: (_) => _virtKeyLongPressTimer?.cancel(), child: SizedBox( - width: _media.size.width / (virtualKeys.length / 2), - height: _virtualKeyboardHeight / 2, + width: _virtKeyWidth, + height: _virtKeysHeight / _virtKeysList.length, child: Center( child: child, ), @@ -186,7 +199,7 @@ class _SSHPageState extends State { ); } - void _doVirtualKey(VirtualKey item) { + void _doVirtualKey(VirtKey item) { if (item.func != null) { _doVirtualKeyFunc(item.func!); return; @@ -326,7 +339,19 @@ class _SSHPageState extends State { _terminal.write('$p0\r\n'); } - Future initTerminal() async { + void _initVirtKeys() { + final virtKeys = _setting.sshVirtKeys.fetch()!; + + for (int len = 0; len < virtKeys.length; len += 7) { + if (len + 7 > virtKeys.length) { + _virtKeysList.add(virtKeys.sublist(len)); + } else { + _virtKeysList.add(virtKeys.sublist(len, len + 7)); + } + } + } + + Future _initTerminal() async { _write('Connecting...\r\n'); _client = await genClient( diff --git a/lib/view/page/ssh/virt_key_setting.dart b/lib/view/page/ssh/virt_key_setting.dart new file mode 100644 index 00000000..033fa90d --- /dev/null +++ b/lib/view/page/ssh/virt_key_setting.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:toolbox/core/extension/order.dart'; +import 'package:toolbox/core/utils/ui.dart'; +import 'package:toolbox/data/model/ssh/virtual_key.dart'; +import 'package:toolbox/data/store/setting.dart'; +import 'package:toolbox/locator.dart'; + +class SSHVirtKeySettingPage extends StatefulWidget { + const SSHVirtKeySettingPage({Key? key}) : super(key: key); + + @override + _SSHVirtKeySettingPageState createState() => _SSHVirtKeySettingPageState(); +} + +class _SSHVirtKeySettingPageState extends State { + final _setting = locator(); + late S _s; + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + _s = S.of(context)!; + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_s.editVirtKeys), + ), + body: _buildBody(), + ); + } + + Widget _buildBody() { + final keys = List.from(_setting.sshVirtKeys.fetch()!); + final disabled = VirtKey.values.where((e) => !keys.contains(e)).toList(); + final allKeys = [...keys, ...disabled]; + return ReorderableListView.builder( + padding: const EdgeInsets.fromLTRB(11, 3, 0, 3), + itemBuilder: (_, idx) { + final key = allKeys[idx]; + return ListTile( + key: ValueKey(idx), + title: _buildTitle(key), + leading: const Icon(Icons.drag_handle, color: Colors.grey), + trailing: _buildCheckBox(keys, key, idx, idx < keys.length), + ); + }, + itemCount: allKeys.length, + onReorder: (o, n) { + if (o >= keys.length || n >= keys.length) { + showSnackBar(context, Text(_s.disabled)); + return; + } + keys.moveById(keys[o], keys[n], _setting.sshVirtKeys); + setState(() { + + }); + }, + ); + } + + Widget _buildTitle(VirtKey key) { + return key.icon == null + ? Text( + key.text, + textAlign: TextAlign.center, + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(key.text), + const SizedBox(width: 10), + Icon(key.icon), + ], + ); + } + + Widget _buildCheckBox(List keys, VirtKey key, int idx, bool value) { + return Checkbox( + value: value, + onChanged: (val) { + if (val == null) return; + if (val) { + if (idx >= keys.length) { + keys.add(key); + } else { + keys.insert(idx - 1, key); + } + } else { + keys.remove(key); + } + _setting.sshVirtKeys.put(keys); + setState(() {}); + }, + ); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index f6f23bfe..84b1feab 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); + flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index f16b4c34..3e922b18 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_volume_controller url_launcher_linux ) diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 169c3fdc..bd594ba3 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,11 +5,13 @@ import FlutterMacOS import Foundation +import flutter_volume_controller import path_provider_foundation import share_plus import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index 5897f40e..ba2190c2 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - flutter_volume_controller (0.0.1): + - FlutterMacOS - FlutterMacOS (1.0.0) - path_provider_foundation (0.0.1): - Flutter @@ -9,12 +11,15 @@ PODS: - FlutterMacOS DEPENDENCIES: + - flutter_volume_controller (from `Flutter/ephemeral/.symlinks/plugins/flutter_volume_controller/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) EXTERNAL SOURCES: + flutter_volume_controller: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_volume_controller/macos FlutterMacOS: :path: Flutter/ephemeral path_provider_foundation: @@ -25,6 +30,7 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos SPEC CHECKSUMS: + flutter_volume_controller: 25d09126b0d695560f11c80b1311d5063fed882f FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 share_plus: 76dd39142738f7a68dd57b05093b5e8193f220f7 diff --git a/pubspec.lock b/pubspec.lock index ec4a60a1..34d2289d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -377,6 +377,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_volume_controller: + dependency: "direct main" + description: + name: flutter_volume_controller + sha256: "5e7d1b63d051c881450a98155024219079a1cde0cf66ef895eb96dc9d8a7f670" + url: "https://pub.dev" + source: hosted + version: "1.2.6" flutter_web_plugins: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 48a2453f..7adddd46 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,6 +66,7 @@ dependencies: highlight: ^0.7.0 flutter_highlight: ^0.7.0 code_text_field: ^1.1.0 + flutter_volume_controller: ^1.2.6 dev_dependencies: flutter_native_splash: ^2.1.6 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index c3384ec5..aed25025 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,10 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + FlutterVolumeControllerPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); SharePlusWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 01d38362..987dc278 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + flutter_volume_controller share_plus url_launcher_windows )