readd: serverTabPreferDiskAmount (#780)

Fixes #643
This commit is contained in:
lollipopkit🏳️‍⚧️
2025-06-08 11:15:54 +08:00
committed by GitHub
parent b5aec55106
commit 4b3953e0d2
42 changed files with 343 additions and 515 deletions

View File

@@ -47,11 +47,11 @@ final class PveProvider extends ChangeNotifier {
final client = HttpClient(); final client = HttpClient();
client.connectionFactory = cf; client.connectionFactory = cf;
if (_ignoreCert) { if (_ignoreCert) {
client.badCertificateCallback = (_, __, ___) => true; client.badCertificateCallback = (_, _, _) => true;
} }
return client; return client;
}, },
validateCertificate: _ignoreCert ? (_, __, ___) => true : null, validateCertificate: _ignoreCert ? (_, _, _) => true : null,
); );
final data = ValueNotifier<PveRes?>(null); final data = ValueNotifier<PveRes?>(null);

View File

@@ -12,9 +12,6 @@ class SettingStore extends HiveStore {
static final instance = SettingStore._(); static final instance = SettingStore._();
/// Discussion #146
late final serverTabUseOldUI = propertyDefault('serverTabUseOldUI', false);
/// Time out for server connect and more... /// Time out for server connect and more...
late final timeout = propertyDefault('timeOut', 5); late final timeout = propertyDefault('timeOut', 5);

View File

@@ -890,6 +890,12 @@ abstract class AppLocalizations {
/// **'Port'** /// **'Port'**
String get port; String get port;
/// No description provided for @preferDiskAmount.
///
/// In en, this message translates to:
/// **'Prioritize displaying disk capacity'**
String get preferDiskAmount;
/// No description provided for @preview. /// No description provided for @preview.
/// ///
/// In en, this message translates to: /// In en, this message translates to:

View File

@@ -440,6 +440,9 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get port => 'Port'; String get port => 'Port';
@override
String get preferDiskAmount => 'Festplattenkapazität vorrangig anzeigen';
@override @override
String get preview => 'Vorschau'; String get preview => 'Vorschau';

View File

@@ -438,6 +438,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get port => 'Port'; String get port => 'Port';
@override
String get preferDiskAmount => 'Prioritize displaying disk capacity';
@override @override
String get preview => 'Preview'; String get preview => 'Preview';

View File

@@ -441,6 +441,10 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get port => 'Puerto'; String get port => 'Puerto';
@override
String get preferDiskAmount =>
'Priorizar la visualización de la capacidad del disco';
@override @override
String get preview => 'Vista previa'; String get preview => 'Vista previa';

View File

@@ -442,6 +442,10 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get port => 'Port'; String get port => 'Port';
@override
String get preferDiskAmount =>
'Prioriser laffichage de la capacité du disque';
@override @override
String get preview => 'Aperçu'; String get preview => 'Aperçu';

View File

@@ -438,6 +438,9 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get port => 'Port'; String get port => 'Port';
@override
String get preferDiskAmount => 'Prioritaskan tampilan kapasitas disk';
@override @override
String get preview => 'Pratinjau'; String get preview => 'Pratinjau';

View File

@@ -424,6 +424,9 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get port => 'ポート'; String get port => 'ポート';
@override
String get preferDiskAmount => 'ディスク容量を優先的に表示';
@override @override
String get preview => 'プレビュー'; String get preview => 'プレビュー';

View File

@@ -438,6 +438,10 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get port => 'Poort'; String get port => 'Poort';
@override
String get preferDiskAmount =>
'Geef de schijfcapaciteit prioriteit bij weergave';
@override @override
String get preview => 'Voorbeeld'; String get preview => 'Voorbeeld';

View File

@@ -439,6 +439,9 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get port => 'Porta'; String get port => 'Porta';
@override
String get preferDiskAmount => 'Priorizar a exibição da capacidade do disco';
@override @override
String get preview => 'Pré-visualização'; String get preview => 'Pré-visualização';

View File

@@ -440,6 +440,9 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get port => 'Порт'; String get port => 'Порт';
@override
String get preferDiskAmount => 'Приоритетное отображение объёма диска';
@override @override
String get preview => 'Предпросмотр'; String get preview => 'Предпросмотр';

View File

@@ -437,6 +437,9 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get port => 'Port'; String get port => 'Port';
@override
String get preferDiskAmount => 'Disk kapasitesini öncelikli olarak göster';
@override @override
String get preview => 'Önizleme'; String get preview => 'Önizleme';

View File

@@ -442,6 +442,9 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get port => 'Порт'; String get port => 'Порт';
@override
String get preferDiskAmount => 'Пріоритетно показувати ємність диска';
@override @override
String get preview => 'Попередній перегляд'; String get preview => 'Попередній перегляд';

View File

@@ -419,6 +419,9 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get port => '端口'; String get port => '端口';
@override
String get preferDiskAmount => '优先显示硬盘容量';
@override @override
String get preview => '预览'; String get preview => '预览';
@@ -1147,6 +1150,9 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override @override
String get port => ''; String get port => '';
@override
String get preferDiskAmount => '優先顯示硬碟容量';
@override @override
String get preview => '預覽'; String get preview => '預覽';

View File

@@ -128,6 +128,7 @@
"pkg": "Pkg", "pkg": "Pkg",
"plugInType": "Einfügetyp", "plugInType": "Einfügetyp",
"port": "Port", "port": "Port",
"preferDiskAmount": "Festplattenkapazität vorrangig anzeigen",
"preview": "Vorschau", "preview": "Vorschau",
"privateKey": "Private Key", "privateKey": "Private Key",
"process": "Prozess", "process": "Prozess",

View File

@@ -128,6 +128,7 @@
"pkg": "Pkg", "pkg": "Pkg",
"plugInType": "Insertion Type", "plugInType": "Insertion Type",
"port": "Port", "port": "Port",
"preferDiskAmount": "Prioritize displaying disk capacity",
"preview": "Preview", "preview": "Preview",
"privateKey": "Private Key", "privateKey": "Private Key",
"process": "Process", "process": "Process",

View File

@@ -128,6 +128,7 @@
"pkg": "Gestión de paquetes", "pkg": "Gestión de paquetes",
"plugInType": "Tipo de inserción", "plugInType": "Tipo de inserción",
"port": "Puerto", "port": "Puerto",
"preferDiskAmount": "Priorizar la visualización de la capacidad del disco",
"preview": "Vista previa", "preview": "Vista previa",
"privateKey": "Llave privada", "privateKey": "Llave privada",
"process": "Proceso", "process": "Proceso",

View File

@@ -128,6 +128,7 @@
"pkg": "Pkg", "pkg": "Pkg",
"plugInType": "Type d'insertion", "plugInType": "Type d'insertion",
"port": "Port", "port": "Port",
"preferDiskAmount": "Prioriser laffichage de la capacité du disque",
"preview": "Aperçu", "preview": "Aperçu",
"privateKey": "Clé privée", "privateKey": "Clé privée",
"process": "Processus", "process": "Processus",

View File

@@ -128,6 +128,7 @@
"pkg": "Pkg", "pkg": "Pkg",
"plugInType": "Jenis Penyisipan", "plugInType": "Jenis Penyisipan",
"port": "Port", "port": "Port",
"preferDiskAmount": "Prioritaskan tampilan kapasitas disk",
"preview": "Pratinjau", "preview": "Pratinjau",
"privateKey": "Kunci Pribadi", "privateKey": "Kunci Pribadi",
"process": "Proses", "process": "Proses",

View File

@@ -128,6 +128,7 @@
"pkg": "パッケージ管理", "pkg": "パッケージ管理",
"plugInType": "挿入タイプ", "plugInType": "挿入タイプ",
"port": "ポート", "port": "ポート",
"preferDiskAmount": "ディスク容量を優先的に表示",
"preview": "プレビュー", "preview": "プレビュー",
"privateKey": "秘密鍵", "privateKey": "秘密鍵",
"process": "プロセス", "process": "プロセス",

View File

@@ -128,6 +128,7 @@
"pkg": "Pkg", "pkg": "Pkg",
"plugInType": "Invoegingstype", "plugInType": "Invoegingstype",
"port": "Poort", "port": "Poort",
"preferDiskAmount": "Geef de schijfcapaciteit prioriteit bij weergave",
"preview": "Voorbeeld", "preview": "Voorbeeld",
"privateKey": "Privésleutel", "privateKey": "Privésleutel",
"process": "Proces", "process": "Proces",

View File

@@ -128,6 +128,7 @@
"pkg": "Gerenciamento de pacotes", "pkg": "Gerenciamento de pacotes",
"plugInType": "Tipo de Inserção", "plugInType": "Tipo de Inserção",
"port": "Porta", "port": "Porta",
"preferDiskAmount": "Priorizar a exibição da capacidade do disco",
"preview": "Pré-visualização", "preview": "Pré-visualização",
"privateKey": "Chave privada", "privateKey": "Chave privada",
"process": "Processo", "process": "Processo",

View File

@@ -128,6 +128,7 @@
"pkg": "Менеджер пакетов", "pkg": "Менеджер пакетов",
"plugInType": "Тип вставки", "plugInType": "Тип вставки",
"port": "Порт", "port": "Порт",
"preferDiskAmount": "Приоритетное отображение объёма диска",
"preview": "Предпросмотр", "preview": "Предпросмотр",
"privateKey": "Приватный ключ", "privateKey": "Приватный ключ",
"process": "Процесс", "process": "Процесс",

View File

@@ -128,6 +128,7 @@
"pkg": "Paket", "pkg": "Paket",
"plugInType": "Eklenti Türü", "plugInType": "Eklenti Türü",
"port": "Port", "port": "Port",
"preferDiskAmount": "Disk kapasitesini öncelikli olarak göster",
"preview": "Önizleme", "preview": "Önizleme",
"privateKey": "Özel Anahtar", "privateKey": "Özel Anahtar",
"process": "İşlem", "process": "İşlem",

View File

@@ -128,6 +128,7 @@
"pkg": "Пакет", "pkg": "Пакет",
"plugInType": "Тип вставки", "plugInType": "Тип вставки",
"port": "Порт", "port": "Порт",
"preferDiskAmount": "Пріоритетно показувати ємність диска",
"preview": "Попередній перегляд", "preview": "Попередній перегляд",
"privateKey": "Приватний ключ", "privateKey": "Приватний ключ",
"process": "Процес", "process": "Процес",

View File

@@ -128,6 +128,7 @@
"pkg": "包管理", "pkg": "包管理",
"plugInType": "插入类型", "plugInType": "插入类型",
"port": "端口", "port": "端口",
"preferDiskAmount": "优先显示硬盘容量",
"preview": "预览", "preview": "预览",
"privateKey": "私钥", "privateKey": "私钥",
"process": "进程", "process": "进程",

View File

@@ -128,6 +128,7 @@
"pkg": "包管理", "pkg": "包管理",
"plugInType": "插入類型", "plugInType": "插入類型",
"port": "埠", "port": "埠",
"preferDiskAmount": "優先顯示硬碟容量",
"preview": "預覽", "preview": "預覽",
"privateKey": "私鑰", "privateKey": "私鑰",
"process": "行程", "process": "行程",

View File

@@ -60,7 +60,7 @@ class _ContainerPageState extends State<ContainerPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<ContainerProvider>( return Consumer<ContainerProvider>(
builder: (_, ___, __) { builder: (_, _, _) {
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(
centerTitle: true, centerTitle: true,

View File

@@ -15,14 +15,10 @@ class PingPage extends StatefulWidget {
@override @override
State<PingPage> createState() => _PingPageState(); State<PingPage> createState() => _PingPageState();
static const route = AppRouteNoArg( static const route = AppRouteNoArg(page: PingPage.new, path: '/ping');
page: PingPage.new,
path: '/ping',
);
} }
class _PingPageState extends State<PingPage> class _PingPageState extends State<PingPage> with AutomaticKeepAliveClientMixin {
with AutomaticKeepAliveClientMixin {
late TextEditingController _textEditingController; late TextEditingController _textEditingController;
final _results = ValueNotifier(<PingResult>[]); final _results = ValueNotifier(<PingResult>[]);
bool get isInit => _results.value.isEmpty; bool get isInit => _results.value.isEmpty;
@@ -43,13 +39,7 @@ class _PingPageState extends State<PingPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
return Scaffold( return Scaffold(body: _results.listenVal(_buildBody), floatingActionButton: _buildFAB());
body: ListenableBuilder(
listenable: _results,
builder: (_, __) => _buildBody(),
),
floatingActionButton: _buildFAB(),
);
} }
Widget _buildFAB() { Widget _buildFAB() {
@@ -81,26 +71,21 @@ class _PingPageState extends State<PingPage>
context.showRoundDialog( context.showRoundDialog(
title: libL10n.error, title: libL10n.error,
child: Text(e.toString()), child: Text(e.toString()),
actions: [ actions: [TextButton(onPressed: () => Pfs.copy(e.toString()), child: Text(libL10n.copy))],
TextButton(
onPressed: () => Pfs.copy(e.toString()),
child: Text(libL10n.copy),
),
],
); );
rethrow; rethrow;
} }
} }
Widget _buildBody() { Widget _buildBody(List<PingResult> results) {
if (isInit) { if (isInit) {
return Center(child: Text(libL10n.empty)); return Center(child: Text(libL10n.empty));
} }
return ListView.builder( return ListView.builder(
padding: const EdgeInsets.all(11), padding: const EdgeInsets.all(11),
controller: ScrollController(), controller: ScrollController(),
itemCount: _results.value.length, itemCount: results.length,
itemBuilder: (_, index) => _buildResultItem(_results.value[index]), itemBuilder: (_, index) => _buildResultItem(results[index]),
); );
} }
@@ -112,22 +97,12 @@ class _PingPageState extends State<PingPage>
contentPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17), contentPadding: const EdgeInsets.symmetric(vertical: 7, horizontal: 17),
title: Text( title: Text(
result.serverName, result.serverName,
style: TextStyle( style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: UIs.primaryColor),
fontSize: 18,
fontWeight: FontWeight.bold,
color: UIs.primaryColor,
),
),
subtitle: Text(
_buildPingSummary(result, unknown, ms),
style: UIs.text11,
), ),
subtitle: Text(_buildPingSummary(result, unknown, ms), style: UIs.text11),
trailing: Text( trailing: Text(
'${l10n.pingAvg}${result.statistic?.avg?.toStringAsFixed(2) ?? l10n.unknown} $ms', '${l10n.pingAvg}${result.statistic?.avg?.toStringAsFixed(2) ?? l10n.unknown} $ms',
style: TextStyle( style: TextStyle(fontSize: 14, color: UIs.primaryColor),
fontSize: 14,
color: UIs.primaryColor,
),
), ),
), ),
); );
@@ -165,20 +140,22 @@ class _PingPageState extends State<PingPage>
return; return;
} }
await Future.wait(ServerProvider.servers.values.map((v) async { await Future.wait(
final e = v.value; ServerProvider.servers.values.map((v) async {
if (e.client == null) { final e = v.value;
return; if (e.client == null) {
} return;
final result = await e.client!.run('ping -c 3 $target').string; }
_results.value.add(PingResult.parse(e.spi.name, result)); final result = await e.client!.run('ping -c 3 $target').string;
// [ValueNotifier] only notify when value is changed _results.value.add(PingResult.parse(e.spi.name, result));
// But we just add a element to list without changing the list itself // [ValueNotifier] only notify when value is changed
// So we need to notify manually // But we just add a element to list without changing the list itself
// // So we need to notify manually
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member //
_results.notifyListeners(); // ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
})); _results.notifyListeners();
}),
);
} }
@override @override

