From 0c198c23fcd3916f304cd8bbbe9b7ca30ee92fd9 Mon Sep 17 00:00:00 2001 From: lollipopkit Date: Fri, 17 Feb 2023 16:29:46 +0800 Subject: [PATCH] opt: check `private key` size --- ios/Runner.xcodeproj/project.pbxproj | 12 +++--- lib/data/provider/server.dart | 1 - lib/data/res/build_data.dart | 9 ++-- lib/data/res/misc.dart | 3 ++ lib/generated/intl/messages_en.dart | 64 +++++++++++++++------------- lib/generated/intl/messages_zh.dart | 64 +++++++++++++++------------- lib/generated/l10n.dart | 10 +++++ lib/l10n/intl_en.arb | 1 + lib/l10n/intl_zh.arb | 1 + lib/view/page/private_key/edit.dart | 16 +++++++ lib/view/page/server/tab.dart | 59 +++++++++++++------------ 11 files changed, 141 insertions(+), 99 deletions(-) diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 019cb754..18a9c1a4 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -356,7 +356,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 220; + CURRENT_PROJECT_VERSION = 221; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -364,7 +364,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.220; + MARKETING_VERSION = 1.0.221; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -486,7 +486,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 220; + CURRENT_PROJECT_VERSION = 221; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -494,7 +494,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.220; + MARKETING_VERSION = 1.0.221; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; @@ -510,7 +510,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CURRENT_PROJECT_VERSION = 220; + CURRENT_PROJECT_VERSION = 221; DEVELOPMENT_TEAM = BA88US33G6; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -518,7 +518,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.220; + MARKETING_VERSION = 1.0.221; PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; diff --git a/lib/data/provider/server.dart b/lib/data/provider/server.dart index 39653019..f84d1241 100644 --- a/lib/data/provider/server.dart +++ b/lib/data/provider/server.dart @@ -52,7 +52,6 @@ class ServerProvider extends BusyProvider { if (s.cs != ServerState.failed) return; _limiter.resetTryTimes(s.spi.id); } - if (onlyFailed && s.cs != ServerState.failed) return; await _getData(s.spi); })); } diff --git a/lib/data/res/build_data.dart b/lib/data/res/build_data.dart index 5fa3d28f..e80adea5 100644 --- a/lib/data/res/build_data.dart +++ b/lib/data/res/build_data.dart @@ -2,8 +2,9 @@ class BuildData { static const String name = "ServerBox"; - static const int build = 220; - static const String engine = "Flutter 3.7.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 9944297138 (7 days ago) • 2023-02-08 15:46:04 -0800\nEngine • revision 248290d6d5\nTools • Dart 2.19.2 • DevTools 2.20.1\n"; - static const String buildAt = "2023-02-16 12:58:28.193531"; - static const int modifications = 3; + static const int build = 221; + static const String engine = + "Flutter 3.7.3 • channel stable • https://github.com/flutter/flutter.git\nFramework • revision 9944297138 (8 days ago) • 2023-02-08 15:46:04 -0800\nEngine • revision 248290d6d5\nTools • Dart 2.19.2 • DevTools 2.20.1\n"; + static const String buildAt = "2023-02-17 15:57:59.844806"; + static const int modifications = 2; } diff --git a/lib/data/res/misc.dart b/lib/data/res/misc.dart index 34c14749..3454ee80 100644 --- a/lib/data/res/misc.dart +++ b/lib/data/res/misc.dart @@ -1 +1,4 @@ final numReg = RegExp(r'\s{1,}'); + +/// Private Key max allowed size is 20kb +const privateKeyMaxSize = 20 * 1024; diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index 51c88e4c..d8583bdb 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -33,38 +33,41 @@ class MessageLookup extends MessageLookupByLibrary { static String m5(file) => "${file} not exist"; - static String m6(count) => "Found ${count} update"; + static String m6(file, size, sizeMax) => + "File \'${file}\' too large ${size}, max ${sizeMax}"; - static String m7(code) => "request failed, status code: ${code}"; + static String m7(count) => "Found ${count} update"; - static String m8(url) => + static String m8(code) => "request failed, status code: ${code}"; + + static String m9(url) => "Please make sure that docker is installed correctly, or that you are using a non-self-compiled version. If you don\'t have the above issues, please submit an issue on ${url}."; - static String m9(myGithub) => "\nMade with ❤️ by ${myGithub}"; + static String m10(myGithub) => "\nMade with ❤️ by ${myGithub}"; - static String m10(url) => "Please report bugs on ${url}"; + static String m11(url) => "Please report bugs on ${url}"; - static String m11(date) => "Are you sure to restore from ${date} ?"; + static String m12(date) => "Are you sure to restore from ${date} ?"; - static String m12(time) => "Spent time: ${time}"; + static String m13(time) => "Spent time: ${time}"; - static String m13(url) => + static String m14(url) => "This function is now in the experimental stage.\n\nPlease report bugs on ${url} or join our development."; - static String m14(name) => "Are you sure to delete [${name}]?"; + static String m15(name) => "Are you sure to delete [${name}]?"; - static String m15(server) => "Are you sure to delete server [${server}]?"; + static String m16(server) => "Are you sure to delete server [${server}]?"; - static String m16(newest) => "Update: v1.0.${newest}"; + static String m17(newest) => "Update: v1.0.${newest}"; - static String m17(newest) => + static String m18(newest) => "Current version is too low, please update to v1.0.${newest}"; - static String m18(build) => "Found: v1.0.${build}, click to update"; + static String m19(build) => "Found: v1.0.${build}, click to update"; - static String m19(build) => "Current: v1.0.${build}"; + static String m20(build) => "Current: v1.0.${build}"; - static String m20(build) => "Current: v1.0.${build}, is up to date"; + static String m21(build) => "Current: v1.0.${build}, is up to date"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -139,12 +142,13 @@ class MessageLookup extends MessageLookupByLibrary { "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage( "These fields must not be empty."), "fileNotExist": m5, + "fileTooLarge": m6, "files": MessageLookupByLibrary.simpleMessage("Files"), - "foundNUpdate": m6, + "foundNUpdate": m7, "go": MessageLookupByLibrary.simpleMessage("Go"), "goto": MessageLookupByLibrary.simpleMessage("Go to"), "host": MessageLookupByLibrary.simpleMessage("Host"), - "httpFailedWithCode": m7, + "httpFailedWithCode": m8, "imagesList": MessageLookupByLibrary.simpleMessage("Images list"), "import": MessageLookupByLibrary.simpleMessage("Import"), "importAndExport": @@ -157,7 +161,7 @@ class MessageLookup extends MessageLookupByLibrary { "invalidJson": MessageLookupByLibrary.simpleMessage("Invalid JSON"), "invalidVersion": MessageLookupByLibrary.simpleMessage("Invalid version"), - "invalidVersionHelp": m8, + "invalidVersionHelp": m9, "isBusy": MessageLookupByLibrary.simpleMessage("Is busy now"), "keepForeground": MessageLookupByLibrary.simpleMessage("Keep app foreground!"), @@ -168,7 +172,7 @@ class MessageLookup extends MessageLookupByLibrary { "loadingFiles": MessageLookupByLibrary.simpleMessage("Loading files..."), "loss": MessageLookupByLibrary.simpleMessage("loss"), - "madeWithLove": m9, + "madeWithLove": m10, "max": MessageLookupByLibrary.simpleMessage("max"), "maxRetryCount": MessageLookupByLibrary.simpleMessage( "Number of server reconnection"), @@ -213,11 +217,11 @@ class MessageLookup extends MessageLookupByLibrary { "privateKey": MessageLookupByLibrary.simpleMessage("Private Key"), "pwd": MessageLookupByLibrary.simpleMessage("Password"), "rename": MessageLookupByLibrary.simpleMessage("Rename"), - "reportBugsOnGithubIssue": m10, + "reportBugsOnGithubIssue": m11, "restore": MessageLookupByLibrary.simpleMessage("Restore"), "restoreSuccess": MessageLookupByLibrary.simpleMessage( "Restore success. Restart app to apply."), - "restoreSureWithDate": m11, + "restoreSureWithDate": m12, "result": MessageLookupByLibrary.simpleMessage("Result"), "run": MessageLookupByLibrary.simpleMessage("Run"), "save": MessageLookupByLibrary.simpleMessage("Save"), @@ -243,14 +247,14 @@ class MessageLookup extends MessageLookupByLibrary { "showDistLogo": MessageLookupByLibrary.simpleMessage("Show distribution logo"), "snippet": MessageLookupByLibrary.simpleMessage("Snippet"), - "spentTime": m12, - "sshTip": m13, + "spentTime": m13, + "sshTip": m14, "start": MessageLookupByLibrary.simpleMessage("Start"), "stop": MessageLookupByLibrary.simpleMessage("Stop"), - "sureDelete": m14, + "sureDelete": m15, "sureNoPwd": MessageLookupByLibrary.simpleMessage( "Are you sure to use no password?"), - "sureToDeleteServer": m15, + "sureToDeleteServer": m16, "termTheme": MessageLookupByLibrary.simpleMessage("Terminal theme"), "times": MessageLookupByLibrary.simpleMessage("Times"), "ttl": MessageLookupByLibrary.simpleMessage("ttl"), @@ -264,14 +268,14 @@ class MessageLookup extends MessageLookupByLibrary { "You set to 0, will not update automatically.\nCan\'t calculate CPU status."), "updateServerStatusInterval": MessageLookupByLibrary.simpleMessage( "Server status update interval"), - "updateTip": m16, - "updateTipTooLow": m17, + "updateTip": m17, + "updateTipTooLow": m18, "upsideDown": MessageLookupByLibrary.simpleMessage("Upside Down"), "urlOrJson": MessageLookupByLibrary.simpleMessage("URL or JSON"), "user": MessageLookupByLibrary.simpleMessage("User"), - "versionHaveUpdate": m18, - "versionUnknownUpdate": m19, - "versionUpdated": m20, + "versionHaveUpdate": m19, + "versionUnknownUpdate": m20, + "versionUpdated": m21, "waitConnection": MessageLookupByLibrary.simpleMessage( "Please wait for the connection to be established."), "willTakEeffectImmediately": diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart index 15ab3c1a..c067efda 100644 --- a/lib/generated/intl/messages_zh.dart +++ b/lib/generated/intl/messages_zh.dart @@ -33,36 +33,39 @@ class MessageLookup extends MessageLookupByLibrary { static String m5(file) => "${file} 不存在"; - static String m6(count) => "找到 ${count} 个更新"; + static String m6(file, size, sizeMax) => + "文件 \'${file}\' 过大 \'${size}\',超过了 ${sizeMax}"; - static String m7(code) => "请求失败, 状态码: ${code}"; + static String m7(count) => "找到 ${count} 个更新"; - static String m8(url) => + static String m8(code) => "请求失败, 状态码: ${code}"; + + static String m9(url) => "请确保正确安装了docker,或者使用的非自编译版本。如果没有以上问题,请在 ${url} 提交问题。"; - static String m9(myGithub) => "\n用❤️制作 by ${myGithub}"; + static String m10(myGithub) => "\n用❤️制作 by ${myGithub}"; - static String m10(url) => "请到 ${url} 提交问题"; + static String m11(url) => "请到 ${url} 提交问题"; - static String m11(date) => "确定恢复 ${date} 的备份吗?"; + static String m12(date) => "确定恢复 ${date} 的备份吗?"; - static String m12(time) => "耗时: ${time}"; + static String m13(time) => "耗时: ${time}"; - static String m13(url) => "该功能目前处于测试阶段。\n\n请在 ${url} 反馈问题,或者加入我们开发。"; + static String m14(url) => "该功能目前处于测试阶段。\n\n请在 ${url} 反馈问题,或者加入我们开发。"; - static String m14(name) => "确定删除[${name}]?"; + static String m15(name) => "确定删除[${name}]?"; - static String m15(server) => "你确定要删除服务器 [${server}] 吗?"; + static String m16(server) => "你确定要删除服务器 [${server}] 吗?"; - static String m16(newest) => "新版本: v1.0.${newest}"; + static String m17(newest) => "新版本: v1.0.${newest}"; - static String m17(newest) => "当前版本过低,请升级至 v1.0.${newest}"; + static String m18(newest) => "当前版本过低,请升级至 v1.0.${newest}"; - static String m18(build) => "找到新版本:v1.0.${build}, 点击更新"; + static String m19(build) => "找到新版本:v1.0.${build}, 点击更新"; - static String m19(build) => "当前:v1.0.${build}"; + static String m20(build) => "当前:v1.0.${build}"; - static String m20(build) => "当前:v1.0.${build}, 已是最新版本"; + static String m21(build) => "当前:v1.0.${build}, 已是最新版本"; final messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { @@ -126,12 +129,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("如果你有任何问题,请在GitHub反馈"), "fieldMustNotEmpty": MessageLookupByLibrary.simpleMessage("这些输入框不能为空。"), "fileNotExist": m5, + "fileTooLarge": m6, "files": MessageLookupByLibrary.simpleMessage("文件"), - "foundNUpdate": m6, + "foundNUpdate": m7, "go": MessageLookupByLibrary.simpleMessage("开始"), "goto": MessageLookupByLibrary.simpleMessage("前往"), "host": MessageLookupByLibrary.simpleMessage("主机"), - "httpFailedWithCode": m7, + "httpFailedWithCode": m8, "imagesList": MessageLookupByLibrary.simpleMessage("镜像列表"), "import": MessageLookupByLibrary.simpleMessage("导入"), "importAndExport": MessageLookupByLibrary.simpleMessage("导入或导出"), @@ -141,7 +145,7 @@ class MessageLookup extends MessageLookupByLibrary { "请先 https://docs.docker.com/engine/install docker"), "invalidJson": MessageLookupByLibrary.simpleMessage("无效的json,存在格式问题"), "invalidVersion": MessageLookupByLibrary.simpleMessage("不支持的版本"), - "invalidVersionHelp": m8, + "invalidVersionHelp": m9, "isBusy": MessageLookupByLibrary.simpleMessage("当前正忙"), "keepForeground": MessageLookupByLibrary.simpleMessage("请保持应用处于前台!"), "keyAuth": MessageLookupByLibrary.simpleMessage("公钥认证"), @@ -150,7 +154,7 @@ class MessageLookup extends MessageLookupByLibrary { "license": MessageLookupByLibrary.simpleMessage("开源证书"), "loadingFiles": MessageLookupByLibrary.simpleMessage("正在加载目录。。。"), "loss": MessageLookupByLibrary.simpleMessage("丢包率"), - "madeWithLove": m9, + "madeWithLove": m10, "max": MessageLookupByLibrary.simpleMessage("最大"), "maxRetryCount": MessageLookupByLibrary.simpleMessage("服务器尝试重连次数"), "maxRetryCountEqual0": MessageLookupByLibrary.simpleMessage("会无限重试"), @@ -185,11 +189,11 @@ class MessageLookup extends MessageLookupByLibrary { "privateKey": MessageLookupByLibrary.simpleMessage("私钥"), "pwd": MessageLookupByLibrary.simpleMessage("密码"), "rename": MessageLookupByLibrary.simpleMessage("重命名"), - "reportBugsOnGithubIssue": m10, + "reportBugsOnGithubIssue": m11, "restore": MessageLookupByLibrary.simpleMessage("恢复"), "restoreSuccess": MessageLookupByLibrary.simpleMessage("恢复成功,需要重启App来应用更改"), - "restoreSureWithDate": m11, + "restoreSureWithDate": m12, "result": MessageLookupByLibrary.simpleMessage("结果"), "run": MessageLookupByLibrary.simpleMessage("运行"), "save": MessageLookupByLibrary.simpleMessage("保存"), @@ -209,13 +213,13 @@ class MessageLookup extends MessageLookupByLibrary { MessageLookupByLibrary.simpleMessage("SFTP 已连接,即将开始下载..."), "showDistLogo": MessageLookupByLibrary.simpleMessage("显示发行版 Logo"), "snippet": MessageLookupByLibrary.simpleMessage("代码片段"), - "spentTime": m12, - "sshTip": m13, + "spentTime": m13, + "sshTip": m14, "start": MessageLookupByLibrary.simpleMessage("开始"), "stop": MessageLookupByLibrary.simpleMessage("停止"), - "sureDelete": m14, + "sureDelete": m15, "sureNoPwd": MessageLookupByLibrary.simpleMessage("确认使用无密码?"), - "sureToDeleteServer": m15, + "sureToDeleteServer": m16, "termTheme": MessageLookupByLibrary.simpleMessage("终端主题"), "times": MessageLookupByLibrary.simpleMessage("次"), "ttl": MessageLookupByLibrary.simpleMessage("缓存时间"), @@ -228,14 +232,14 @@ class MessageLookup extends MessageLookupByLibrary { "你设置为0,服务器状态不会自动刷新。\n且不能计算CPU使用情况。"), "updateServerStatusInterval": MessageLookupByLibrary.simpleMessage("服务器状态刷新间隔"), - "updateTip": m16, - "updateTipTooLow": m17, + "updateTip": m17, + "updateTipTooLow": m18, "upsideDown": MessageLookupByLibrary.simpleMessage("上下交换"), "urlOrJson": MessageLookupByLibrary.simpleMessage("链接或JSON"), "user": MessageLookupByLibrary.simpleMessage("用户"), - "versionHaveUpdate": m18, - "versionUnknownUpdate": m19, - "versionUpdated": m20, + "versionHaveUpdate": m19, + "versionUnknownUpdate": m20, + "versionUpdated": m21, "waitConnection": MessageLookupByLibrary.simpleMessage("请等待连接建立"), "willTakEeffectImmediately": MessageLookupByLibrary.simpleMessage("更改将会立即生效") diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index 704b5205..e60bfb8d 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -591,6 +591,16 @@ class S { ); } + /// `File '{file}' too large {size}, max {sizeMax}` + String fileTooLarge(Object file, Object size, Object sizeMax) { + return Intl.message( + 'File \'$file\' too large $size, max $sizeMax', + name: 'fileTooLarge', + desc: '', + args: [file, size, sizeMax], + ); + } + /// `Files` String get files { return Intl.message( diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index c7b4f5ac..5659b83b 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -53,6 +53,7 @@ "feedbackOnGithub": "If you have any questions, please feedback on Github.", "fieldMustNotEmpty": "These fields must not be empty.", "fileNotExist": "{file} not exist", + "fileTooLarge": "File '{file}' too large {size}, max {sizeMax}", "files": "Files", "foundNUpdate": "Found {count} update", "go": "Go", diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb index 33302562..4ad94826 100644 --- a/lib/l10n/intl_zh.arb +++ b/lib/l10n/intl_zh.arb @@ -53,6 +53,7 @@ "feedbackOnGithub": "如果你有任何问题,请在GitHub反馈", "fieldMustNotEmpty": "这些输入框不能为空。", "fileNotExist": "{file} 不存在", + "fileTooLarge": "文件 '{file}' 过大 '{size}',超过了 {sizeMax}", "files": "文件", "foundNUpdate": "找到 {count} 个更新", "go": "开始", diff --git a/lib/view/page/private_key/edit.dart b/lib/view/page/private_key/edit.dart index 90debba6..eca22a01 100644 --- a/lib/view/page/private_key/edit.dart +++ b/lib/view/page/private_key/edit.dart @@ -6,6 +6,8 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:toolbox/core/extension/numx.dart'; +import 'package:toolbox/data/res/misc.dart'; import '../../../core/utils/ui.dart'; import '../../../data/model/server/private_key_info.dart'; @@ -111,6 +113,20 @@ class _PrivateKeyEditPageState extends State showSnackBar(context, Text(_s.fileNotExist(path))); return; } + final size = (await file.stat()).size; + if (size > privateKeyMaxSize) { + showSnackBar( + context, + Text( + _s.fileTooLarge( + path, + size.convertBytes, + privateKeyMaxSize.convertBytes, + ), + ), + ); + return; + } _keyController.text = await file.readAsString(); }, diff --git a/lib/view/page/server/tab.dart b/lib/view/page/server/tab.dart index fddadc30..f25c3018 100644 --- a/lib/view/page/server/tab.dart +++ b/lib/view/page/server/tab.dart @@ -63,35 +63,38 @@ class _ServerPageState extends State @override Widget build(BuildContext context) { super.build(context); - final child = Consumer( - builder: (_, pro, __) { - if (pro.servers.isEmpty) { - return Center( - child: Text( - _s.serverTabEmpty, - textAlign: TextAlign.center, - ), - ); - } - return ListView.separated( - padding: const EdgeInsets.all(7), - controller: ScrollController(), - physics: const AlwaysScrollableScrollPhysics(), - itemBuilder: (ctx, idx) { - if (idx == pro.servers.length) { - return SizedBox(height: _media.padding.bottom); - } - return _buildEachServerCard(pro.servers[idx]); - }, - itemCount: pro.servers.length + 1, - separatorBuilder: (_, __) => const SizedBox( - height: 3, - ), - ); - }, - ); return Scaffold( - body: RefreshIndicator(child: child, onRefresh: () async => await _serverProvider.refreshData(onlyFailed: true)), + body: RefreshIndicator( + onRefresh: () async => + await _serverProvider.refreshData(onlyFailed: true), + child: Consumer( + builder: (_, pro, __) { + if (pro.servers.isEmpty) { + return Center( + child: Text( + _s.serverTabEmpty, + textAlign: TextAlign.center, + ), + ); + } + return ListView.separated( + padding: const EdgeInsets.all(7), + controller: ScrollController(), + physics: const AlwaysScrollableScrollPhysics(), + itemBuilder: (ctx, idx) { + if (idx == pro.servers.length) { + return SizedBox(height: _media.padding.bottom); + } + return _buildEachServerCard(pro.servers[idx]); + }, + itemCount: pro.servers.length + 1, + separatorBuilder: (_, __) => const SizedBox( + height: 3, + ), + ); + }, + ), + ), floatingActionButton: FloatingActionButton( onPressed: () => AppRoute( const ServerEditPage(),