diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dc6aa1e5..9bfea105 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -586,7 +586,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -596,7 +596,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -720,7 +720,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -730,7 +730,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -748,7 +748,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist"; @@ -758,7 +758,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -779,7 +779,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -792,7 +792,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; @@ -818,7 +818,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -831,7 +831,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -854,7 +854,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_TEAM = BA88US33G6; GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; @@ -867,7 +867,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -890,7 +890,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -902,7 +902,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; @@ -931,7 +931,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -943,7 +943,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; @@ -969,7 +969,7 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 672; + CURRENT_PROJECT_VERSION = 674; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_PREVIEWS = YES; @@ -981,7 +981,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.672; + MARKETING_VERSION = 1.0.674; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd; PRODUCT_NAME = ServerBox; diff --git a/lib/core/extension/widget.dart b/lib/core/extension/widget.dart new file mode 100644 index 00000000..e5b3d2b4 --- /dev/null +++ b/lib/core/extension/widget.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; +import 'package:toolbox/core/utils/function.dart'; + +extension WidgetX on Widget { + Widget padding(EdgeInsetsGeometry padding) { + return Padding(padding: padding, child: this); + } + + Widget expanded({int flex = 1}) { + return Expanded(flex: flex, child: this); + } + + Widget center() { + return Center(child: this); + } + + Widget tap({ + VoidCallback? onTap, + bool disable = false, + VoidCallback? onLongTap, + VoidCallback? onDoubleTap, + }) { + if (disable) return this; + + return InkWell( + onTap: () => Funcs.throttle(onTap), + onLongPress: onLongTap, + onDoubleTap: onDoubleTap, + child: this, + ); + } +} diff --git a/lib/core/utils/function.dart b/lib/core/utils/function.dart new file mode 100644 index 00000000..ab0267b6 --- /dev/null +++ b/lib/core/utils/function.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +abstract final class Funcs { + static const int _defaultDurationTime = 377; + static const String _defaultThrottleId = 'default'; + static final Map startTimeMap = { + _defaultThrottleId: 0 + }; + + static void throttle( + VoidCallback? func, { + String id = _defaultThrottleId, + int duration = _defaultDurationTime, + Function? continueClick, + }) { + final currentTime = DateTime.now().millisecondsSinceEpoch; + if (currentTime - (startTimeMap[id] ?? 0) > duration) { + func?.call(); + startTimeMap[id] = DateTime.now().millisecondsSinceEpoch; + } else { + continueClick?.call(); + } + } +} diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 8f648c01..50e57324 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,9 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 672; - static const String engine = "3.16.2"; - static const String buildAt = "2023-12-11 11:40:12"; - static const int modifications = 3; + static const int build = 674; + static const String engine = "3.16.3"; + static const String buildAt = "2023-12-12 18:14:36"; + static const int modifications = 29; static const int script = 31; } diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 54350f2e..17d2f60d 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -51,10 +51,10 @@ class SettingStore extends PersistentStore { property('diskIgnorePath', Defaults.diskIgnorePath); /// Use double column servers page on Desktop - late final doubleColumnServersPage = property( - 'doubleColumnServersPage', - isDesktop, - ); + // late final doubleColumnServersPage = property( + // 'doubleColumnServersPage', + // isDesktop, + // ); /// Disk view: amount / IO late final serverTabPreferDiskAmount = property( diff --git a/lib/view/page/backup.dart b/lib/view/page/backup.dart index acd33b8f..2ae13952 100644 --- a/lib/view/page/backup.dart +++ b/lib/view/page/backup.dart @@ -53,7 +53,7 @@ class BackupPage extends StatelessWidget { Widget _buildTip() { return CardX( - ListTile( + child: ListTile( leading: const Icon(Icons.warning), title: Text(l10n.attention), subtitle: Text(l10n.backupTip, style: UIs.textGrey), @@ -63,7 +63,7 @@ class BackupPage extends StatelessWidget { Widget _buildFile(BuildContext context) { return CardX( - ExpandTile( + child: ExpandTile( leading: const Icon(Icons.file_open), title: Text(l10n.files), initiallyExpanded: true, @@ -94,7 +94,8 @@ class BackupPage extends StatelessWidget { Widget _buildIcloud(BuildContext context) { return CardX( - ListTile( + child: ListTile( + leading: const Icon(Icons.cloud), title: const Text('iCloud'), trailing: StoreSwitch( prop: Stores.setting.icloudSync, @@ -119,7 +120,7 @@ class BackupPage extends StatelessWidget { Widget _buildWebdav(BuildContext context) { return CardX( - ExpandTile( + child: ExpandTile( leading: const Icon(Icons.storage), title: const Text('WebDAV'), initiallyExpanded: !(isIOS || isMacOS), diff --git a/lib/view/page/docker.dart b/lib/view/page/docker.dart index b9aff00e..cdc0480d 100644 --- a/lib/view/page/docker.dart +++ b/lib/view/page/docker.dart @@ -214,7 +214,7 @@ class _DockerManagePageState extends State { _buildPs(), _buildImage(), _buildEditHost(), - ].map((e) => CardX(e)); + ].map((e) => CardX(child: e)); return ListView( padding: const EdgeInsets.all(7), children: items.toList(), diff --git a/lib/view/page/home.dart b/lib/view/page/home.dart index 36e6e200..65cb137d 100644 --- a/lib/view/page/home.dart +++ b/lib/view/page/home.dart @@ -241,7 +241,7 @@ class _HomePageState extends State title: Text('${l10n.about} & ${l10n.feedback}'), onTap: _showAboutDialog, ) - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ), ); } diff --git a/lib/view/page/ping.dart b/lib/view/page/ping.dart index 203b4d8a..59f3c3fb 100644 --- a/lib/view/page/ping.dart +++ b/lib/view/page/ping.dart @@ -119,7 +119,7 @@ class _PingPageState extends State final unknown = l10n.unknown; final ms = l10n.ms; return CardX( - ListTile( + child: ListTile( contentPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17), title: Text( result.serverName, diff --git a/lib/view/page/private_key/list.dart b/lib/view/page/private_key/list.dart index 4a490017..0a072afd 100644 --- a/lib/view/page/private_key/list.dart +++ b/lib/view/page/private_key/list.dart @@ -54,7 +54,7 @@ class _PrivateKeyListState extends State itemBuilder: (context, idx) { final item = key.pkis[idx]; return CardX( - ListTile( + child: ListTile( leading: Text( '#$idx', style: const TextStyle( diff --git a/lib/view/page/process.dart b/lib/view/page/process.dart index e9331c3d..5fb2e473 100644 --- a/lib/view/page/process.dart +++ b/lib/view/page/process.dart @@ -143,7 +143,8 @@ class _ProcessPageState extends State { ? Text(proc.pid.toString()) : TwoLineText(up: proc.pid.toString(), down: proc.user!); return CardX( - ListTile( + key: ValueKey(proc.pid), + child: ListTile( leading: SizedBox( width: _media.size.width / 6, child: leading, @@ -178,7 +179,6 @@ class _ProcessPageState extends State { selected: _lastFocusId == proc.pid, autofocus: _lastFocusId == proc.pid, ), - key: ValueKey(proc.pid), ); } diff --git a/lib/view/page/server/detail.dart b/lib/view/page/server/detail.dart index 999797eb..c40ab450 100644 --- a/lib/view/page/server/detail.dart +++ b/lib/view/page/server/detail.dart @@ -4,6 +4,7 @@ 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/order.dart'; +import 'package:toolbox/core/extension/widget.dart'; import 'package:toolbox/data/model/server/cpu.dart'; import 'package:toolbox/data/model/server/disk.dart'; import 'package:toolbox/data/model/server/net_speed.dart'; @@ -135,7 +136,7 @@ class _ServerDetailPageState extends State } return CardX( - ExpandTile( + child: ExpandTile( title: Align( alignment: Alignment.centerLeft, child: _buildAnimatedText( @@ -202,7 +203,7 @@ class _ServerDetailPageState extends State Widget _buildUpTimeAndSys(ServerStatus ss) { return CardX( - Padding( + child: Padding( padding: UIs.roundRectCardPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -230,7 +231,7 @@ class _ServerDetailPageState extends State final usedStr = used.toStringAsFixed(0); return CardX( - Padding( + child: Padding( padding: UIs.roundRectCardPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -275,7 +276,7 @@ class _ServerDetailPageState extends State final used = ss.swap.usedPercent * 100; final cached = ss.swap.cached / ss.swap.total * 100; return CardX( - Padding( + child: Padding( padding: UIs.roundRectCardPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.center, @@ -309,7 +310,7 @@ class _ServerDetailPageState extends State if (ss.nvdia == null) return UIs.placeholder; final children = ss.nvdia!.map((e) => _buildGpuItem(e)).toList(); return CardX( - ExpandTile( + child: ExpandTile( title: const Text('GPU'), leading: const Icon(Icons.memory, size: 17), initiallyExpanded: children.length <= 3, @@ -392,7 +393,7 @@ class _ServerDetailPageState extends State style: UIs.textSize11Grey, textScaler: _textFactor, ), - trailing: InkWell( + trailing: const Icon(Icons.info_outline, size: 17).tap( onTap: () { context.showRoundDialog( title: SizedBox( @@ -417,7 +418,6 @@ class _ServerDetailPageState extends State ], ); }, - child: const Icon(Icons.info_outline, size: 17), ), ); } @@ -433,7 +433,7 @@ class _ServerDetailPageState extends State final children = List.generate(disks.length, (idx) => _buildDiskItem(disks[idx], ss)); return CardX( - ExpandTile( + child: ExpandTile( title: Text(l10n.disk), childrenPadding: const EdgeInsets.only(bottom: 7), leading: const Icon(Icons.storage, size: 17), @@ -497,7 +497,7 @@ class _ServerDetailPageState extends State children.addAll(devices.map((e) => _buildNetSpeedItem(ns, e))); } return CardX( - ExpandTile( + child: ExpandTile( title: Row( children: [ Text(l10n.net), @@ -579,10 +579,11 @@ class _ServerDetailPageState extends State return UIs.placeholder; } return CardX( - ExpandTile( + child: ExpandTile( title: Text(l10n.temperature), leading: const Icon(Icons.ac_unit, size: 17), initiallyExpanded: ss.temps.devices.length <= 7, + childrenPadding: EdgeInsets.zero, children: ss.temps.devices .map((key) => _buildTemperatureItem(key, ss.temps.get(key))) .toList(), diff --git a/lib/view/page/server/edit.dart b/lib/view/page/server/edit.dart index 6975ffeb..df7d0729 100644 --- a/lib/view/page/server/edit.dart +++ b/lib/view/page/server/edit.dart @@ -127,16 +127,15 @@ class _ServerEditPageState extends State { )), UIs.height13, if (widget.spi?.server?.canViewDetails ?? false) - Row( - children: [ - Checkbox( - value: delScripts, - onChanged: (_) => setState( - () => delScripts = !delScripts, - ), - ), - Text(l10n.deleteScripts), - ], + CheckboxListTile( + value: delScripts, + onChanged: (_) => setState( + () => delScripts = !delScripts, + ), + controlAffinity: ListTileControlAffinity.leading, + subtitle: Text(l10n.deleteScripts), + tileColor: Colors.transparent, + contentPadding: EdgeInsets.zero, ) ], ); @@ -315,7 +314,7 @@ class _ServerEditPageState extends State { ), ); return CardX( - Padding( + child: Padding( padding: const EdgeInsets.symmetric(horizontal: 17), child: Column( children: tiles, @@ -375,7 +374,7 @@ class _ServerEditPageState extends State { onTap: () => _jumpServer.value = null, )); return CardX( - ExpandTile( + child: ExpandTile( leading: const Icon(Icons.map), initiallyExpanded: _jumpServer.value != null, title: Text(l10n.jumpServer), diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index 72785433..d3fdbd5a 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -8,6 +8,7 @@ import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/context/locale.dart'; import 'package:toolbox/core/extension/media_queryx.dart'; import 'package:toolbox/core/extension/ssh_client.dart'; +import 'package:toolbox/core/extension/widget.dart'; import 'package:toolbox/core/utils/platform/base.dart'; import 'package:toolbox/core/utils/share.dart'; import 'package:toolbox/data/model/app/shell_func.dart'; @@ -55,6 +56,7 @@ class _ServerPageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( + appBar: _buildTagsSwitcher(Pros.server), body: _buildBody(), floatingActionButton: FloatingActionButton( onPressed: () => AppRoute.serverEdit().go(context), @@ -81,10 +83,10 @@ class _ServerPageState extends State } final filtered = _filterServers(pro); - if (_useDoubleColumn && - Stores.setting.doubleColumnServersPage.fetch()) { - return _buildBodyMedium(pro: pro, filtered: filtered); - } + // if (_useDoubleColumn && + // Stores.setting.doubleColumnServersPage.fetch()) { + // return _buildBodyMedium(pro: pro, filtered: filtered); + // } return _buildBodySmall(provider: pro, filtered: filtered); }, ); @@ -99,7 +101,7 @@ class _ServerPageState extends State ); } - Widget _buildTagsSwitcher(ServerProvider provider) { + TagSwitcher _buildTagsSwitcher(ServerProvider provider) { return TagSwitcher( tags: provider.tags, width: _media.size.width, @@ -115,61 +117,33 @@ class _ServerPageState extends State required ServerProvider provider, required List filtered, EdgeInsets? padding = const EdgeInsets.fromLTRB(7, 0, 7, 7), - bool buildTags = true, }) { - final count = buildTags ? filtered.length + 2 : filtered.length + 1; + final count = filtered.length + 1; return ListView.builder( padding: padding, itemCount: count, itemBuilder: (_, index) { - if (index == 0 && buildTags) return _buildTagsSwitcher(provider); - // Issue #130 if (index == count - 1) return UIs.height77; - - if (buildTags) index--; return _buildEachServerCard(provider.pick(id: filtered[index])); }, ); } - Widget _buildBodyMedium({ - required ServerProvider pro, - required List filtered, - }) { - final left = filtered.where((e) => filtered.indexOf(e) % 2 == 0).toList(); - final right = filtered.where((e) => filtered.indexOf(e) % 2 == 1).toList(); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 7), - child: _buildTagsSwitcher(pro), - ), - Expanded( - child: Row( - children: [ - Expanded( - child: _buildBodySmall( - provider: pro, - filtered: left, - padding: const EdgeInsets.fromLTRB(7, 0, 0, 7), - buildTags: false, - ), - ), - Expanded( - child: _buildBodySmall( - provider: pro, - filtered: right, - padding: const EdgeInsets.fromLTRB(0, 0, 7, 7), - buildTags: false, - ), - ), - ], - )) - ], - ); - } + // Widget _buildBodyMedium({ + // required ServerProvider pro, + // required List filtered, + // }) { + // return GridView.builder( + // gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + // crossAxisCount: 2, + // ), + // itemCount: filtered.length, + // itemBuilder: (context, index) { + // return _buildEachServerCard(pro.pick(id: filtered[index])); + // }, + // ); + // } Widget _buildEachServerCard(Server? srv) { if (srv == null) { @@ -178,7 +152,7 @@ class _ServerPageState extends State return CardX( key: Key(srv.spi.id + (_tag ?? '')), - InkWell( + child: _buildRealServerCard(srv).padding(const EdgeInsets.all(13)).tap( onTap: () { if (srv.canViewDetails) { AppRoute.serverDetail(spi: srv.spi).go(context); @@ -186,7 +160,7 @@ class _ServerPageState extends State _showFailReason(srv.status); } }, - onLongPress: () { + onLongTap: () { if (srv.state == ServerState.finished) { final id = srv.spi.id; final cardStatus = getCardNoti(id); @@ -197,10 +171,6 @@ class _ServerPageState extends State AppRoute.serverEdit(spi: srv.spi).go(context); } }, - child: Padding( - padding: const EdgeInsets.all(13), - child: _buildRealServerCard(srv), - ), ), ); } diff --git a/lib/view/page/setting/android.dart b/lib/view/page/setting/android.dart index 045cc325..4a7608a1 100644 --- a/lib/view/page/setting/android.dart +++ b/lib/view/page/setting/android.dart @@ -43,7 +43,7 @@ class _AndroidSettingsPageState extends State { _buildAndroidWidgetSharedPreference(), if (BioAuth.isPlatformSupported) PlatformPublicSettings.buildBioAuth(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ), ); } diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index 480bded2..6968c182 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -12,6 +12,8 @@ import 'package:toolbox/core/extension/context/snackbar.dart'; import 'package:toolbox/core/extension/locale.dart'; import 'package:toolbox/core/extension/context/dialog.dart'; import 'package:toolbox/core/extension/stringx.dart'; +import 'package:toolbox/core/extension/widget.dart'; +import 'package:toolbox/core/utils/function.dart'; import 'package:toolbox/core/utils/platform/base.dart'; import 'package:toolbox/data/res/provider.dart'; import 'package:toolbox/data/res/rebuild.dart'; @@ -100,55 +102,53 @@ class _SettingPageState extends State { appBar: CustomAppBar( title: Text(l10n.setting), actions: [ - Padding( - padding: const EdgeInsets.only(right: 17), - child: InkWell( - onTap: () => context.showRoundDialog( - title: Text(l10n.attention), - child: Text(l10n.askContinue( - '${l10n.delete}: **${l10n.all}** ${l10n.setting}', - )), - actions: [ - TextButton( - onPressed: () { - _setting.box.deleteAll(_setting.box.keys); - context.pop(); - context.showSnackBar(l10n.success); - }, - child: Text( - l10n.ok, - style: const TextStyle(color: Colors.red), + const Icon(Icons.delete) + .tap( + onTap: () => context.showRoundDialog( + title: Text(l10n.attention), + child: Text(l10n.askContinue( + '${l10n.delete}: **${l10n.all}** ${l10n.setting}', + )), + actions: [ + TextButton( + onPressed: () { + _setting.box.deleteAll(_setting.box.keys); + context.pop(); + context.showSnackBar(l10n.success); + }, + child: Text( + l10n.ok, + style: const TextStyle(color: Colors.red), + ), ), - ), - ], - ), + ], + ), - /// Only for debug, this will cause the app to crash - onDoubleTap: () => context.showRoundDialog( - title: Text(l10n.attention), - child: Text(l10n.askContinue( - 'Delete all data from disk, and exit the app?', - )), - actions: [ - TextButton( - onPressed: () { - if (!BuildMode.isDebug) return; - Stores.docker.box.deleteFromDisk(); - Stores.server.box.deleteFromDisk(); - Stores.setting.box.deleteFromDisk(); - Stores.history.box.deleteFromDisk(); - Stores.snippet.box.deleteFromDisk(); - Stores.key.box.deleteFromDisk(); - exit(0); - }, - child: Text(l10n.ok, - style: const TextStyle(color: Colors.red)), - ), - ], - ), - child: const Icon(Icons.delete), - ), - ), + /// Only for debug, this will cause the app to crash + onDoubleTap: () => context.showRoundDialog( + title: Text(l10n.attention), + child: Text(l10n.askContinue( + 'Delete all data from disk, and exit the app?', + )), + actions: [ + TextButton( + onPressed: () { + if (!BuildMode.isDebug) return; + Stores.docker.box.deleteFromDisk(); + Stores.server.box.deleteFromDisk(); + Stores.setting.box.deleteFromDisk(); + Stores.history.box.deleteFromDisk(); + Stores.snippet.box.deleteFromDisk(); + Stores.key.box.deleteFromDisk(); + exit(0); + }, + child: Text(l10n.ok, + style: const TextStyle(color: Colors.red)), + ), + ], + ), + ) + .padding(const EdgeInsets.only(right: 17)), ], ), body: ListView( @@ -198,7 +198,7 @@ class _SettingPageState extends State { children.add(_buildPlatformSetting()); } return Column( - children: children.map((e) => CardX(e)).toList(), + children: children.map((e) => CardX(child: e)).toList(), ); } @@ -208,7 +208,7 @@ class _SettingPageState extends State { _buildFullScreenSwitch(), _buildFullScreenJitter(), _buildFulScreenRotateQuarter(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ); } @@ -223,7 +223,7 @@ class _SettingPageState extends State { //_buildDiskIgnorePath(), _buildDeleteServers(), //if (isDesktop) _buildDoubleColumnServersPage(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ); } @@ -236,7 +236,7 @@ class _SettingPageState extends State { // Use hardware keyboard on desktop, so there is no need to set it if (isMobile) _buildKeyboardType(), _buildSSHVirtKeys(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ); } @@ -247,7 +247,7 @@ class _SettingPageState extends State { _buildEditorTheme(), _buildEditorDarkTheme(), _buildEditorHighlight(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ); } @@ -267,7 +267,7 @@ class _SettingPageState extends State { return ListTile( title: Text(l10n.autoCheckUpdate), subtitle: Text(display, style: UIs.textGrey), - onTap: () => doUpdate(ctx), + onTap: () => Funcs.throttle(() => doUpdate(ctx)), trailing: StoreSwitch(prop: _setting.autoCheckAppUpdate), ); }, @@ -861,7 +861,7 @@ class _SettingPageState extends State { children: [ _buildSftpRmrDir(), _buildSftpOpenLastPath(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ); } diff --git a/lib/view/page/setting/ios.dart b/lib/view/page/setting/ios.dart index d67d5b0e..79ed16ff 100644 --- a/lib/view/page/setting/ios.dart +++ b/lib/view/page/setting/ios.dart @@ -45,7 +45,7 @@ class _IOSSettingsPageState extends State { _buildWatchApp(), if (BioAuth.isPlatformSupported) PlatformPublicSettings.buildBioAuth(), - ].map((e) => CardX(e)).toList(), + ].map((e) => CardX(child: e)).toList(), ), ); } diff --git a/lib/view/page/setting/srv_detail_seq.dart b/lib/view/page/setting/srv_detail_seq.dart index bafc7487..4cbb1269 100644 --- a/lib/view/page/setting/srv_detail_seq.dart +++ b/lib/view/page/setting/srv_detail_seq.dart @@ -53,10 +53,12 @@ class _ServerDetailOrderPageState extends State { return ReorderableDelayedDragStartListener( key: ValueKey('$index'), index: index, - child: CardX(ListTile( - title: Text(id), - trailing: const Icon(Icons.drag_handle), - )), + child: CardX( + child: ListTile( + title: Text(id), + trailing: const Icon(Icons.drag_handle), + ), + ), ); } } diff --git a/lib/view/page/setting/srv_seq.dart b/lib/view/page/setting/srv_seq.dart index 9630fc9b..863288c5 100644 --- a/lib/view/page/setting/srv_seq.dart +++ b/lib/view/page/setting/srv_seq.dart @@ -54,14 +54,16 @@ class _ServerOrderPageState extends State { return ReorderableDelayedDragStartListener( key: ValueKey('$index'), index: index, - child: CardX(ListTile( - title: Text(spi.name), - subtitle: Text(spi.id, style: UIs.textGrey), - leading: CircleAvatar( - child: Text(spi.name[0]), + child: CardX( + child: ListTile( + title: Text(spi.name), + subtitle: Text(spi.id, style: UIs.textGrey), + leading: CircleAvatar( + child: Text(spi.name[0]), + ), + trailing: const Icon(Icons.drag_handle), ), - trailing: const Icon(Icons.drag_handle), - )), + ), ); } } diff --git a/lib/view/page/setting/virt_key.dart b/lib/view/page/setting/virt_key.dart index ffd0630e..7d46e9a0 100644 --- a/lib/view/page/setting/virt_key.dart +++ b/lib/view/page/setting/virt_key.dart @@ -43,7 +43,7 @@ class _SSHVirtKeySettingPageState extends State { final help = key.help; return CardX( key: ValueKey(idx), - ListTile( + child: ListTile( title: _buildTitle(key), subtitle: help == null ? null : Text(help, style: UIs.textGrey), leading: _buildCheckBox(keys, key, idx, idx < keys.length), diff --git a/lib/view/page/snippet/list.dart b/lib/view/page/snippet/list.dart index 152f1a15..7c59a0f7 100644 --- a/lib/view/page/snippet/list.dart +++ b/lib/view/page/snippet/list.dart @@ -94,7 +94,7 @@ class _SnippetListPageState extends State { Widget _buildSnippetItem(Snippet snippet) { return CardX( - ListTile( + child: ListTile( contentPadding: const EdgeInsets.only(left: 23, right: 17), title: Text( snippet.name, diff --git a/lib/view/page/snippet/result.dart b/lib/view/page/snippet/result.dart index a7c612b6..b0088323 100644 --- a/lib/view/page/snippet/result.dart +++ b/lib/view/page/snippet/result.dart @@ -29,7 +29,7 @@ class SnippetResultPage extends StatelessWidget { final item = results[index]; if (item == null) return UIs.placeholder; return CardX( - ExpandTile( + child: ExpandTile( initiallyExpanded: results.length == 1, title: Text(item.dest ?? ''), subtitle: Text(item.time.toString(), style: UIs.textGrey), diff --git a/lib/view/page/ssh/page.dart b/lib/view/page/ssh/page.dart index 622c4a82..31af2743 100644 --- a/lib/view/page/ssh/page.dart +++ b/lib/view/page/ssh/page.dart @@ -9,6 +9,7 @@ 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/utils/platform/base.dart'; +import 'package:toolbox/core/utils/server.dart'; import 'package:toolbox/core/utils/share.dart'; import 'package:toolbox/data/model/server/server.dart'; import 'package:toolbox/data/model/server/snippet.dart'; @@ -58,7 +59,7 @@ class _SSHPageState extends State with AutomaticKeepAliveClientMixin { bool _isDark = false; Timer? _virtKeyLongPressTimer; late final Server? _server = widget.spi.server; - late final SSHClient? _client = _server?.client; + late SSHClient? _client = _server?.client; Timer? _discontinuityTimer; @override @@ -329,9 +330,7 @@ class _SSHPageState extends State with AutomaticKeepAliveClientMixin { Future _initTerminal() async { _writeLn('Connecting...\r\n'); - if (_client == null) { - await Pros.server.refreshData(spi: widget.spi); - } + _client ??= await genClient(widget.spi); _writeLn('Starting shell...\r\n'); final session = await _client?.shell( diff --git a/lib/view/page/ssh/tab.dart b/lib/view/page/ssh/tab.dart index 717466c7..f2d830fd 100644 --- a/lib/view/page/ssh/tab.dart +++ b/lib/view/page/ssh/tab.dart @@ -3,6 +3,7 @@ import 'package:provider/provider.dart'; 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/widget.dart'; import 'package:toolbox/data/provider/server.dart'; import 'package:toolbox/data/res/ui.dart'; import 'package:toolbox/view/page/ssh/page.dart'; @@ -49,12 +50,9 @@ class _SSHTabPageState extends State children: [ Text(e), UIs.width7, - InkWell( - borderRadius: BorderRadius.circular(17), - child: const Padding( - padding: EdgeInsets.all(7), - child: Icon(Icons.close, size: 17), - ), + const Icon(Icons.close, size: 17) + .padding(const EdgeInsets.all(7)) + .tap( onTap: () async { final confirm = await context.showRoundDialog( title: Text(l10n.attention), @@ -89,27 +87,29 @@ class _SSHTabPageState extends State padding: const EdgeInsets.all(7), itemBuilder: (_, idx) { final spi = pro.servers.toList()[idx].spi; - return CardX(ListTile( - title: Text(spi.name), - subtitle: Text(spi.id, style: UIs.textGrey), - trailing: const Icon(Icons.chevron_right), - onTap: () { - final name = () { - if (_tabIds.containsKey(spi.name)) { - return '${spi.name}(${_tabIds.length + 1})'; - } - return spi.name; - }(); - final key = GlobalKey(debugLabel: 'sshTabPage_$name'); - _tabIds[name] = SSHPage( - key: key, - spi: spi, - pop: false, - ); - _refreshTabs(); - _tabController.animateTo(_tabIds.length - 1); - }, - )); + return CardX( + child: ListTile( + title: Text(spi.name), + subtitle: Text(spi.id, style: UIs.textGrey), + trailing: const Icon(Icons.chevron_right), + onTap: () { + final name = () { + if (_tabIds.containsKey(spi.name)) { + return '${spi.name}(${_tabIds.length + 1})'; + } + return spi.name; + }(); + final key = GlobalKey(debugLabel: 'sshTabPage_$name'); + _tabIds[name] = SSHPage( + key: key, + spi: spi, + pop: false, + ); + _refreshTabs(); + _tabController.animateTo(_tabIds.length - 1); + }, + ), + ); }, itemCount: pro.servers.length, ); diff --git a/lib/view/page/storage/local.dart b/lib/view/page/storage/local.dart index 47e63380..64c0902c 100644 --- a/lib/view/page/storage/local.dart +++ b/lib/view/page/storage/local.dart @@ -139,32 +139,35 @@ class _LocalStoragePageState extends State { var stat = file.statSync(); var isDir = stat.type == FileSystemEntityType.directory; - return CardX(ListTile( - leading: isDir - ? const Icon(Icons.folder) - : const Icon(Icons.insert_drive_file), - title: Text(fileName), - subtitle: - isDir ? null : Text(stat.size.convertBytes, style: UIs.textGrey), - trailing: Text( - stat.modified - .toString() - .substring(0, stat.modified.toString().length - 4), - style: UIs.textGrey, + return CardX( + child: ListTile( + leading: isDir + ? const Icon(Icons.folder) + : const Icon(Icons.insert_drive_file), + title: Text(fileName), + subtitle: isDir + ? null + : Text(stat.size.convertBytes, style: UIs.textGrey), + trailing: Text( + stat.modified + .toString() + .substring(0, stat.modified.toString().length - 4), + style: UIs.textGrey, + ), + onLongPress: () { + if (!isDir) return; + _showDirActionDialog(file); + }, + onTap: () async { + if (!isDir) { + await _showFileActionDialog(file); + return; + } + _path!.update(fileName); + setState(() {}); + }, ), - onLongPress: () { - if (!isDir) return; - _showDirActionDialog(file); - }, - onTap: () async { - if (!isDir) { - await _showFileActionDialog(file); - return; - } - _path!.update(fileName); - setState(() {}); - }, - )); + ); }, ); } diff --git a/lib/view/page/storage/sftp.dart b/lib/view/page/storage/sftp.dart index 5f12b344..fcbb036c 100644 --- a/lib/view/page/storage/sftp.dart +++ b/lib/view/page/storage/sftp.dart @@ -264,26 +264,28 @@ class _SftpPageState extends State with AfterLayoutMixin { style: UIs.textGrey, textAlign: TextAlign.right, ); - return CardX(ListTile( - leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file), - title: Text(file.filename), - trailing: trailing, - subtitle: isDir - ? null - : Text( - (file.attr.size ?? 0).convertBytes, - style: UIs.textGrey, - ), - onTap: () { - if (isDir) { - _status.path?.update(file.filename); - _listDir(); - } else { - _onItemPress(file, true); - } - }, - onLongPress: () => _onItemPress(file, !isDir), - )); + return CardX( + child: ListTile( + leading: Icon(isDir ? Icons.folder : Icons.insert_drive_file), + title: Text(file.filename), + trailing: trailing, + subtitle: isDir + ? null + : Text( + (file.attr.size ?? 0).convertBytes, + style: UIs.textGrey, + ), + onTap: () { + if (isDir) { + _status.path?.update(file.filename); + _listDir(); + } else { + _onItemPress(file, true); + } + }, + onLongPress: () => _onItemPress(file, !isDir), + ), + ); } void _onItemPress(SftpName file, bool notDir) { diff --git a/lib/view/page/storage/sftp_mission.dart b/lib/view/page/storage/sftp_mission.dart index db14bcac..c0676edf 100644 --- a/lib/view/page/storage/sftp_mission.dart +++ b/lib/view/page/storage/sftp_mission.dart @@ -134,7 +134,7 @@ class _SftpMissionPageState extends State { }) { final time = DateTime.fromMicrosecondsSinceEpoch(status.id); return CardX( - ListTile( + child: ListTile( leading: Text(time.hourMinute), title: Text( status.fileName, diff --git a/lib/view/widget/cardx.dart b/lib/view/widget/cardx.dart index b27c75e1..f0e637ef 100644 --- a/lib/view/widget/cardx.dart +++ b/lib/view/widget/cardx.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; class CardX extends StatelessWidget { - const CardX(this.child, {super.key, this.color}); + const CardX({super.key, required this.child, this.color}); final Widget child; final Color? color; diff --git a/lib/view/widget/input_field.dart b/lib/view/widget/input_field.dart index 3d15c144..ac9f1d90 100644 --- a/lib/view/widget/input_field.dart +++ b/lib/view/widget/input_field.dart @@ -58,7 +58,7 @@ class _InputState extends State { @override Widget build(BuildContext context) { return CardX( - Padding( + child: Padding( padding: const EdgeInsets.symmetric(horizontal: 17), child: TextField( controller: widget.controller, diff --git a/lib/view/widget/tag.dart b/lib/view/widget/tag.dart index 96519ae1..644455c6 100644 --- a/lib/view/widget/tag.dart +++ b/lib/view/widget/tag.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; 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/widget.dart'; import 'package:toolbox/data/res/ui.dart'; import 'package:toolbox/view/widget/input_field.dart'; import 'package:toolbox/view/widget/cardx.dart'; @@ -56,16 +57,17 @@ class TagEditor extends StatefulWidget { class _TagEditorState extends State { @override Widget build(BuildContext context) { - return CardX(ListTile( - leading: const Icon(Icons.tag), - title: _buildTags(widget.tags), - trailing: InkWell( - child: const Icon(Icons.add), - onTap: () { - _showAddTagDialog(); - }, + return CardX( + child: ListTile( + leading: const Icon(Icons.tag), + title: _buildTags(widget.tags), + trailing: const Icon(Icons.add).tap( + onTap: () { + _showAddTagDialog(); + }, + ), ), - )); + ); } Widget _buildTags(List tags) { @@ -176,7 +178,7 @@ class _TagEditorState extends State { } } -class TagSwitcher extends StatelessWidget { +class TagSwitcher extends StatelessWidget implements PreferredSizeWidget { final List tags; final double width; final void Function(String?) onTagChanged; @@ -199,6 +201,7 @@ class TagSwitcher extends StatelessWidget { return Container( height: _kTagBtnHeight, width: width, + padding: const EdgeInsets.symmetric(horizontal: 7), alignment: Alignment.center, color: Colors.transparent, child: ListView.builder( @@ -215,6 +218,9 @@ class TagSwitcher extends StatelessWidget { ), ); } + + @override + Size get preferredSize => const Size.fromHeight(_kTagBtnHeight); } Widget _wrap( @@ -228,16 +234,10 @@ Widget _wrap( borderRadius: const BorderRadius.all(Radius.circular(20.0)), child: Material( color: primaryColor.withAlpha(20), - child: InkWell( - onTap: onTap, - onLongPress: onLongPress, - child: Padding( - /// Hard coded padding - /// For centering the text - padding: const EdgeInsets.fromLTRB(11.7, 2.7, 11.7, 0), - child: child, - ), - ), + child: child.padding(const EdgeInsets.fromLTRB(11.7, 2.7, 11.7, 0)).tap( + onTap: onTap, + onLongTap: onLongPress, + ), ), ), );