View File

@@ -20,10 +20,7 @@ class ServerEditPage extends StatefulWidget {
const ServerEditPage({super.key, this.args}); const ServerEditPage({super.key, this.args});
static const route = AppRoute<bool, SpiRequiredArgs>( static const route = AppRoute<bool, SpiRequiredArgs>(page: ServerEditPage.new, path: '/servers/edit');
page: ServerEditPage.new,
path: '/servers/edit',
);
@override @override
State<ServerEditPage> createState() => _ServerEditPageState(); State<ServerEditPage> createState() => _ServerEditPageState();
@@ -118,15 +115,9 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
} }
Widget _buildForm() { Widget _buildForm() {
final topItems = [ final topItems = [_buildWriteScriptTip(), if (isMobile) _buildQrScan()];
_buildWriteScriptTip(),
if (isMobile) _buildQrScan(),
];
final children = [ final children = [
Row( Row(mainAxisAlignment: MainAxisAlignment.center, children: topItems.joinWith(UIs.width13).toList()),
mainAxisAlignment: MainAxisAlignment.center,
children: topItems.joinWith(UIs.width13).toList(),
),
Input( Input(
autoFocus: true, autoFocus: true,
controller: _nameController, controller: _nameController,
@@ -173,10 +164,9 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
TagTile(tags: _tags, allTags: ServerProvider.tags.value).cardx, TagTile(tags: _tags, allTags: ServerProvider.tags.value).cardx,
ListTile( ListTile(
title: Text(l10n.autoConnect), title: Text(l10n.autoConnect),
trailing: ListenableBuilder( trailing: _autoConnect.listenVal(
listenable: _autoConnect, (val) => Switch(
builder: (_, __) => Switch( value: val,
value: _autoConnect.value,
onChanged: (val) { onChanged: (val) {
_autoConnect.value = val; _autoConnect.value = val;
}, },
@@ -193,10 +183,9 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
Widget _buildAuth() { Widget _buildAuth() {
final switch_ = ListTile( final switch_ = ListTile(
title: Text(l10n.keyAuth), title: Text(l10n.keyAuth),
trailing: ListenableBuilder( trailing: _keyIdx.listenVal(
listenable: _keyIdx, (v) => Switch(
builder: (_, __) => Switch( value: v != null,
value: _keyIdx.value != null,
onChanged: (val) { onChanged: (val) {
if (val) { if (val) {
_keyIdx.value = -1; _keyIdx.value = -1;
@@ -209,14 +198,13 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
); );
/// Put [switch_] out of [ValueBuilder] to avoid rebuild /// Put [switch_] out of [ValueBuilder] to avoid rebuild
return ListenableBuilder( return _keyIdx.listenVal((v) {
listenable: _keyIdx, final children = <Widget>[switch_];
builder: (_, __) { if (v != null) {
final children = <Widget>[switch_]; children.add(_buildKeyAuth());
if (_keyIdx.value != null) { } else {
children.add(_buildKeyAuth()); children.add(
} else { Input(
children.add(Input(
controller: _passwordController, controller: _passwordController,
obscureText: true, obscureText: true,
type: TextInputType.text, type: TextInputType.text,
@@ -225,52 +213,43 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
hint: l10n.pwd, hint: l10n.pwd,
suggestion: false, suggestion: false,
onSubmitted: (_) => _onSave(), onSubmitted: (_) => _onSave(),
)); ),
} );
return Column(children: children); }
}, return Column(children: children);
); });
} }
Widget _buildKeyAuth() { Widget _buildKeyAuth() {
return PrivateKeyProvider.pkis.listenVal( return PrivateKeyProvider.pkis.listenVal((pkis) {
(pkis) { final tiles = List<Widget>.generate(pkis.length, (index) {
final tiles = List<Widget>.generate(pkis.length, (index) { final e = pkis[index];
final e = pkis[index]; return ListTile(
return ListTile( contentPadding: const EdgeInsets.only(left: 10, right: 15),
contentPadding: const EdgeInsets.only(left: 10, right: 15), leading: Radio<int>(
leading: Radio<int>( value: index,
value: index, groupValue: _keyIdx.value,
groupValue: _keyIdx.value, onChanged: (value) => _keyIdx.value = value,
onChanged: (value) => _keyIdx.value = value,
),
title: Text(e.id, textAlign: TextAlign.start),
subtitle: Text(
e.type ?? l10n.unknown,
textAlign: TextAlign.start,
style: UIs.textGrey,
),
trailing: Btn.icon(
icon: const Icon(Icons.edit),
onTap: () => PrivateKeyEditPage.route.go(
context,
args: PrivateKeyEditPageArgs(pki: e),
),
),
onTap: () => _keyIdx.value = index,
);
});
tiles.add(
ListTile(
title: Text(libL10n.add),
contentPadding: const EdgeInsets.only(left: 23, right: 23),
trailing: const Icon(Icons.add),
onTap: () => PrivateKeyEditPage.route.go(context),
), ),
title: Text(e.id, textAlign: TextAlign.start),
subtitle: Text(e.type ?? l10n.unknown, textAlign: TextAlign.start, style: UIs.textGrey),
trailing: Btn.icon(
icon: const Icon(Icons.edit),
onTap: () => PrivateKeyEditPage.route.go(context, args: PrivateKeyEditPageArgs(pki: e)),
),
onTap: () => _keyIdx.value = index,
); );
return _keyIdx.listenVal((_) => Column(children: tiles)).cardx; });
}, tiles.add(
); ListTile(
title: Text(libL10n.add),
contentPadding: const EdgeInsets.only(left: 23, right: 23),
trailing: const Icon(Icons.add),
onTap: () => PrivateKeyEditPage.route.go(context),
),
);
return _keyIdx.listenVal((_) => Column(children: tiles)).cardx;
});
} }
Widget _buildEnvs() { Widget _buildEnvs() {
@@ -282,10 +261,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
title: Text(l10n.envVars), title: Text(l10n.envVars),
trailing: const Icon(Icons.keyboard_arrow_right), trailing: const Icon(Icons.keyboard_arrow_right),
onTap: () async { onTap: () async {
final res = await KvEditor.route.go( final res = await KvEditor.route.go(context, KvEditorArgs(data: spi?.envs ?? {}));
context,
KvEditorArgs(data: spi?.envs ?? {}),
);
if (res == null) return; if (res == null) return;
_env.value = res; _env.value = res;
}, },
@@ -385,10 +361,9 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
ListTile( ListTile(
leading: const Icon(MingCute.certificate_line), leading: const Icon(MingCute.certificate_line),
title: TipText('PVE ${l10n.ignoreCert}', l10n.pveIgnoreCertTip), title: TipText('PVE ${l10n.ignoreCert}', l10n.pveIgnoreCertTip),
trailing: ListenableBuilder( trailing: _pveIgnoreCert.listenVal(
listenable: _pveIgnoreCert, (v) => Switch(
builder: (_, __) => Switch( value: v,
value: _pveIgnoreCert.value,
onChanged: (val) { onChanged: (val) {
_pveIgnoreCert.value = val; _pveIgnoreCert.value = val;
}, },
@@ -404,17 +379,15 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
CenterGreyTitle(l10n.customCmd), CenterGreyTitle(l10n.customCmd),
_customCmds.listenVal( _customCmds.listenVal((vals) {
(vals) { return ListTile(
return ListTile( leading: const Icon(BoxIcons.bxs_file_json),
leading: const Icon(BoxIcons.bxs_file_json), title: const Text('JSON'),
title: const Text('JSON'), subtitle: vals.isEmpty ? null : Text(vals.keys.join(','), style: UIs.textGrey),
subtitle: vals.isEmpty ? null : Text(vals.keys.join(','), style: UIs.textGrey), trailing: const Icon(Icons.keyboard_arrow_right),
trailing: const Icon(Icons.keyboard_arrow_right), onTap: _onTapCustomItem,
onTap: _onTapCustomItem, );
); }).cardx,
},
).cardx,
ListTile( ListTile(
leading: const Icon(MingCute.doc_line), leading: const Icon(MingCute.doc_line),
title: Text(libL10n.doc), title: Text(libL10n.doc),
@@ -464,10 +437,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
} }
Widget _buildFAB() { Widget _buildFAB() {
return FloatingActionButton( return FloatingActionButton(onPressed: _onSave, child: const Icon(Icons.save));
onPressed: _onSave,
child: const Icon(Icons.save),
);
} }
Widget _buildJumpServer() { Widget _buildJumpServer() {
@@ -477,36 +447,31 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
.where((e) => e.spi.jumpId == null) .where((e) => e.spi.jumpId == null)
.where((e) => e.spi.id != spi?.id) .where((e) => e.spi.id != spi?.id)
.toList(); .toList();
final choice = _jumpServer.listenVal( final choice = _jumpServer.listenVal((val) {
(val) { final srv = srvs.firstWhereOrNull((e) => e.id == _jumpServer.value);
final srv = srvs.firstWhereOrNull((e) => e.id == _jumpServer.value); return Choice<Server>(
return Choice<Server>( multiple: false,
multiple: false, clearable: true,
clearable: true, value: srv != null ? [srv] : [],
value: srv != null ? [srv] : [], builder: (state, _) => Wrap(
builder: (state, _) => Wrap( children: List<Widget>.generate(srvs.length, (index) {
children: List<Widget>.generate( final item = srvs[index];
srvs.length, return ChoiceChipX<Server>(
(index) { label: item.spi.name,
final item = srvs[index]; state: state,
return ChoiceChipX<Server>( value: item,
label: item.spi.name, onSelected: (srv, on) {
state: state, if (on) {
value: item, _jumpServer.value = srv.spi.id;
onSelected: (srv, on) { } else {
if (on) { _jumpServer.value = null;
_jumpServer.value = srv.spi.id; }
} else {
_jumpServer.value = null;
}
},
);
}, },
), );
), }),
); ),
}, );
); });
return ExpandTile( return ExpandTile(
leading: const Icon(Icons.map), leading: const Icon(Icons.map),
initiallyExpanded: _jumpServer.value != null, initiallyExpanded: _jumpServer.value != null,
@@ -537,10 +502,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
text: libL10n.import, text: libL10n.import,
icon: const Icon(Icons.qr_code, color: Colors.grey), icon: const Icon(Icons.qr_code, color: Colors.grey),
onTap: () async { onTap: () async {
final ret = await BarcodeScannerPage.route.go( final ret = await BarcodeScannerPage.route.go(context, args: const BarcodeScannerPageArgs());
context,
args: const BarcodeScannerPageArgs(),
);
final code = ret?.text; final code = ret?.text;
if (code == null) return; if (code == null) return;
try { try {
@@ -560,9 +522,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
onPressed: () { onPressed: () {
context.showRoundDialog( context.showRoundDialog(
title: libL10n.attention, title: libL10n.attention,
child: Text(libL10n.askContinue( child: Text(libL10n.askContinue('${libL10n.delete} ${l10n.server}(${spi!.name})')),
'${libL10n.delete} ${l10n.server}(${spi!.name})',
)),
actions: Btn.ok( actions: Btn.ok(
onTap: () async { onTap: () async {
context.pop(); context.pop();
@@ -587,10 +547,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
extension on _ServerEditPageState { extension on _ServerEditPageState {
void _onTapCustomItem() async { void _onTapCustomItem() async {
final res = await KvEditor.route.go( final res = await KvEditor.route.go(context, KvEditorArgs(data: _customCmds.value));
context,
KvEditorArgs(data: _customCmds.value),
);
if (res == null) return; if (res == null) return;
_customCmds.value = res; _customCmds.value = res;
} }
@@ -602,21 +559,12 @@ extension on _ServerEditPageState {
} }
if (_keyIdx.value == null && _passwordController.text.isEmpty) { if (_keyIdx.value == null && _passwordController.text.isEmpty) {
final cancel = await context.showRoundDialog<bool>( final ok = await context.showRoundDialog<bool>(
title: libL10n.attention, title: libL10n.attention,
child: Text(libL10n.askContinue(l10n.useNoPwd)), child: Text(libL10n.askContinue(l10n.useNoPwd)),
actions: [ actions: Btnx.cancelRedOk,
TextButton(
onPressed: () => context.pop(false),
child: Text(libL10n.ok),
),
TextButton(
onPressed: () => context.pop(true),
child: Text(libL10n.cancel),
)
],
); );
if (cancel != false) return; if (ok != true) return;
} }
// If [_pubKeyIndex] is -1, it means that the user has not selected // If [_pubKeyIndex] is -1, it means that the user has not selected
@@ -644,11 +592,7 @@ extension on _ServerEditPageState {
final wolEmpty = _wolMacCtrl.text.isEmpty && _wolIpCtrl.text.isEmpty && _wolPwdCtrl.text.isEmpty; final wolEmpty = _wolMacCtrl.text.isEmpty && _wolIpCtrl.text.isEmpty && _wolPwdCtrl.text.isEmpty;
final wol = wolEmpty final wol = wolEmpty
? null ? null
: WakeOnLanCfg( : WakeOnLanCfg(mac: _wolMacCtrl.text, ip: _wolIpCtrl.text, pwd: _wolPwdCtrl.text.selfNotEmptyOrNull);
mac: _wolMacCtrl.text,
ip: _wolIpCtrl.text,
pwd: _wolPwdCtrl.text.selfNotEmptyOrNull,
);
if (wol != null) { if (wol != null) {
final wolValidation = wol.validate(); final wolValidation = wol.validate();
if (!wolValidation.$2) { if (!wolValidation.$2) {
@@ -696,9 +640,7 @@ extension on _ServerEditPageState {
if (spi.keyId == null) { if (spi.keyId == null) {
_passwordController.text = spi.pwd ?? ''; _passwordController.text = spi.pwd ?? '';
} else { } else {
_keyIdx.value = PrivateKeyProvider.pkis.value.indexWhere( _keyIdx.value = PrivateKeyProvider.pkis.value.indexWhere((e) => e.id == spi.keyId);
(e) => e.id == spi.keyId,
);
} }
/// List in dart is passed by pointer, so you need to copy it here /// List in dart is passed by pointer, so you need to copy it here

View File

@@ -85,29 +85,26 @@ ${ss.err?.message ?? 'null'}
Widget _buildDisk(ServerStatus ss, String id) { Widget _buildDisk(ServerStatus ss, String id) {
final cardNoti = _getCardNoti(id); final cardNoti = _getCardNoti(id);
return ListenableBuilder( return cardNoti.listenVal((v) {
listenable: cardNoti, final isSpeed = v.diskIO ?? !Stores.setting.serverTabPreferDiskAmount.fetch();
builder: (_, __) {
final isSpeed = cardNoti.value.diskIO ?? !Stores.setting.serverTabPreferDiskAmount.fetch();
final (r, w) = ss.diskIO.cachedAllSpeed; final (r, w) = ss.diskIO.cachedAllSpeed;
return AnimatedSwitcher( return AnimatedSwitcher(
duration: const Duration(milliseconds: 377), duration: const Duration(milliseconds: 377),
transitionBuilder: (Widget child, Animation<double> animation) { transitionBuilder: (child, animation) {
return FadeTransition(opacity: animation, child: child); return FadeTransition(opacity: animation, child: child);
},
child: _buildIOData(
isSpeed ? '${l10n.read}:\n$r' : 'Total:\n${ss.diskUsage?.size.kb2Str}',
isSpeed ? '${l10n.write}:\n$w' : 'Used:\n${ss.diskUsage?.used.kb2Str}',
onTap: () {
cardNoti.value = v.copyWith(diskIO: !isSpeed);
}, },
child: _buildIOData( key: ValueKey(isSpeed),
isSpeed ? '${l10n.read}:\n$r' : 'Total:\n${ss.diskUsage?.size.kb2Str}', ),
isSpeed ? '${l10n.write}:\n$w' : 'Used:\n${ss.diskUsage?.used.kb2Str}', );
onTap: () { });
cardNoti.value = cardNoti.value.copyWith(diskIO: !isSpeed);
},
key: ValueKey(isSpeed),
),
);
},
);
} }
Widget _buildNet(ServerStatus ss, String id) { Widget _buildNet(ServerStatus ss, String id) {

View File

@@ -42,17 +42,14 @@ extension on _ServerPageState {
final title = _buildServerCardTitle(srv); final title = _buildServerCardTitle(srv);
final List<Widget> children = [title, _buildNormalCard(srv.status, srv.spi)]; final List<Widget> children = [title, _buildNormalCard(srv.status, srv.spi)];
return ListenableBuilder( return _getCardNoti(id).listenVal((_) {
listenable: _getCardNoti(id), return Column(
builder: (_, __) { mainAxisSize: MainAxisSize.min,
return Column( mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center, children: children,
crossAxisAlignment: CrossAxisAlignment.center, );
children: children, });
);
},
);
}); });
}, },
); );

View File

@@ -319,9 +319,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
], ],
), ),
UIs.height13, UIs.height13,
if (Stores.setting.moveServerFuncs.fetch() && if (Stores.setting.moveServerFuncs.fetch())
// Discussion #146
!Stores.setting.serverTabUseOldUI.fetch())
SizedBox(height: 27, child: ServerFuncBtns(spi: spi)), SizedBox(height: 27, child: ServerFuncBtns(spi: spi)),
], ],
); );

View File

@@ -100,9 +100,7 @@ extension _Utils on _ServerPageState {
if (flip) { if (flip) {
return _ServerPageState._kCardHeightFlip; return _ServerPageState._kCardHeightFlip;
} }
if (Stores.setting.moveServerFuncs.fetch() && if (Stores.setting.moveServerFuncs.fetch()) {
// Discussion #146
!Stores.setting.serverTabUseOldUI.fetch()) {
return _ServerPageState._kCardHeightMoveOutFuncs; return _ServerPageState._kCardHeightMoveOutFuncs;
} }
return _ServerPageState._kCardHeightNormal; return _ServerPageState._kCardHeightNormal;

View File

@@ -173,6 +173,7 @@ extension _Server on _AppSettingsPageState {
title: Text(l10n.more), title: Text(l10n.more),
initiallyExpanded: false, initiallyExpanded: false,
children: [ children: [
_buildServerTabPreferDiskAmount(),
_buildRememberPwdInMem(), _buildRememberPwdInMem(),
_buildTextScaler(), _buildTextScaler(),
_buildKeepStatusWhenErr(), _buildKeepStatusWhenErr(),
@@ -298,4 +299,11 @@ extension _Server on _AppSettingsPageState {
}, },
); );
} }
Widget _buildServerTabPreferDiskAmount() {
return ListTile(
title: Text(l10n.preferDiskAmount),
trailing: StoreSwitch(prop: Stores.setting.serverTabPreferDiskAmount),
);
}
} }

View File

@@ -11,7 +11,7 @@ abstract final class PlatformPublicSettings {
title: Text(libL10n.bioAuth), title: Text(libL10n.bioAuth),
subtitle: const Text('...', style: UIs.textGrey), subtitle: const Text('...', style: UIs.textGrey),
), ),
error: (e, __) => ListTile( error: (e, _) => ListTile(
title: Text(libL10n.bioAuth), title: Text(libL10n.bioAuth),
subtitle: Text('${libL10n.fail}: $e', style: UIs.textGrey), subtitle: Text('${libL10n.fail}: $e', style: UIs.textGrey),
), ),

View File

@@ -189,7 +189,7 @@ class SSHPageState extends State<SSHPage>
if (hasBg) { if (hasBg) {
children.add( children.add(
Positioned.fill( Positioned.fill(
child: Image.file(file, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const SizedBox()), child: Image.file(file, fit: BoxFit.cover, errorBuilder: (_, _, _) => const SizedBox()),
), ),
); );
if (blur > 0) { if (blur > 0) {
@@ -247,8 +247,8 @@ class SSHPageState extends State<SSHPage>
height: _virtKeysHeight + _media.padding.bottom, height: _virtKeysHeight + _media.padding.bottom,
child: ChangeNotifierProvider( child: ChangeNotifierProvider(
create: (_) => _keyboard, create: (_) => _keyboard,
builder: (_, __) => Consumer<VirtKeyProvider>( builder: (_, _) => Consumer<VirtKeyProvider>(
builder: (_, __, ___) { builder: (_, _, _) {
return _buildVirtualKey(); return _buildVirtualKey();
}, },
), ),

View File

@@ -186,7 +186,7 @@ final class _TabBar extends StatelessWidget implements PreferredSizeWidget {
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 5), padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 5),
itemCount: names.length, itemCount: names.length,
itemBuilder: (_, idx) => _buildItem(idx), itemBuilder: (_, idx) => _buildItem(idx),
separatorBuilder: (_, __) => Padding( separatorBuilder: (_, _) => Padding(
padding: const EdgeInsets.symmetric(vertical: 17), padding: const EdgeInsets.symmetric(vertical: 17),
child: Container( child: Container(
color: const Color.fromARGB(61, 158, 158, 158), color: const Color.fromARGB(61, 158, 158, 158),

View File

@@ -26,28 +26,18 @@ final class SftpPageArgs {
final bool isSelect; final bool isSelect;
final String? initPath; final String? initPath;
const SftpPageArgs({ const SftpPageArgs({required this.spi, this.isSelect = false, this.initPath});
required this.spi,
this.isSelect = false,
this.initPath,
});
} }
class SftpPage extends StatefulWidget { class SftpPage extends StatefulWidget {
final SftpPageArgs args; final SftpPageArgs args;
const SftpPage({ const SftpPage({super.key, required this.args});
super.key,
required this.args,
});
@override @override
State<SftpPage> createState() => _SftpPageState(); State<SftpPage> createState() => _SftpPageState();
static const route = AppRouteArg<String, SftpPageArgs>( static const route = AppRouteArg<String, SftpPageArgs>(page: SftpPage.new, path: '/sftp');
page: SftpPage.new,
path: '/sftp',
);
} }
class _SftpPageState extends State<SftpPage> with AfterLayoutMixin { class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
@@ -64,20 +54,14 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final children = [ final children = [
Btn.icon( Btn.icon(icon: const Icon(Icons.downloading), onTap: () => SftpMissionPage.route.go(context)),
icon: const Icon(Icons.downloading),
onTap: () => SftpMissionPage.route.go(context),
),
_buildSortMenu(), _buildSortMenu(),
_buildSearchBtn(), _buildSearchBtn(),
]; ];
if (isDesktop) children.add(_buildRefreshBtn()); if (isDesktop) children.add(_buildRefreshBtn());
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(title: Text(widget.args.spi.name), actions: children),
title: Text(widget.args.spi.name),
actions: children,
),
body: _buildFileView(), body: _buildFileView(),
bottomNavigationBar: _buildBottom(), bottomNavigationBar: _buildBottom(),
); );
@@ -105,56 +89,45 @@ extension _UI on _SftpPageState {
(_SortType.size, l10n.size), (_SortType.size, l10n.size),
(_SortType.time, l10n.time), (_SortType.time, l10n.time),
]; ];
return _sortOption.listenVal( return _sortOption.listenVal((value) {
(value) { return PopupMenuButton<_SortType>(
return PopupMenuButton<_SortType>( icon: const Icon(Icons.sort),
icon: const Icon(Icons.sort), itemBuilder: (context) {
itemBuilder: (context) { return options.map((r) {
return options.map((r) { final (type, name) = r;
final (type, name) = r; final selected = type == value.sortBy;
final selected = type == value.sortBy; final title = selected ? "$name (${value.reversed ? '-' : '+'})" : name;
final title = selected ? "$name (${value.reversed ? '-' : '+'})" : name; return PopupMenuItem(
return PopupMenuItem( value: type,
value: type, child: Text(
child: Text( title,
title, style: TextStyle(
style: TextStyle( color: selected ? UIs.primaryColor : null,
color: selected ? UIs.primaryColor : null, fontWeight: selected ? FontWeight.bold : null,
fontWeight: selected ? FontWeight.bold : null,
),
), ),
); ),
}).toList(); );
}, }).toList();
onSelected: (sortBy) { },
final old = _sortOption.value; onSelected: (sortBy) {
if (old.sortBy == sortBy) { final old = _sortOption.value;
_sortOption.value = old.copyWith(reversed: !old.reversed); if (old.sortBy == sortBy) {
} else { _sortOption.value = old.copyWith(reversed: !old.reversed);
_sortOption.value = old.copyWith(sortBy: sortBy); } else {
} _sortOption.value = old.copyWith(sortBy: sortBy);
}, }
); },
}, );
); });
} }
Widget _buildBottom() { Widget _buildBottom() {
final children = widget.args.isSelect final children = widget.args.isSelect
? [ ? [
IconButton( IconButton(onPressed: () => context.pop(_status.path.path), icon: const Icon(Icons.done)),
onPressed: () => context.pop(_status.path.path),
icon: const Icon(Icons.done),
),
_buildSearchBtn(), _buildSearchBtn(),
] ]
: [ : [_buildBackBtn(), _buildHomeBtn(), _buildAddBtn(), _buildGotoBtn(), _buildUploadBtn()];
_buildBackBtn(),
_buildHomeBtn(),
_buildAddBtn(),
_buildGotoBtn(),
_buildUploadBtn(),
];
return SafeArea( return SafeArea(
child: Container( child: Container(
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11), padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
@@ -162,10 +135,7 @@ extension _UI on _SftpPageState {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
OmitStartText(_status.path.path), OmitStartText(_status.path.path),
Row( Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: children),
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: children,
)
], ],
), ),
), ),
@@ -182,10 +152,7 @@ extension _UI on _SftpPageState {
child: ValBuilder( child: ValBuilder(
listenable: _sortOption, listenable: _sortOption,
builder: (sortOption) { builder: (sortOption) {
final files = sortOption.sortBy.sort( final files = sortOption.sortBy.sort(_status.files, reversed: sortOption.reversed);
_status.files,
reversed: sortOption.reversed,
);
return ListView.builder( return ListView.builder(
itemCount: files.length, itemCount: files.length,
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 3),
@@ -209,12 +176,7 @@ extension _UI on _SftpPageState {
leading: Icon(isDir ? Icons.folder_outlined : Icons.insert_drive_file), leading: Icon(isDir ? Icons.folder_outlined : Icons.insert_drive_file),
title: Text(file.filename), title: Text(file.filename),
trailing: trailing, trailing: trailing,
subtitle: isDir subtitle: isDir ? null : Text((file.attr.size ?? 0).bytes2Str, style: UIs.textGrey),
? null
: Text(
(file.attr.size ?? 0).bytes2Str,
style: UIs.textGrey,
),
onTap: () { onTap: () {
beforeTap?.call(); beforeTap?.call();
if (isDir) { if (isDir) {
@@ -236,16 +198,8 @@ extension _UI on _SftpPageState {
extension _Actions on _SftpPageState { extension _Actions on _SftpPageState {
void _onItemPress(SftpName file, bool notDir) { void _onItemPress(SftpName file, bool notDir) {
final children = [ final children = [
ListTile( ListTile(leading: const Icon(Icons.delete), title: Text(libL10n.delete), onTap: () => _delete(file)),
leading: const Icon(Icons.delete), ListTile(leading: const Icon(Icons.abc), title: Text(libL10n.rename), onTap: () => _rename(file)),
title: Text(libL10n.delete),
onTap: () => _delete(file),
),
ListTile(
leading: const Icon(Icons.abc),
title: Text(libL10n.rename),
onTap: () => _rename(file),
),
ListTile( ListTile(
leading: const Icon(MingCute.copy_line), leading: const Icon(MingCute.copy_line),
title: Text(l10n.copyPath), title: Text(l10n.copyPath),
@@ -270,21 +224,19 @@ extension _Actions on _SftpPageState {
final permStr = newPerm.perm; final permStr = newPerm.perm;
if (ok == true && permStr != perm.perm) { if (ok == true && permStr != perm.perm) {
await context.showLoadingDialog(fn: () async { await context.showLoadingDialog(
await _client.run('chmod $permStr "${_getRemotePath(file)}"'); fn: () async {
await _listDir(); await _client.run('chmod $permStr "${_getRemotePath(file)}"');
}); await _listDir();
},
);
} }
}, },
), ),
]; ];
if (notDir) { if (notDir) {
children.addAll([ children.addAll([
ListTile( ListTile(leading: const Icon(Icons.edit), title: Text(libL10n.edit), onTap: () => _edit(file)),
leading: const Icon(Icons.edit),
title: Text(libL10n.edit),
onTap: () => _edit(file),
),
ListTile( ListTile(
leading: const Icon(Icons.download), leading: const Icon(Icons.download),
title: Text(libL10n.download), title: Text(libL10n.download),
@@ -300,10 +252,7 @@ extension _Actions on _SftpPageState {
]); ]);
} }
context.showRoundDialog( context.showRoundDialog(
child: Column( child: Column(mainAxisSize: MainAxisSize.min, children: children),
mainAxisSize: MainAxisSize.min,
children: children,
),
); );
} }
@@ -323,27 +272,16 @@ extension _Actions on _SftpPageState {
final size = name.attr.size; final size = name.attr.size;
if (size == null || size > Miscs.editorMaxSize) { if (size == null || size > Miscs.editorMaxSize) {
context.showSnackBar(l10n.fileTooLarge( context.showSnackBar(l10n.fileTooLarge(name.filename, size ?? 0, Miscs.editorMaxSize));
name.filename,
size ?? 0,
Miscs.editorMaxSize,
));
return; return;
} }
final remotePath = _getRemotePath(name); final remotePath = _getRemotePath(name);
final localPath = _getLocalPath(remotePath); final localPath = _getLocalPath(remotePath);
final completer = Completer(); final completer = Completer();
final req = SftpReq( final req = SftpReq(widget.args.spi, remotePath, localPath, SftpReqType.download);
widget.args.spi,
remotePath,
localPath,
SftpReqType.download,
);
SftpProvider.add(req, completer: completer); SftpProvider.add(req, completer: completer);
final (suc, err) = await context.showLoadingDialog( final (suc, err) = await context.showLoadingDialog(fn: () => completer.future);
fn: () => completer.future,
);
if (suc == null || err != null) return; if (suc == null || err != null) return;
await EditorPage.route.go( await EditorPage.route.go(
@@ -351,12 +289,7 @@ extension _Actions on _SftpPageState {
args: EditorPageArgs( args: EditorPageArgs(
path: localPath, path: localPath,
onSave: (_) { onSave: (_) {
SftpProvider.add(SftpReq( SftpProvider.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
req.spi,
remotePath,
localPath,
SftpReqType.upload,
));
context.showSnackBar(l10n.added2List); context.showSnackBar(l10n.added2List);
}, },
closeAfterSave: SettingStore.instance.closeAfterSave.fetch(), closeAfterSave: SettingStore.instance.closeAfterSave.fetch(),
@@ -371,28 +304,20 @@ extension _Actions on _SftpPageState {
title: libL10n.attention, title: libL10n.attention,
child: Text('${l10n.dl2Local(name.filename)}\n${l10n.keepForeground}'), child: Text('${l10n.dl2Local(name.filename)}\n${l10n.keepForeground}'),
actions: [ actions: [
TextButton( TextButton(onPressed: () => context.pop(), child: Text(libL10n.cancel)),
onPressed: () => context.pop(),
child: Text(libL10n.cancel),
),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
context.pop(); context.pop();
final remotePath = _getRemotePath(name); final remotePath = _getRemotePath(name);
SftpProvider.add( SftpProvider.add(
SftpReq( SftpReq(widget.args.spi, remotePath, _getLocalPath(remotePath), SftpReqType.download),
widget.args.spi,
remotePath,
_getLocalPath(remotePath),
SftpReqType.download,
),
); );
context.pop(); context.pop();
}, },
child: Text(libL10n.download), child: Text(libL10n.download),
) ),
], ],
); );
} }
@@ -416,9 +341,7 @@ extension _Actions on _SftpPageState {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTile( ListTile(title: Text(text)),
title: Text(text),
),
if (!useRmr) if (!useRmr)
StatefulBuilder( StatefulBuilder(
builder: (_, setState) { builder: (_, setState) {
@@ -436,10 +359,7 @@ extension _Actions on _SftpPageState {
], ],
), ),
actions: [ actions: [
TextButton( TextButton(onPressed: () => context.pop(), child: Text(libL10n.cancel)),
onPressed: () => context.pop(),
child: Text(libL10n.cancel),
),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
context.pop(); context.pop();
@@ -474,10 +394,7 @@ extension _Actions on _SftpPageState {
void onSubmitted() async { void onSubmitted() async {
final text = textController.text.trim(); final text = textController.text.trim();
if (text.isEmpty) { if (text.isEmpty) {
context.showRoundDialog( context.showRoundDialog(child: Text(libL10n.empty), actions: Btnx.oks);
child: Text(libL10n.empty),
actions: Btnx.oks,
);
return; return;
} }
context.pop(); context.pop();
@@ -504,10 +421,7 @@ extension _Actions on _SftpPageState {
suggestion: true, suggestion: true,
onSubmitted: (_) => onSubmitted(), onSubmitted: (_) => onSubmitted(),
), ),
actions: Btn.ok( actions: Btn.ok(onTap: onSubmitted, red: true).toList,
onTap: onSubmitted,
red: true,
).toList,
); );
} }
@@ -518,11 +432,7 @@ extension _Actions on _SftpPageState {
void onSubmitted() async { void onSubmitted() async {
final text = textController.text.trim(); final text = textController.text.trim();
if (text.isEmpty) { if (text.isEmpty) {
context.showRoundDialog( context.showRoundDialog(title: libL10n.attention, child: Text(libL10n.empty), actions: Btnx.oks);
title: libL10n.attention,
child: Text(libL10n.empty),
actions: Btnx.oks,
);
return; return;
} }
context.pop(); context.pop();
@@ -560,11 +470,7 @@ extension _Actions on _SftpPageState {
void onSubmitted() async { void onSubmitted() async {
final text = textController.text.trim(); final text = textController.text.trim();
if (text.isEmpty) { if (text.isEmpty) {
context.showRoundDialog( context.showRoundDialog(title: libL10n.attention, child: Text(libL10n.empty), actions: Btnx.oks);
title: libL10n.attention,
child: Text(libL10n.empty),
actions: Btnx.oks,
);
return; return;
} }
context.pop(); context.pop();
@@ -685,10 +591,7 @@ extension _Actions on _SftpPageState {
} }
Widget _buildBackBtn() { Widget _buildBackBtn() {
return Btn.icon( return Btn.icon(onTap: _backward, icon: const Icon(Icons.arrow_back));
onTap: _backward,
icon: const Icon(Icons.arrow_back),
);
} }
Widget _buildSearchBtn() { Widget _buildSearchBtn() {
@@ -718,26 +621,16 @@ extension _Actions on _SftpPageState {
return Btn.icon( return Btn.icon(
onTap: () async { onTap: () async {
final idx = await context.showRoundDialog( final idx = await context.showRoundDialog(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Btn.tile( Btn.tile(icon: const Icon(Icons.open_in_new), text: l10n.system, onTap: () => context.pop(1)),
icon: const Icon(Icons.open_in_new), Btn.tile(icon: const Icon(Icons.folder), text: l10n.inner, onTap: () => context.pop(0)),
text: l10n.system, ],
onTap: () => context.pop(1), ),
), );
Btn.tile(
icon: const Icon(Icons.folder),
text: l10n.inner,
onTap: () => context.pop(0),
),
],
));
final path = switch (idx) { final path = switch (idx) {
0 => await LocalFilePage.route.go( 0 => await LocalFilePage.route.go(context, args: const LocalFilePageArgs(isPickFile: true)),
context,
args: const LocalFilePageArgs(isPickFile: true),
),
1 => await Pfs.pickFilePath(), 1 => await Pfs.pickFilePath(),
_ => null, _ => null,
}; };
@@ -747,9 +640,7 @@ extension _Actions on _SftpPageState {
final fileName = path.split(Platform.pathSeparator).lastOrNull; final fileName = path.split(Platform.pathSeparator).lastOrNull;
final remotePath = '$remoteDir/$fileName'; final remotePath = '$remoteDir/$fileName';
Loggers.app.info('SFTP upload local: $path, remote: $remotePath'); Loggers.app.info('SFTP upload local: $path, remote: $remotePath');
SftpProvider.add( SftpProvider.add(SftpReq(widget.args.spi, remotePath, path, SftpReqType.upload));
SftpReq(widget.args.spi, remotePath, path, SftpReqType.upload),
);
}, },
icon: const Icon(Icons.upload_file), icon: const Icon(Icons.upload_file),
); );
@@ -761,16 +652,8 @@ extension _Actions on _SftpPageState {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Btn.tile( Btn.tile(icon: const Icon(Icons.folder), text: libL10n.folder, onTap: _mkdir),
icon: const Icon(Icons.folder), Btn.tile(icon: const Icon(Icons.insert_drive_file), text: libL10n.file, onTap: _newFile),
text: libL10n.folder,
onTap: _mkdir,
),
Btn.tile(
icon: const Icon(Icons.insert_drive_file),
text: libL10n.file,
onTap: _newFile,
),
], ],
), ),
), ),
@@ -788,11 +671,9 @@ extension _Actions on _SftpPageState {
if (!Stores.setting.recordHistory.fetch()) { if (!Stores.setting.recordHistory.fetch()) {
return []; return [];
} }
return Stores.history.sftpGoPath.all.cast<String>().where( return Stores.history.sftpGoPath.all.cast<String>().where((e) => e.contains(val.text));
(element) => element.contains(val.text),
);
}, },
fieldViewBuilder: (_, controller, node, __) { fieldViewBuilder: (_, controller, node, _) {
return Input( return Input(
autoFocus: true, autoFocus: true,
icon: Icons.abc, icon: Icons.abc,
@@ -821,10 +702,7 @@ extension _Actions on _SftpPageState {
} }
Widget _buildRefreshBtn() { Widget _buildRefreshBtn() {
return Btn.icon( return Btn.icon(onTap: _listDir, icon: const Icon(Icons.refresh));
onTap: _listDir,
icon: const Icon(Icons.refresh),
);
} }
Widget _buildHomeBtn() { Widget _buildHomeBtn() {
@@ -913,8 +791,7 @@ String _getTime(int? unixMill) {
enum _SortType { enum _SortType {
name, name,
time, time,
size, size;
;
List<SftpName> sort(List<SftpName> files, {bool reversed = false}) { List<SftpName> sort(List<SftpName> files, {bool reversed = false}) {
var comparator = ChainComparator<SftpName>.create(); var comparator = ChainComparator<SftpName>.create();
@@ -933,24 +810,10 @@ enum _SortType {
); );
break; break;
case _SortType.time: case _SortType.time:
files.sort( files.sort(comparator.thenCompareBy<num>((x) => x.attr.modifyTime ?? 0, reversed: reversed).compare);
comparator
.thenCompareBy<num>(
(x) => x.attr.modifyTime ?? 0,
reversed: reversed,
)
.compare,
);
break; break;
case _SortType.size: case _SortType.size:
files.sort( files.sort(comparator.thenCompareBy<num>((x) => x.attr.size ?? 0, reversed: reversed).compare);
comparator
.thenCompareBy<num>(
(x) => x.attr.size ?? 0,
reversed: reversed,
)
.compare,
);
break; break;
} }
return files; return files;
@@ -963,13 +826,7 @@ class _SortOption {
_SortOption({this.sortBy = _SortType.name, this.reversed = false}); _SortOption({this.sortBy = _SortType.name, this.reversed = false});
_SortOption copyWith({ _SortOption copyWith({_SortType? sortBy, bool? reversed}) {
_SortType? sortBy, return _SortOption(sortBy: sortBy ?? this.sortBy, reversed: reversed ?? this.reversed);
bool? reversed,
}) {
return _SortOption(
sortBy: sortBy ?? this.sortBy,
reversed: reversed ?? this.reversed,
);
} }
} }

View File

@@ -5,26 +5,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: _fe_analyzer_shared name: _fe_analyzer_shared
sha256: dc27559385e905ad30838356c5f5d574014ba39872d732111cd07ac0beff4c57 sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "80.0.0" version: "82.0.0"
analyzer: analyzer:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: analyzer name: analyzer
sha256: "192d1c5b944e7e53b24b5586db760db934b177d4147c42fbca8c8c5f1eb8d11e" sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.3.0" version: "7.4.5"
analyzer_plugin: analyzer_plugin:
dependency: transitive dependency: transitive
description: description:
name: analyzer_plugin name: analyzer_plugin
sha256: b3075265c5ab222f8b3188342dcb50b476286394a40323e85d1fa725035d40a4 sha256: ee188b6df6c85f1441497c7171c84f1392affadc0384f71089cb10a3bc508cef
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.0" version: "0.13.1"
animations: animations:
dependency: transitive dependency: transitive
description: description:
@@ -189,10 +189,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: camera_android_camerax name: camera_android_camerax
sha256: "0bd3d1645df00af2540a22df13ba466ac5fb2838a09bce4089cecdb1712a9e94" sha256: "68d7ec97439108ac22cfba34bb74d0ab53adbc017175116d2cbc5a3d8fc8ea5e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.18" version: "0.6.18+2"
camera_avfoundation: camera_avfoundation:
dependency: transitive dependency: transitive
description: description:
@@ -237,10 +237,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: checked_yaml name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.4"
choice: choice:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -311,10 +311,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: coverage name: coverage
sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" sha256: aa07dbe5f2294c827b7edb9a87bba44a9c15a3cc81bc8da2ca19b37322d30080
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.14.0" version: "1.14.1"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@@ -351,10 +351,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: custom_lint_visitor name: custom_lint_visitor
sha256: "36282d85714af494ee2d7da8c8913630aa6694da99f104fb2ed4afcf8fc857d8" sha256: cba5b6d7a6217312472bf4468cdf68c949488aed7ffb0eab792cd0b6c435054d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0+7.3.0" version: "1.0.0+7.4.5"
dart_style: dart_style:
dependency: transitive dependency: transitive
description: description:
@@ -367,8 +367,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.283" ref: "v1.0.285"
resolved-ref: f9d00c98c2047c7012f5fbce39cf01d386ca136a resolved-ref: "18fb1ad15ee6d2c8c5ec67722bf8b90fe0f4746d"
url: "https://github.com/lollipopkit/dartssh2" url: "https://github.com/lollipopkit/dartssh2"
source: git source: git
version: "2.12.0" version: "2.12.0"
@@ -432,10 +432,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: extended_image_library name: extended_image_library
sha256: ae468c31c375064964de11cbb31310a58c4462df6e3bae1a0bc0066f586795d5 sha256: "1f9a24d3a00c2633891c6a7b5cab2807999eb2d5b597e5133b63f49d113811fe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "5.0.1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@@ -497,8 +497,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.319" ref: "v1.0.321"
resolved-ref: d45c47a24303a2e937fee6753f1d0755805b8483 resolved-ref: e0b3338be10fa71c96d017d873f5e10bb4374709
url: "https://github.com/lppcg/fl_lib" url: "https://github.com/lppcg/fl_lib"
source: git source: git
version: "0.0.1" version: "0.0.1"
@@ -527,10 +527,10 @@ packages:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.0" version: "6.0.0"
flutter_localizations: flutter_localizations:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -553,13 +553,12 @@ packages:
source: hosted source: hosted
version: "0.3.4" version: "0.3.4"
flutter_math_fork: flutter_math_fork:
dependency: "direct overridden" dependency: transitive
description: description:
path: "." name: flutter_math_fork
ref: HEAD sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407"
resolved-ref: "2f270aee06d3ca02ca6d108420921472fd10e5cb" url: "https://pub.dev"
url: "https://github.com/simpleclub/flutter_math" source: hosted
source: git
version: "0.7.4" version: "0.7.4"
flutter_native_splash: flutter_native_splash:
dependency: "direct dev" dependency: "direct dev"
@@ -839,10 +838,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.1" version: "6.0.0"
local_auth: local_auth:
dependency: transitive dependency: transitive
description: description:
@@ -1168,10 +1167,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: qr_code_dart_scan name: qr_code_dart_scan
sha256: bc4fc6f400b4350c6946d123c7871e156459703a61f8fa57d7144df9bbb46610 sha256: "6e1aab64b8f5f768416b471dbc3fb0fc94969c3e236157a96b52a70f9fe12ebb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.10.0" version: "0.10.1"
quiver: quiver:
dependency: transitive dependency: transitive
description: description:
@@ -1807,10 +1806,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: zxing_lib name: zxing_lib
sha256: "870a63610be3f20009ca9201f7ba2d53d7eaefa675c154b3e8c1f6fc55984d04" sha256: d5d81917be2e18b06a2cf4ca12927f3ab957dfbd25bd7b8175b3e9a0ce5c2e2b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.2" version: "1.1.3"
sdks: sdks:
dart: ">=3.8.0 <4.0.0" dart: ">=3.8.0 <4.0.0"
flutter: ">=3.32.1" flutter: ">=3.32.1"

View File

@@ -38,7 +38,7 @@ dependencies:
dartssh2: dartssh2:
git: git:
url: https://github.com/lollipopkit/dartssh2 url: https://github.com/lollipopkit/dartssh2
ref: v1.0.283 ref: v1.0.285
circle_chart: circle_chart:
git: git:
url: https://github.com/lollipopkit/circle_chart url: https://github.com/lollipopkit/circle_chart
@@ -62,7 +62,7 @@ dependencies:
fl_lib: fl_lib:
git: git:
url: https://github.com/lppcg/fl_lib url: https://github.com/lppcg/fl_lib
ref: v1.0.319 ref: v1.0.321
dependency_overrides: dependency_overrides:
# webdav_client_plus: # webdav_client_plus:
@@ -75,16 +75,13 @@ dependency_overrides:
# path: ../fl_lib # path: ../fl_lib
# fl_build: # fl_build:
# path: ../fl_build # path: ../fl_build
flutter_math_fork: # Refer to https://github.com/simpleclub/flutter_math/issues/110
git:
url: https://github.com/simpleclub/flutter_math
dev_dependencies: dev_dependencies:
analyzer: ^7.3.0 analyzer: ^7.3.0
flutter_native_splash: ^2.1.6 flutter_native_splash: ^2.1.6
hive_ce_generator: ^1.9.2 hive_ce_generator: ^1.9.2
build_runner: ^2.4.15 build_runner: ^2.4.15
flutter_lints: ^5.0.0 flutter_lints: ^6.0.0
json_serializable: ^6.8.0 json_serializable: ^6.8.0
freezed: ^2.5.7 freezed: ^2.5.7
riverpod_generator: ^2.6.3 riverpod_generator: ^2.6.3