opt.: redesigned settings page (#587)

This commit is contained in:
lollipopkit🏳️‍⚧️
2024-09-21 22:37:42 +08:00
committed by GitHub
parent d7669c94b8
commit c062c12a0e
21 changed files with 561 additions and 551 deletions

View File

@@ -3,13 +3,11 @@ import 'package:flutter/material.dart';
import 'package:server_box/data/model/server/private_key_info.dart'; import 'package:server_box/data/model/server/private_key_info.dart';
import 'package:server_box/data/model/server/server_private_info.dart'; import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/backup.dart';
import 'package:server_box/view/page/container.dart'; import 'package:server_box/view/page/container.dart';
import 'package:server_box/view/page/home/home.dart'; import 'package:server_box/view/page/home/home.dart';
import 'package:server_box/view/page/iperf.dart'; import 'package:server_box/view/page/iperf.dart';
import 'package:server_box/view/page/ping.dart'; import 'package:server_box/view/page/ping.dart';
import 'package:server_box/view/page/private_key/edit.dart'; import 'package:server_box/view/page/private_key/edit.dart';
import 'package:server_box/view/page/private_key/list.dart';
import 'package:server_box/view/page/pve.dart'; import 'package:server_box/view/page/pve.dart';
import 'package:server_box/view/page/server/detail/view.dart'; import 'package:server_box/view/page/server/detail/view.dart';
import 'package:server_box/view/page/setting/platform/android.dart'; import 'package:server_box/view/page/setting/platform/android.dart';
@@ -18,17 +16,13 @@ import 'package:server_box/view/page/setting/seq/srv_func_seq.dart';
import 'package:server_box/view/page/snippet/result.dart'; import 'package:server_box/view/page/snippet/result.dart';
import 'package:server_box/view/page/ssh/page.dart'; import 'package:server_box/view/page/ssh/page.dart';
import 'package:server_box/view/page/setting/seq/virt_key.dart'; import 'package:server_box/view/page/setting/seq/virt_key.dart';
import 'package:server_box/view/page/storage/local.dart';
import 'package:server_box/data/model/server/snippet.dart'; import 'package:server_box/data/model/server/snippet.dart';
import 'package:server_box/view/page/editor.dart'; import 'package:server_box/view/page/editor.dart';
import 'package:server_box/view/page/process.dart'; import 'package:server_box/view/page/process.dart';
import 'package:server_box/view/page/server/tab.dart'; import 'package:server_box/view/page/server/tab.dart';
import 'package:server_box/view/page/setting/entry.dart';
import 'package:server_box/view/page/setting/seq/srv_detail_seq.dart'; import 'package:server_box/view/page/setting/seq/srv_detail_seq.dart';
import 'package:server_box/view/page/setting/seq/srv_seq.dart'; import 'package:server_box/view/page/setting/seq/srv_seq.dart';
import 'package:server_box/view/page/snippet/edit.dart'; import 'package:server_box/view/page/snippet/edit.dart';
import 'package:server_box/view/page/snippet/list.dart';
import 'package:server_box/view/page/storage/sftp.dart'; import 'package:server_box/view/page/storage/sftp.dart';
import 'package:server_box/view/page/storage/sftp_mission.dart'; import 'package:server_box/view/page/storage/sftp_mission.dart';
@@ -72,10 +66,6 @@ class AppRoutes {
); );
} }
static AppRoutes keyList({Key? key}) {
return AppRoutes(PrivateKeysListPage(key: key), 'key_detail');
}
static AppRoutes snippetEdit({Key? key, Snippet? snippet}) { static AppRoutes snippetEdit({Key? key, Snippet? snippet}) {
return AppRoutes( return AppRoutes(
SnippetEditPage(snippet: snippet), SnippetEditPage(snippet: snippet),
@@ -83,10 +73,6 @@ class AppRoutes {
); );
} }
static AppRoutes snippetList({Key? key}) {
return AppRoutes(SnippetListPage(key: key), 'snippet_detail');
}
static AppRoutes ssh({ static AppRoutes ssh({
Key? key, Key? key,
required Spi spi, required Spi spi,
@@ -108,17 +94,6 @@ class AppRoutes {
return AppRoutes(SSHVirtKeySettingPage(key: key), 'ssh_virt_key_setting'); return AppRoutes(SSHVirtKeySettingPage(key: key), 'ssh_virt_key_setting');
} }
static AppRoutes localStorage(
{Key? key, bool isPickFile = false, String? initDir}) {
return AppRoutes(
LocalStoragePage(
key: key,
isPickFile: isPickFile,
initDir: initDir,
),
'local_storage');
}
static AppRoutes sftpMission({Key? key}) { static AppRoutes sftpMission({Key? key}) {
return AppRoutes(SftpMissionPage(key: key), 'sftp_mission'); return AppRoutes(SftpMissionPage(key: key), 'sftp_mission');
} }
@@ -135,10 +110,6 @@ class AppRoutes {
'sftp'); 'sftp');
} }
static AppRoutes backup({Key? key}) {
return AppRoutes(BackupPage(key: key), 'backup');
}
static AppRoutes docker({Key? key, required Spi spi}) { static AppRoutes docker({Key? key, required Spi spi}) {
return AppRoutes(ContainerPage(key: key, spi: spi), 'docker'); return AppRoutes(ContainerPage(key: key, spi: spi), 'docker');
} }
@@ -179,10 +150,6 @@ class AppRoutes {
return AppRoutes(ProcessPage(key: key, spi: spi), 'process'); return AppRoutes(ProcessPage(key: key, spi: spi), 'process');
} }
static AppRoutes settings({Key? key}) {
return AppRoutes(SettingPage(key: key), 'setting');
}
static AppRoutes serverOrder({Key? key}) { static AppRoutes serverOrder({Key? key}) {
return AppRoutes(ServerOrderPage(key: key), 'server_order'); return AppRoutes(ServerOrderPage(key: key), 'server_order');
} }

View File

@@ -1,26 +1,62 @@
import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/view/page/ping.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/view/page/server/tab.dart'; import 'package:server_box/view/page/server/tab.dart';
import 'package:server_box/view/page/setting/entry.dart';
import 'package:server_box/view/page/snippet/list.dart'; import 'package:server_box/view/page/snippet/list.dart';
import 'package:server_box/view/page/ssh/tab.dart'; import 'package:server_box/view/page/ssh/tab.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/view/page/storage/local.dart';
enum AppTab { enum AppTab {
server, server,
ssh, ssh,
file,
snippet, snippet,
ping, settings,
; ;
Widget get page { Widget get page {
switch (this) { return switch (this) {
case server: server => const ServerPage(),
return const ServerPage(); settings => const SettingsPage(),
case snippet: ssh => const SSHTabPage(),
return const SnippetListPage(); file => const LocalFilePage(),
case ssh: snippet => const SnippetListPage(),
return const SSHTabPage(); };
case ping: }
return const PingPage();
} NavigationDestination get navDestination {
return switch (this) {
server => NavigationDestination(
icon: const Icon(BoxIcons.bx_server),
label: l10n.server,
selectedIcon: const Icon(BoxIcons.bxs_server),
),
settings => NavigationDestination(
icon: const Icon(Icons.settings),
label: libL10n.setting,
selectedIcon: const Icon(Icons.settings),
),
ssh => const NavigationDestination(
icon: Icon(Icons.terminal_outlined),
label: 'SSH',
selectedIcon: Icon(Icons.terminal),
),
snippet => NavigationDestination(
icon: const Icon(Icons.code),
label: l10n.snippet,
selectedIcon: const Icon(Icons.code),
),
file => NavigationDestination(
icon: const Icon(Icons.folder_open),
label: libL10n.file,
selectedIcon: const Icon(Icons.folder),
),
};
}
static List<NavigationDestination> get navDestinations {
return AppTab.values.map((e) => e.navDestination).toList();
} }
} }

View File

@@ -119,6 +119,9 @@ extension Spix on Spi {
return (ip_, usr, port_); return (ip_, usr, port_);
} }
/// Just for showing the struct of the class.
///
/// **NOT** the default value.
static const example = Spi( static const example = Spi(
name: 'name', name: 'name',
ip: 'ip', ip: 'ip',

View File

@@ -88,16 +88,15 @@ class ServerProvider extends Provider {
} }
static void _updateTags() { static void _updateTags() {
final tags = <String>{};
for (final s in servers.values) { for (final s in servers.values) {
final tags = s.value.spi.tags; final spiTags = s.value.spi.tags;
if (tags == null) continue; if (spiTags == null) continue;
for (final t in tags) { for (final t in spiTags) {
if (!_tags.value.contains(t)) { tags.add(t);
_tags.value.add(t);
}
} }
} }
_tags.value = (_tags.value.toList()..sort()).toSet(); _tags.value = tags;
} }
static Server genServer(Spi spi) { static Server genServer(Spi spi) {

View File

@@ -15,18 +15,23 @@ import 'package:server_box/data/res/store.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/data/store/no_backup.dart'; import 'package:server_box/data/store/no_backup.dart';
final icloudLoading = false.vn; class BackupPage extends StatefulWidget {
final webdavLoading = false.vn;
final _noBak = NoBackupStore.instance;
class BackupPage extends StatelessWidget {
const BackupPage({super.key}); const BackupPage({super.key});
@override
State<BackupPage> createState() => _BackupPageState();
}
final class _BackupPageState extends State<BackupPage>
with AutomaticKeepAliveClientMixin {
final _noBak = NoBackupStore.instance;
final icloudLoading = false.vn;
final webdavLoading = false.vn;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return Scaffold( return Scaffold(
appBar: CustomAppBar(title: Text(libL10n.backup)),
body: _buildBody(context), body: _buildBody(context),
); );
} }
@@ -477,4 +482,7 @@ class BackupPage extends StatelessWidget {
Loggers.app.warning('Import servers failed', e, s); Loggers.app.warning('Import servers failed', e, s);
} }
} }
@override
bool get wantKeepAlive => true;
} }

View File

@@ -1,40 +1,20 @@
part of 'home.dart'; part of 'home.dart';
final class _AppBar extends CustomAppBar { final class _AppBar extends StatelessWidget implements PreferredSizeWidget {
final ValueNotifier<int> selectIndex; final double paddingTop;
final ValueNotifier<bool> landscape;
const _AppBar({ const _AppBar(this.paddingTop);
required this.selectIndex,
required this.landscape,
super.title,
super.actions,
super.centerTitle,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final placeholder = SizedBox( return SizedBox(
height: CustomAppBar.sysStatusBarHeight ?? height: paddingTop,
0 + MediaQuery.of(context).padding.top, child: isIOS
); ? const Center(child: Text(BuildData.name, style: UIs.text15Bold))
return selectIndex.listenVal( : null,
(idx) {
if (isDesktop) return super.build(context);
if (idx == AppTab.ssh.index) {
return placeholder;
}
return ValBuilder(
listenable: landscape,
builder: (ls) {
if (ls) return placeholder;
return super.build(context);
},
);
},
); );
} }
@override
Size get preferredSize => Size.fromHeight(paddingTop);
} }

View File

@@ -1,17 +1,10 @@
import 'dart:convert';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/core/channel/home_widget.dart'; import 'package:server_box/core/channel/home_widget.dart';
import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/route.dart';
import 'package:server_box/data/model/app/tab.dart'; import 'package:server_box/data/model/app/tab.dart';
import 'package:server_box/data/provider/app.dart'; import 'package:server_box/data/provider/app.dart';
import 'package:server_box/data/provider/server.dart'; import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/github_id.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/res/url.dart'; import 'package:server_box/data/res/url.dart';
import 'package:wakelock_plus/wakelock_plus.dart'; import 'package:wakelock_plus/wakelock_plus.dart';
@@ -104,39 +97,10 @@ class _HomePageState extends State<HomePage>
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context); super.build(context);
AppProvider.ctx = context; AppProvider.ctx = context;
final sysPadding = MediaQuery.of(context).padding;
final appBar = _AppBar(
selectIndex: _selectIndex,
landscape: _isLandscape,
centerTitle: false,
title: const Text(BuildData.name),
actions: <Widget>[
ValBuilder(
listenable: Stores.setting.serverStatusUpdateInterval.listenable(),
builder: (interval) {
if (interval != 0) return UIs.placeholder;
return IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Refresh',
onPressed: () async {
await ServerProvider.refresh();
},
);
},
),
IconButton(
icon: const Icon(Icons.developer_mode, size: 21),
tooltip: 'Debug',
onPressed: () => DebugPage.route.go(
context,
args: const DebugPageArgs(title: 'Debug(${BuildData.build})'),
),
),
],
);
return Scaffold( return Scaffold(
drawer: _buildDrawer(), appBar: _AppBar(sysPadding.top),
appBar: appBar,
body: PageView.builder( body: PageView.builder(
controller: _pageController, controller: _pageController,
itemCount: AppTab.values.length, itemCount: AppTab.values.length,
@@ -184,133 +148,7 @@ class _HomePageState extends State<HomePage>
labelBehavior: ls labelBehavior: ls
? NavigationDestinationLabelBehavior.alwaysHide ? NavigationDestinationLabelBehavior.alwaysHide
: NavigationDestinationLabelBehavior.onlyShowSelected, : NavigationDestinationLabelBehavior.onlyShowSelected,
destinations: [ destinations: AppTab.navDestinations,
NavigationDestination(
icon: const Icon(BoxIcons.bx_server),
label: l10n.server,
selectedIcon: const Icon(BoxIcons.bxs_server),
),
const NavigationDestination(
icon: Icon(Icons.terminal_outlined),
label: 'SSH',
selectedIcon: Icon(Icons.terminal),
),
NavigationDestination(
icon: const Icon(MingCute.file_code_line),
label: l10n.snippet,
selectedIcon: const Icon(MingCute.file_code_fill),
),
const NavigationDestination(
icon: Icon(MingCute.planet_line),
label: 'Ping',
selectedIcon: Icon(MingCute.planet_fill),
),
],
);
}
Widget _buildDrawer() {
return Drawer(
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildIcon(),
const Text(
'${BuildData.name}\nv${BuildData.build}',
textAlign: TextAlign.center,
style: UIs.text15,
),
const SizedBox(height: 37),
_buildTiles(),
],
),
);
}
Widget _buildTiles() {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 17),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.settings),
title: Text(libL10n.setting),
onTap: () => AppRoutes.settings().go(context),
onLongPress: _onLongPressSetting,
),
ListTile(
leading: const Icon(Icons.vpn_key),
title: Text(l10n.privateKey),
onTap: () => AppRoutes.keyList().go(context),
),
ListTile(
leading: const Icon(BoxIcons.bxs_file_blank),
title: Text(libL10n.file),
onTap: () => AppRoutes.localStorage().go(context),
),
ListTile(
leading: const Icon(MingCute.file_import_fill),
title: Text(libL10n.backup),
onTap: () => AppRoutes.backup().go(context),
),
ListTile(
leading: const Icon(OctIcons.feed_discussion),
title: Text('${libL10n.about} & ${libL10n.feedback}'),
onTap: _showAboutDialog,
)
].map((e) => CardX(child: e)).toList(),
),
);
}
void _showAboutDialog() {
context.showRoundDialog(
title: libL10n.about,
child: _buildAboutContent(),
actions: [
TextButton(
onPressed: () => Urls.appWiki.launch(),
child: const Text('Wiki'),
),
TextButton(
onPressed: () => Urls.appHelp.launch(),
child: Text(libL10n.feedback),
),
TextButton(
onPressed: () => showLicensePage(context: context),
child: Text(l10n.license),
),
],
);
}
Widget _buildAboutContent() {
return SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width * 0.8,
child: SimpleMarkdown(
data: '''
${l10n.madeWithLove('[lollipopkit](${Urls.myGithub})')}
#### Contributors
${GithubIds.contributors.map((e) => '[$e](${e.url})').join(' ')}
#### Participants
${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
#### My other apps
- [GPT Box](https://github.com/lollipopkit/flutter_gpt_box)
''',
),
),
);
}
Widget _buildIcon() {
return ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 57, maxWidth: 57),
child: UIs.appIcon,
); );
} }
@@ -366,35 +204,4 @@ ${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
); );
} }
} }
Future<void> _onLongPressSetting() async {
final map = Stores.setting.box.toJson(includeInternal: false);
final keys = map.keys;
/// Encode [map] to String with indent `\t`
final text = Miscs.jsonEncoder.convert(map);
final result = await AppRoutes.editor(
text: text,
langCode: 'json',
title: libL10n.setting,
).go<String>(context);
if (result == null) {
return;
}
try {
final newSettings = json.decode(result) as Map<String, dynamic>;
Stores.setting.box.putAll(newSettings);
final newKeys = newSettings.keys;
final removedKeys = keys.where((e) => !newKeys.contains(e));
for (final key in removedKeys) {
Stores.setting.box.delete(key);
}
} catch (e, trace) {
context.showRoundDialog(
title: libL10n.error,
child: Text('${l10n.save}:\n$e'),
);
Loggers.app.warning('Update json settings failed', e, trace);
}
}
} }

View File

@@ -22,9 +22,6 @@ class _PrivateKeyListState extends State<PrivateKeysListPage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: CustomAppBar(
title: Text(l10n.privateKey),
),
body: _buildBody(), body: _buildBody(),
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add), child: const Icon(Icons.add),

View File

@@ -118,10 +118,10 @@ class _ServerDetailPageState extends State<ServerDetailPage>
return CustomAppBar( return CustomAppBar(
title: Text(si.spi.name), title: Text(si.spi.name),
actions: [ actions: [
ShareBtn( QrShareBtn(
data: si.spi.toJsonString(), data: si.spi.toJsonString(),
tip: si.spi.name, tip: si.spi.name,
tip2: '${libL10n.share} ${l10n.server} ~ ServerBox', tip2: '${l10n.server} ~ ServerBox',
), ),
IconButton( IconButton(
icon: const Icon(Icons.edit), icon: const Icon(Icons.edit),

View File

@@ -610,13 +610,12 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
envs: _env.value.isEmpty ? null : _env.value, envs: _env.value.isEmpty ? null : _env.value,
); );
final existsIds = ServerStore.instance.box.keys;
if (existsIds.contains(spi.id)) {
context.showSnackBar('${l10n.sameIdServerExist}: ${spi.id}');
return;
}
if (this.spi == null) { if (this.spi == null) {
final existsIds = ServerStore.instance.box.keys;
if (existsIds.contains(spi.id)) {
context.showSnackBar('${l10n.sameIdServerExist}: ${spi.id}');
return;
}
ServerProvider.addServer(spi); ServerProvider.addServer(spi);
} else { } else {
ServerProvider.updateServer(this.spi!, spi); ServerProvider.updateServer(this.spi!, spi);

View File

@@ -8,8 +8,10 @@ import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/extension/ssh_client.dart'; import 'package:server_box/core/extension/ssh_client.dart';
import 'package:server_box/data/model/app/shell_func.dart'; import 'package:server_box/data/model/app/shell_func.dart';
import 'package:server_box/data/model/server/try_limiter.dart'; import 'package:server_box/data/model/server/try_limiter.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/server/edit.dart'; import 'package:server_box/view/page/server/edit.dart';
import 'package:server_box/view/page/setting/entry.dart';
import 'package:server_box/view/widget/percent_circle.dart'; import 'package:server_box/view/widget/percent_circle.dart';
import 'package:server_box/core/route.dart'; import 'package:server_box/core/route.dart';
@@ -19,6 +21,8 @@ import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/provider/server.dart'; import 'package:server_box/data/provider/server.dart';
import 'package:server_box/view/widget/server_func_btns.dart'; import 'package:server_box/view/widget/server_func_btns.dart';
part 'top_bar.dart';
class ServerPage extends StatefulWidget { class ServerPage extends StatefulWidget {
const ServerPage({super.key}); const ServerPage({super.key});
@@ -85,7 +89,7 @@ class _ServerPageState extends State<ServerPage>
Widget _buildPortrait() { Widget _buildPortrait() {
return Scaffold( return Scaffold(
appBar: TagSwitcher( appBar: _TopBar(
tags: ServerProvider.tags, tags: ServerProvider.tags,
onTagChanged: (p0) => _tag.value = p0, onTagChanged: (p0) => _tag.value = p0,
initTag: _tag.value, initTag: _tag.value,
@@ -130,7 +134,7 @@ class _ServerPageState extends State<ServerPage>
top: 0, top: 0,
left: 0, left: 0,
child: IconButton( child: IconButton(
onPressed: () => AppRoutes.settings().go(context), onPressed: () => SettingsPage.route.go(context),
icon: const Icon(Icons.settings, color: Colors.grey), icon: const Icon(Icons.settings, color: Colors.grey),
), ),
), ),
@@ -482,30 +486,18 @@ class _ServerPageState extends State<ServerPage>
null, null,
), ),
ServerConn.failed => ( ServerConn.failed => (
const Icon( const Icon(Icons.refresh, size: 21, color: Colors.grey),
Icons.refresh,
size: 21,
color: Colors.grey,
),
() { () {
TryLimiter.reset(s.spi.id); TryLimiter.reset(s.spi.id);
ServerProvider.refresh(spi: s.spi); ServerProvider.refresh(spi: s.spi);
}, },
), ),
ServerConn.disconnected => ( ServerConn.disconnected => (
const Icon( const Icon(MingCute.link_3_line, size: 19, color: Colors.grey),
MingCute.link_3_line,
size: 19,
color: Colors.grey,
),
() => ServerProvider.refresh(spi: s.spi) () => ServerProvider.refresh(spi: s.spi)
), ),
ServerConn.finished => ( ServerConn.finished => (
const Icon( const Icon(MingCute.unlink_2_line, size: 17, color: Colors.grey),
MingCute.unlink_2_line,
size: 17,
color: Colors.grey,
),
() => ServerProvider.closeServer(id: s.spi.id), () => ServerProvider.closeServer(id: s.spi.id),
), ),
_ when Stores.setting.serverTabUseOldUI.fetch() => ( _ when Stores.setting.serverTabUseOldUI.fetch() => (
@@ -517,11 +509,7 @@ class _ServerPageState extends State<ServerPage>
// Or the loading icon will be rescaled. // Or the loading icon will be rescaled.
final wrapped = child is SizedBox final wrapped = child is SizedBox
? child ? child
: SizedBox( : SizedBox(height: _kCardHeightMin, width: 27, child: child);
height: _kCardHeightMin,
width: 27,
child: child,
);
if (onTap == null) return wrapped.paddingOnly(left: 10); if (onTap == null) return wrapped.paddingOnly(left: 10);
return InkWell( return InkWell(
borderRadius: BorderRadius.circular(7), borderRadius: BorderRadius.circular(7),
@@ -654,7 +642,7 @@ ${ss.err?.message ?? 'null'}
List<String> _filterServers(List<String> order) { List<String> _filterServers(List<String> order) {
final tag = _tag.value; final tag = _tag.value;
if (tag == kDefaultTag) return order; if (tag == TagSwitcher.kDefaultTag) return order;
return order.where((e) { return order.where((e) {
final tags = ServerProvider.pick(id: e)?.value.spi.tags; final tags = ServerProvider.pick(id: e)?.value.spi.tags;
if (tags == null) return false; if (tags == null) return false;

View File

@@ -0,0 +1,43 @@
part of 'tab.dart';
final class _TopBar extends StatelessWidget implements PreferredSizeWidget {
final ValueNotifier<Set<String>> tags;
final void Function(String) onTagChanged;
final String initTag;
const _TopBar({
required this.initTag,
required this.onTagChanged,
required this.tags,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(left: 17),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Center(
child: Text(
BuildData.name,
style: TextStyle(fontSize: 20),
textAlign: TextAlign.center,
),
),
const SizedBox(width: 30),
TagSwitcher(
tags: tags,
onTagChanged: onTagChanged,
initTag: initTag,
singleLine: true,
reversed: true,
).expanded(),
],
),
);
}
@override
Size get preferredSize => const Size.fromHeight(TagSwitcher.kTagBtnHeight);
}

View File

@@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
@@ -6,6 +7,7 @@ import 'package:flutter_highlight/theme_map.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:icons_plus/icons_plus.dart'; import 'package:icons_plus/icons_plus.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/data/res/github_id.dart';
import 'package:server_box/data/res/rebuild.dart'; import 'package:server_box/data/res/rebuild.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/res/url.dart'; import 'package:server_box/data/res/url.dart';
@@ -13,70 +15,196 @@ import 'package:server_box/data/res/url.dart';
import 'package:server_box/core/route.dart'; import 'package:server_box/core/route.dart';
import 'package:server_box/data/model/app/net_view.dart'; import 'package:server_box/data/model/app/net_view.dart';
import 'package:server_box/data/res/build_data.dart'; import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/view/page/backup.dart';
import 'package:server_box/view/page/private_key/list.dart';
const _kIconSize = 23.0; const _kIconSize = 23.0;
class SettingPage extends StatefulWidget { enum SettingsTabs {
const SettingPage({super.key}); app,
privateKey,
backup,
about,
;
@override String get i18n => switch (this) {
State<SettingPage> createState() => _SettingPageState(); SettingsTabs.app => libL10n.app,
SettingsTabs.privateKey => l10n.privateKey,
SettingsTabs.backup => libL10n.backup,
SettingsTabs.about => libL10n.about,
};
Widget get page => switch (this) {
SettingsTabs.app => const AppSettingsPage(),
SettingsTabs.privateKey => const PrivateKeysListPage(),
SettingsTabs.backup => const BackupPage(),
SettingsTabs.about => const AppAboutPage(),
};
static final List<Widget> pages =
SettingsTabs.values.map((e) => e.page).toList();
} }
class _SettingPageState extends State<SettingPage> { class SettingsPage extends StatefulWidget {
final _setting = Stores.setting; const SettingsPage({super.key});
static const route = AppRouteNoArg(page: SettingsPage.new, path: '/settings');
@override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage>
with SingleTickerProviderStateMixin {
late final _tabCtrl =
TabController(length: SettingsTabs.values.length, vsync: this);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: TabBar(
title: Text(libL10n.setting), controller: _tabCtrl,
actions: [ dividerHeight: 0,
IconButton( tabAlignment: TabAlignment.center,
icon: const Icon(Icons.delete), isScrollable: true,
onPressed: () => context.showRoundDialog( tabs: SettingsTabs.values
title: libL10n.attention, .map((e) => Tab(text: e.i18n))
child: SimpleMarkdown( .toList(growable: false),
data: libL10n.askContinue(
'${libL10n.delete} **${libL10n.all}** ${libL10n.setting}',
),
),
actions: Btn.ok(
onTap: () {
context.pop();
_setting.box.deleteAll(_setting.box.keys);
context.showSnackBar(libL10n.success);
},
red: true,
).toList,
),
),
],
), ),
body: MultiList( // actions: [
widthDivider: 2.3, // IconButton(
thumbVisibility: true, // icon: const Icon(Icons.delete),
children: [ // onPressed: () => context.showRoundDialog(
[const CenterGreyTitle('App'), _buildApp()], // title: libL10n.attention,
[CenterGreyTitle(l10n.server), _buildServer()], // child: SimpleMarkdown(
[ // data: libL10n.askContinue(
const CenterGreyTitle('SSH'), // '${libL10n.delete} **${libL10n.all}** ${libL10n.setting}',
_buildSSH(), // ),
const CenterGreyTitle('SFTP'), // ),
_buildSFTP() // actions: [
], // CountDownBtn(
[ // onTap: () {
CenterGreyTitle(l10n.container), // context.pop();
_buildContainer(), // _setting.box.deleteAll(_setting.box.keys);
CenterGreyTitle(l10n.editor), // context.showSnackBar(libL10n.success);
_buildEditor(), // },
], // afterColor: Colors.red,
// )
// ],
// ),
// ),
// ],
body: TabBarView(controller: _tabCtrl, children: SettingsTabs.pages),
);
}
}
/// Fullscreen Mode is designed for old mobile phone which can be final class AppAboutPage extends StatefulWidget {
/// used as a status screen. const AppAboutPage({super.key});
if (isMobile) [CenterGreyTitle(l10n.fullScreen), _buildFullScreen()],
@override
State<AppAboutPage> createState() => _AppAboutPageState();
}
final class _AppAboutPageState extends State<AppAboutPage>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
return ListView(
padding: const EdgeInsets.all(13),
children: [
UIs.height13,
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 47, maxWidth: 47),
child: UIs.appIcon,
),
const Text(
'${BuildData.name}\nv${BuildData.build}',
textAlign: TextAlign.center,
style: UIs.text15,
),
UIs.height13,
SizedBox(
height: 47,
child: ListView(
scrollDirection: Axis.horizontal,
children: <Widget>[
Btn.elevated(
icon: const Icon(Icons.edit_document),
text: 'Wiki',
onTap: Urls.appWiki.launch,
),
Btn.elevated(
icon: const Icon(Icons.feedback),
text: libL10n.feedback,
onTap: Urls.appHelp.launch,
),
Btn.elevated(
icon: const Icon(MingCute.question_fill),
text: l10n.license,
onTap: () => showLicensePage(context: context),
),
].joinWith(UIs.width13),
),
),
UIs.height13,
SimpleMarkdown(
data: '''
#### Contributors
${GithubIds.contributors.map((e) => '[$e](${e.url})').join(' ')}
#### Participants
${GithubIds.participants.map((e) => '[$e](${e.url})').join(' ')}
#### My other apps
[GPT Box](https://github.com/lollipopkit/flutter_gpt_box)
${l10n.madeWithLove('[lollipopkit](${Urls.myGithub})')}
''',
).paddingAll(13).cardx,
],
);
}
@override
bool get wantKeepAlive => true;
}
final class AppSettingsPage extends StatefulWidget {
const AppSettingsPage({super.key});
@override
State<AppSettingsPage> createState() => _AppSettingsPageState();
}
final class _AppSettingsPageState extends State<AppSettingsPage> {
final _setting = Stores.setting;
@override
Widget build(BuildContext context) {
return MultiList(
thumbVisibility: true,
children: [
[const CenterGreyTitle('App'), _buildApp()],
[CenterGreyTitle(l10n.server), _buildServer()],
[
const CenterGreyTitle('SSH'),
_buildSSH(),
const CenterGreyTitle('SFTP'),
_buildSFTP()
], ],
), [
CenterGreyTitle(l10n.container),
_buildContainer(),
CenterGreyTitle(l10n.editor),
_buildEditor(),
],
/// Fullscreen Mode is designed for old mobile phone which can be
/// used as a status screen.
if (isMobile) [CenterGreyTitle(l10n.fullScreen), _buildFullScreen()],
],
); );
} }
@@ -94,9 +222,7 @@ class _SettingPageState extends State<SettingPage> {
_buildAppMore(), _buildAppMore(),
]; ];
return Column( return Column(children: children.map((e) => e.cardx).toList());
children: children.map((e) => CardX(child: e)).toList(),
);
} }
Widget _buildFullScreen() { Widget _buildFullScreen() {
@@ -1182,4 +1308,35 @@ class _SettingPageState extends State<SettingPage> {
}, },
); );
} }
Future<void> _onLongPressSetting() async {
final map = Stores.setting.box.toJson(includeInternal: false);
final keys = map.keys;
/// Encode [map] to String with indent `\t`
final text = jsonIndentEncoder.convert(map);
final result = await AppRoutes.editor(
text: text,
langCode: 'json',
title: libL10n.setting,
).go<String>(context);
if (result == null) {
return;
}
try {
final newSettings = json.decode(result) as Map<String, dynamic>;
Stores.setting.box.putAll(newSettings);
final newKeys = newSettings.keys;
final removedKeys = keys.where((e) => !newKeys.contains(e));
for (final key in removedKeys) {
Stores.setting.box.delete(key);
}
} catch (e, trace) {
context.showRoundDialog(
title: libL10n.error,
child: Text('${l10n.save}:\n$e'),
);
Loggers.app.warning('Update json settings failed', e, trace);
}
}
} }

View File

@@ -13,11 +13,13 @@ class SnippetListPage extends StatefulWidget {
State<SnippetListPage> createState() => _SnippetListPageState(); State<SnippetListPage> createState() => _SnippetListPageState();
} }
class _SnippetListPageState extends State<SnippetListPage> { class _SnippetListPageState extends State<SnippetListPage>
with AutomaticKeepAliveClientMixin {
final _tag = ''.vn; final _tag = ''.vn;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return Scaffold( return Scaffold(
appBar: TagSwitcher( appBar: TagSwitcher(
tags: SnippetProvider.tags, tags: SnippetProvider.tags,
@@ -43,7 +45,7 @@ class _SnippetListPageState extends State<SnippetListPage> {
} }
Widget _buildSnippetList(List<Snippet> snippets, String tag) { Widget _buildSnippetList(List<Snippet> snippets, String tag) {
final filtered = tag == kDefaultTag final filtered = tag == TagSwitcher.kDefaultTag
? snippets ? snippets
: snippets.where((e) => e.tags?.contains(tag) ?? false).toList(); : snippets.where((e) => e.tags?.contains(tag) ?? false).toList();
@@ -95,6 +97,9 @@ class _SnippetListPageState extends State<SnippetListPage> {
); );
} }
@override
bool get wantKeepAlive => true;
// Future<void> _runSnippet(Snippet snippet) async { // Future<void> _runSnippet(Snippet snippet) async {
// final servers = await context.showPickDialog<Server>( // final servers = await context.showPickDialog<Server>(
// items: Pros.server.servers.toList(), // items: Pros.server.servers.toList(),

View File

@@ -300,7 +300,9 @@ class SSHPageState extends State<SSHPage>
title: l10n.snippet, title: l10n.snippet,
tags: SnippetProvider.tags, tags: SnippetProvider.tags,
itemsBuilder: (e) { itemsBuilder: (e) {
if (e == kDefaultTag) return SnippetProvider.snippets.value; if (e == TagSwitcher.kDefaultTag) {
return SnippetProvider.snippets.value;
}
return SnippetProvider.snippets.value return SnippetProvider.snippets.value
.where((element) => element.tags?.contains(e) ?? false) .where((element) => element.tags?.contains(e) ?? false)
.toList(); .toList();

View File

@@ -8,187 +8,141 @@ import 'package:server_box/data/model/sftp/worker.dart';
import 'package:server_box/data/provider/server.dart'; import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/sftp.dart'; import 'package:server_box/data/provider/sftp.dart';
import 'package:server_box/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:server_box/view/widget/omit_start_text.dart';
import 'package:server_box/core/route.dart'; import 'package:server_box/core/route.dart';
import 'package:server_box/data/model/app/path_with_prefix.dart'; import 'package:server_box/data/model/app/path_with_prefix.dart';
class LocalStoragePage extends StatefulWidget { final class LocalFilePageArgs {
final bool isPickFile; final bool? isPickFile;
final String? initDir; final String? initDir;
const LocalStoragePage({ const LocalFilePageArgs({
super.key, this.isPickFile,
required this.isPickFile,
this.initDir, this.initDir,
}); });
@override
State<LocalStoragePage> createState() => _LocalStoragePageState();
} }
class _LocalStoragePageState extends State<LocalStoragePage> { class LocalFilePage extends StatefulWidget {
LocalPath? _path; final LocalFilePageArgs? args;
final _sortType = ValueNotifier(_SortType.name); const LocalFilePage({super.key, this.args});
static const route = AppRoute<String, LocalFilePageArgs>(
page: LocalFilePage.new,
path: '/local_file',
);
@override @override
void initState() { State<LocalFilePage> createState() => _LocalFilePageState();
super.initState(); }
if (widget.initDir != null) {
setState(() { class _LocalFilePageState extends State<LocalFilePage>
_path = LocalPath(widget.initDir!); with AutomaticKeepAliveClientMixin {
}); late final _path = LocalPath(widget.args?.initDir ?? Paths.file);
} else { final _sortType = _SortType.name.vn;
setState(() { bool get isPickFile => widget.args?.isPickFile ?? false;
_path = LocalPath(Paths.file);
});
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
final title = _path.path.fileName ?? libL10n.file;
return Scaffold( return Scaffold(
appBar: CustomAppBar( appBar: CustomAppBar(
leading: IconButton( title: AnimatedSwitcher(
icon: const BackButtonIcon(), duration: Durations.short3,
onPressed: () { child: Text(title, key: ValueKey(title)),
if (_path != null) {
_path!.update('/');
}
context.pop();
},
), ),
title: Text(libL10n.file),
actions: [ actions: [
IconButton( if (!isPickFile)
icon: const Icon(Icons.downloading), IconButton(
onPressed: () => AppRoutes.sftpMission().go(context), onPressed: () async {
), final path = await Pfs.pickFilePath();
ValBuilder<_SortType>( if (path == null) return;
listenable: _sortType, final name = path.getFileName() ?? 'imported';
builder: (value) { await File(path).copy(_path.path.joinPath(name));
return PopupMenuButton<_SortType>( setState(() {});
icon: const Icon(Icons.sort), },
itemBuilder: (context) { icon: const Icon(Icons.add),
return [ ),
PopupMenuItem( if (!isPickFile) _buildMissionBtn(),
value: _SortType.name, _buildSortBtn(),
child: Text(libL10n.name),
),
PopupMenuItem(
value: _SortType.size,
child: Text(l10n.size),
),
PopupMenuItem(
value: _SortType.time,
child: Text(l10n.time),
),
];
},
onSelected: (value) {
_sortType.value = value;
},
);
},
),
], ],
), ),
body: FadeIn( body: _sortType.listen(_buildBody),
key: UniqueKey(),
child: ValBuilder(
listenable: _sortType,
builder: (val) {
return _buildBody();
},
),
),
bottomNavigationBar: SafeArea(child: _buildPath()),
);
}
Widget _buildPath() {
return Container(
padding: const EdgeInsets.fromLTRB(11, 7, 11, 11),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
OmitStartText(_path?.path ?? '...'),
_buildBtns(),
],
),
);
}
Widget _buildBtns() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
IconButton(
onPressed: () {
_path?.update('..');
setState(() {});
},
icon: const Icon(Icons.arrow_back),
),
IconButton(
onPressed: () async {
final path = await Pfs.pickFilePath();
if (path == null) return;
final name = path.getFileName() ?? 'imported';
await File(path).copy(_path!.path.joinPath(name));
setState(() {});
},
icon: const Icon(Icons.add),
),
],
); );
} }
Widget _buildBody() { Widget _buildBody() {
if (_path == null) { Future<List<(FileSystemEntity, FileStat)>> getEntities() async {
return const Center( final files = await Directory(_path.path).list().toList();
child: CircularProgressIndicator(), final sorted = _sortType.value.sort(files);
final stats = await Future.wait(
sorted.map((e) async => (e, await e.stat())),
); );
return stats;
} }
final dir = Directory(_path!.path);
final tempFiles = dir.listSync();
final files = _sortType.value.sort(tempFiles);
return ListView.builder(
itemCount: files.length,
padding: const EdgeInsets.symmetric(vertical: 3, horizontal: 7),
itemBuilder: (context, index) {
final file = files[index];
final fileName = file.path.split('/').last;
final stat = file.statSync();
final isDir = stat.type == FileSystemEntityType.directory;
return CardX( return FutureWidget(
child: ListTile( future: getEntities(),
leading: isDir loading: UIs.placeholder,
? const Icon(Icons.folder_open) success: (items_) {
: const Icon(Icons.insert_drive_file), final items = items_ ?? [];
title: Text(fileName), final len = _path.canBack ? items.length + 1 : items.length;
subtitle: return ListView.builder(
isDir ? null : Text(stat.size.bytes2Str, style: UIs.textGrey), itemCount: len,
trailing: Text( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 13),
stat.modified itemBuilder: (context, index) {
.toString() if (index == 0 && _path.canBack) {
.substring(0, stat.modified.toString().length - 4), return CardX(
style: UIs.textGrey, child: ListTile(
), leading: const Icon(Icons.arrow_back),
onLongPress: () { title: const Text('..'),
if (!isDir) return; onTap: () {
_showDirActionDialog(file); _path.update('..');
}, setState(() {});
onTap: () async { },
if (!isDir) { ),
await _showFileActionDialog(file); );
return; }
}
_path!.update(fileName); if (_path.canBack) index--;
setState(() {});
}, final item = items[index];
), final file = item.$1;
final fileName = file.path.split('/').last;
final stat = item.$2;
final isDir = stat.type == FileSystemEntityType.directory;
return CardX(
child: ListTile(
leading: isDir
? const Icon(Icons.folder_open)
: const Icon(Icons.insert_drive_file),
title: Text(fileName),
subtitle: isDir
? null
: Text(stat.size.bytes2Str, style: UIs.textGrey),
trailing: Text(
stat.modified.ymdhms(),
style: UIs.textGrey,
),
onLongPress: () {
if (isDir) {
_showDirActionDialog(file);
return;
}
_showFileActionDialog(file);
},
onTap: () {
if (!isDir) {
_showFileActionDialog(file);
return;
}
_path.update(fileName);
setState(() {});
},
),
);
},
); );
}, },
); );
@@ -222,7 +176,7 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
Future<void> _showFileActionDialog(FileSystemEntity file) async { Future<void> _showFileActionDialog(FileSystemEntity file) async {
final fileName = file.path.split('/').last; final fileName = file.path.split('/').last;
if (widget.isPickFile) { if (isPickFile) {
await context.showRoundDialog( await context.showRoundDialog(
title: libL10n.file, title: libL10n.file,
child: Text(fileName), child: Text(fileName),
@@ -324,25 +278,33 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
void _showRenameDialog(FileSystemEntity file) { void _showRenameDialog(FileSystemEntity file) {
final fileName = file.path.split('/').last; final fileName = file.path.split('/').last;
final ctrl = TextEditingController(text: fileName);
void onSubmit() async {
final newName = ctrl.text;
if (newName.isEmpty) {
context.showSnackBar(libL10n.empty);
return;
}
context.pop();
final newPath = '${file.parent.path}/$newName';
await context.showLoadingDialog(fn: () => file.rename(newPath));
setState(() {});
}
context.showRoundDialog( context.showRoundDialog(
title: libL10n.rename, title: libL10n.rename,
child: Input( child: Input(
autoFocus: true, autoFocus: true,
icon: Icons.abc,
label: libL10n.name,
controller: TextEditingController(text: fileName), controller: TextEditingController(text: fileName),
suggestion: true, suggestion: true,
onSubmitted: (p0) { maxLines: 3,
context.pop(); onSubmitted: (p0) => onSubmit(),
final newPath = '${file.parent.path}/$p0';
try {
file.renameSync(newPath);
} catch (e) {
context.showSnackBar('${libL10n.fail}:\n$e');
return;
}
setState(() {});
},
), ),
actions: Btn.ok(onTap: onSubmit).toList,
); );
} }
@@ -365,6 +327,30 @@ class _LocalStoragePageState extends State<LocalStoragePage> {
).toList, ).toList,
); );
} }
Widget _buildMissionBtn() {
return IconButton(
icon: const Icon(Icons.downloading),
onPressed: () => AppRoutes.sftpMission().go(context),
);
}
Widget _buildSortBtn() {
return _sortType.listenVal(
(value) {
return PopupMenuButton<_SortType>(
icon: const Icon(Icons.sort),
itemBuilder: (_) => _SortType.values.map((e) => e.menuItem).toList(),
onSelected: (value) {
_sortType.value = value;
},
);
},
);
}
@override
bool get wantKeepAlive => true;
} }
enum _SortType { enum _SortType {
@@ -388,4 +374,29 @@ enum _SortType {
} }
return files; return files;
} }
String get i18n => switch (this) {
name => libL10n.name,
size => l10n.size,
time => l10n.time,
};
IconData get icon => switch (this) {
name => Icons.sort_by_alpha,
size => Icons.sort,
time => Icons.access_time,
};
PopupMenuItem<_SortType> get menuItem {
return PopupMenuItem(
value: this,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Icon(icon),
Text(i18n),
],
),
);
}
} }

View File

@@ -14,6 +14,7 @@ import 'package:server_box/data/model/sftp/worker.dart';
import 'package:server_box/data/provider/sftp.dart'; import 'package:server_box/data/provider/sftp.dart';
import 'package:server_box/data/res/misc.dart'; import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/res/store.dart'; import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/storage/local.dart';
import 'package:server_box/view/widget/omit_start_text.dart'; import 'package:server_box/view/widget/omit_start_text.dart';
import 'package:server_box/view/widget/two_line_text.dart'; import 'package:server_box/view/widget/two_line_text.dart';
import 'package:server_box/view/widget/unix_perm.dart'; import 'package:server_box/view/widget/unix_perm.dart';
@@ -691,8 +692,10 @@ class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
], ],
)); ));
final path = switch (idx) { final path = switch (idx) {
0 => 0 => await LocalFilePage.route.go(
await AppRoutes.localStorage(isPickFile: true).go<String>(context), context,
args: const LocalFilePageArgs(isPickFile: true),
),
1 => await Pfs.pickFilePath(), 1 => await Pfs.pickFilePath(),
_ => null, _ => null,
}; };

View File

@@ -1,9 +1,9 @@
import 'package:fl_lib/fl_lib.dart'; import 'package:fl_lib/fl_lib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:server_box/core/extension/context/locale.dart'; import 'package:server_box/core/extension/context/locale.dart';
import 'package:server_box/core/route.dart';
import 'package:server_box/data/model/sftp/worker.dart'; import 'package:server_box/data/model/sftp/worker.dart';
import 'package:server_box/data/provider/sftp.dart'; import 'package:server_box/data/provider/sftp.dart';
import 'package:server_box/view/page/storage/local.dart';
class SftpMissionPage extends StatefulWidget { class SftpMissionPage extends StatefulWidget {
const SftpMissionPage({super.key}); const SftpMissionPage({super.key});
@@ -115,7 +115,10 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
onPressed: () { onPressed: () {
final idx = status.req.localPath.lastIndexOf('/'); final idx = status.req.localPath.lastIndexOf('/');
final dir = status.req.localPath.substring(0, idx); final dir = status.req.localPath.substring(0, idx);
AppRoutes.localStorage(initDir: dir).go(context); LocalFilePage.route.go(
context,
args: LocalFilePageArgs(initDir: dir),
);
}, },
icon: const Icon(Icons.file_open), icon: const Icon(Icons.file_open),
), ),

View File

@@ -113,7 +113,9 @@ void _onTapMoreBtns(
title: l10n.snippet, title: l10n.snippet,
tags: SnippetProvider.tags, tags: SnippetProvider.tags,
itemsBuilder: (e) { itemsBuilder: (e) {
if (e == kDefaultTag) return SnippetProvider.snippets.value; if (e == TagSwitcher.kDefaultTag) {
return SnippetProvider.snippets.value;
}
return SnippetProvider.snippets.value return SnippetProvider.snippets.value
.where((element) => element.tags?.contains(e) ?? false) .where((element) => element.tags?.contains(e) ?? false)
.toList(); .toList();

View File

@@ -470,8 +470,8 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: "v1.0.183" ref: "v1.0.187"
resolved-ref: "1333b6269a4caa54c58324c66558e09a0525784b" resolved-ref: "9c69e55fd227428935de8de938c265ab5c099451"
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"

View File

@@ -60,7 +60,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.183 ref: v1.0.187
dependency_overrides: dependency_overrides:
# dartssh2: # dartssh2: