diff --git a/lib/data/model/app/bak/backup_service.dart b/lib/data/model/app/bak/backup_service.dart index eff921dc..19453a69 100644 --- a/lib/data/model/app/bak/backup_service.dart +++ b/lib/data/model/app/bak/backup_service.dart @@ -121,12 +121,20 @@ class BackupService { await context.showRoundDialog( title: libL10n.restore, child: Text(libL10n.askContinue('${libL10n.restore} ${libL10n.backup}(${backup.$2})')), - actions: Btn.ok( - onTap: () async { - await backup.$1.merge(force: true); - context.pop(); - }, - ).toList, + actions: [ + Btn.cancel(), + Btn.ok( + onTap: () async { + try { + await backup.$1.merge(force: true); + context.pop(); + } catch (e, s) { + context.pop(); + context.showErrDialog(e, s, libL10n.restore); + } + }, + ), + ], ); } diff --git a/lib/data/res/github_id.dart b/lib/data/res/github_id.dart index 675654a9..f3e4fbc0 100644 --- a/lib/data/res/github_id.dart +++ b/lib/data/res/github_id.dart @@ -136,7 +136,9 @@ abstract final class GithubIds { 'Bjups', '4061N', 'itmagpro', - 'atikattar1104' + 'atikattar1104', + 'coldboy404', + 'puskyer' }; } diff --git a/lib/data/store/setting.dart b/lib/data/store/setting.dart index 0f127879..8bfadc84 100644 --- a/lib/data/store/setting.dart +++ b/lib/data/store/setting.dart @@ -61,6 +61,12 @@ class SettingStore extends HiveStore { defaultValue: ServerDetailCards.values.map((e) => e.name).toList(), ); + // Disabled detail cards (for persistence when toggling visibility) + late final detailCardDisabled = listProperty('detailCardDisabled'); + + // Disabled SSH virtual keys (for persistence when toggling visibility) + late final sshVirtKeysDisabled = listProperty('sshVirtKeysDisabled'); + // SSH term font size late final termFontSize = propertyDefault('termFontSize', 13.0); diff --git a/lib/view/page/server/detail/view.dart b/lib/view/page/server/detail/view.dart index 78665d57..d77b98c3 100644 --- a/lib/view/page/server/detail/view.dart +++ b/lib/view/page/server/detail/view.dart @@ -83,7 +83,8 @@ class _ServerDetailPageState extends ConsumerState with Single void initState() { super.initState(); final order = _settings.detailCardOrder.fetch(); - order.removeWhere((e) => !ServerDetailCards.names.contains(e)); + final disabled = _settings.detailCardDisabled.fetch(); + order.removeWhere((e) => !ServerDetailCards.names.contains(e) || disabled.contains(e)); _cardsOrder.addAll(order); } diff --git a/lib/view/page/server/edit/widget.dart b/lib/view/page/server/edit/widget.dart index 447e9ee0..807ca045 100644 --- a/lib/view/page/server/edit/widget.dart +++ b/lib/view/page/server/edit/widget.dart @@ -59,6 +59,7 @@ extension _Widgets on _ServerEditPageState { children: List.generate(pkis.length, (index) { final item = pkis[index]; return ChoiceChipX( + key: ValueKey(index), label: item.id, state: state, value: index, @@ -366,6 +367,7 @@ extension _Widgets on _ServerEditPageState { children: List.generate(srvs.length, (index) { final item = srvs[index]; return ChoiceChipX( + key: ValueKey(item), label: item.name, state: state, value: item, diff --git a/lib/view/page/setting/entries/app.dart b/lib/view/page/setting/entries/app.dart index 629d8c8c..41afedd8 100644 --- a/lib/view/page/setting/entries/app.dart +++ b/lib/view/page/setting/entries/app.dart @@ -2,6 +2,7 @@ part of '../entry.dart'; extension _App on _AppSettingsPageState { Widget _buildApp() { + final androidSettings = isAndroid ? _buildAndroidSettings() : null; final specific = _buildPlatformSetting(); final children = [ _buildLocale(), @@ -10,6 +11,7 @@ extension _App on _AppSettingsPageState { _buildCheckUpdate(), _buildHomeTabs(), PlatformPublicSettings.buildBioAuth, + if (androidSettings != null) androidSettings, if (specific != null) specific, _buildAppMore(), ]; @@ -17,18 +19,73 @@ extension _App on _AppSettingsPageState { return Column(children: children.map((e) => e.cardx).toList()); } - Widget? _buildPlatformSetting() { - final func = switch (Pfs.type) { - Pfs.android => AndroidSettingsPage.route.go, - Pfs.ios => IosSettingsPage.route.go, - _ => null, - }; - if (func == null) return null; - return ListTile( + Widget _buildAndroidSettings() { + return ExpandTile( leading: const Icon(Icons.phone_android), - title: Text('${Pfs.type} ${libL10n.setting}'), + title: Text('Android ${libL10n.setting}'), + children: [ + _buildBgRun(), + _buildAndroidWidgetSharedPreference(), + ], + ); + } + + Widget _buildBgRun() { + return ListTile( + title: TipText(l10n.bgRun, l10n.bgRunTip), + trailing: StoreSwitch(prop: Stores.setting.bgRun), + ); + } + + Widget _buildAndroidWidgetSharedPreference() { + return ListTile( + title: Text(l10n.homeWidgetUrlConfig), trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () => func(context), + onTap: () async { + const prefix = 'widget_'; + final data = {}; + final keys = PrefStore.shared.keys(); + + for (final key in keys) { + if (!key.startsWith(prefix)) continue; + final val = PrefStore.shared.get(key); + if (val != null) data[key] = val; + } + final result = await KvEditor.route.go( + context, + KvEditorArgs(data: data, prefix: prefix), + ); + if (result != null) { + await _saveWidgetSP(result, data, prefix); + } + }, + ); + } + + Future _saveWidgetSP(Map map, Map old, String prefix) async { + try { + final keysDel = old.keys.toSet().difference(map.keys.toSet()); + for (final key in keysDel) { + if (!key.startsWith(prefix)) continue; + await PrefStore.shared.remove(key); + } + for (final entry in map.entries) { + if (!entry.key.startsWith(prefix)) continue; + await PrefStore.shared.set(entry.key, entry.value); + } + if (mounted) context.showSnackBar(libL10n.success); + } catch (e) { + if (mounted) context.showSnackBar(e.toString()); + } + } + + Widget? _buildPlatformSetting() { + if (!isIOS) return null; + return ListTile( + leading: const Icon(MingCute.apple_fill), + title: Text('iOS ${libL10n.setting}'), + trailing: const Icon(Icons.keyboard_arrow_right), + onTap: () => IosSettingsPage.route.go(context), ); } @@ -135,7 +192,7 @@ extension _App on _AppSettingsPageState { }, ), actions: [ - Btn.cancel(onTap: () => context.pop(false)), + Btn.cancel(), Btn.ok(onTap: () => _onSaveColor(ctrl.text)), ], ); diff --git a/lib/view/page/setting/entry.dart b/lib/view/page/setting/entry.dart index 2802f2b1..2363a565 100644 --- a/lib/view/page/setting/entry.dart +++ b/lib/view/page/setting/entry.dart @@ -20,7 +20,6 @@ import 'package:server_box/view/page/backup.dart'; import 'package:server_box/view/page/private_key/list.dart'; import 'package:server_box/view/page/server/connection_stats.dart'; import 'package:server_box/view/page/setting/entries/home_tabs.dart'; -import 'package:server_box/view/page/setting/platform/android.dart'; import 'package:server_box/view/page/setting/platform/ios.dart'; import 'package:server_box/view/page/setting/platform/platform_pub.dart'; import 'package:server_box/view/page/setting/seq/srv_detail_seq.dart'; diff --git a/lib/view/page/setting/platform/android.dart b/lib/view/page/setting/platform/android.dart deleted file mode 100644 index 99a398e7..00000000 --- a/lib/view/page/setting/platform/android.dart +++ /dev/null @@ -1,128 +0,0 @@ -import 'package:fl_lib/fl_lib.dart'; -import 'package:flutter/material.dart'; -import 'package:server_box/core/extension/context/locale.dart'; -import 'package:server_box/data/res/store.dart'; - -class AndroidSettingsPage extends StatefulWidget { - const AndroidSettingsPage({super.key}); - - @override - State createState() => _AndroidSettingsPageState(); - - static const route = AppRouteNoArg( - page: AndroidSettingsPage.new, - path: '/settings/android', - ); -} - -const _homeWidgetPrefPrefix = 'widget_'; - -class _AndroidSettingsPageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: CustomAppBar(title: const Text('Android')), - body: ListView( - padding: const EdgeInsets.symmetric(horizontal: 17), - children: [ - // _buildFgService(), - _buildBgRun(), - _buildAndroidWidgetSharedPreference(), - ].map((e) => CardX(child: e)).toList(), - ), - ); - } - - // Widget _buildFgService() { - // return ListTile( - // title: TipText(l10n.fgService, l10n.fgServiceTip), - // trailing: StoreSwitch(prop: Stores.setting.fgService), - // ); - // } - - Widget _buildBgRun() { - return ListTile( - title: TipText(l10n.bgRun, l10n.bgRunTip), - trailing: StoreSwitch(prop: Stores.setting.bgRun), - ); - } - - void _saveWidgetSP(Map map, Map old) { - try { - final keysDel = old.keys.toSet().difference(map.keys.toSet()); - for (final key in keysDel) { - if (!key.startsWith(_homeWidgetPrefPrefix)) continue; - PrefStore.shared.remove(key); - } - for (final entry in map.entries) { - if (!entry.key.startsWith(_homeWidgetPrefPrefix)) continue; - PrefStore.shared.set(entry.key, entry.value); - } - context.showSnackBar(libL10n.success); - } catch (e) { - context.showSnackBar(e.toString()); - } - } - - Widget _buildAndroidWidgetSharedPreference() { - return ListTile( - title: Text(l10n.homeWidgetUrlConfig), - trailing: const Icon(Icons.keyboard_arrow_right), - onTap: () async { - final data = {}; - final keys = PrefStore.shared.keys(); - - for (final key in keys) { - final val = PrefStore.shared.get(key); - if (val != null) { - data[key] = val; - } - } - final result = await KvEditor.route.go( - context, - KvEditorArgs(data: data, prefix: _homeWidgetPrefPrefix), - ); - if (result != null) { - _saveWidgetSP(result, data); - } - }, - ); - } - - /// It's removed due to Issue #381 - // Widget _buildWatch() { - // return FutureWidget( - // future: wc.isReachable, - // error: (e, s) { - // Loggers.app.warning('WatchOS error', e, s); - // return ListTile( - // title: const Text('Watch app'), - // subtitle: Text(l10n.viewErr, style: UIs.textGrey), - // trailing: const Icon(Icons.keyboard_arrow_right), - // onTap: () { - // context.showRoundDialog( - // title: l10n.error, - // child: SingleChildScrollView( - // child: SimpleMarkdown(data: '${e.toString()}\n```$s```'), - // ), - // ); - // }, - // ); - // }, - // success: (val) { - // if (val == null) { - // return ListTile( - // title: const Text('Watch app'), - // subtitle: Text(l10n.watchNotPaired, style: UIs.textGrey), - // ); - // } - // return ListTile( - // title: const Text('Watch app'), - // subtitle: Text(l10n.sync, style: UIs.textGrey), - // trailing: const Icon(Icons.keyboard_arrow_right), - // onTap: () async {}, - // ); - // }, - // ); - // } -} diff --git a/lib/view/page/setting/platform/ios.dart b/lib/view/page/setting/platform/ios.dart index b53a8d4f..28c9e3d0 100644 --- a/lib/view/page/setting/platform/ios.dart +++ b/lib/view/page/setting/platform/ios.dart @@ -16,8 +16,14 @@ class IosSettingsPage extends StatefulWidget { class _IosSettingsPageState extends State { final _pushToken = ValueNotifier(null); - final wc = WatchConnectivity(); + late final _watchContextFuture = _loadWatchContext(); + late final _pushTokenFuture = getToken(); + + Future?> _loadWatchContext() async { + if (!await wc.isPaired) return null; + return await wc.applicationContext; + } @override void dispose() { @@ -58,7 +64,7 @@ class _IosSettingsPageState extends State { }, ), subtitle: FutureWidget( - future: getToken(), + future: _pushTokenFuture, loading: const Text('...'), error: (error, trace) => Text('${libL10n.error}: $error'), success: (text) { @@ -79,10 +85,7 @@ class _IosSettingsPageState extends State { Widget _buildWatchApp() { return FutureWidget( - future: () async { - if (!await wc.isPaired) return null; - return await wc.applicationContext; - }(), + future: _watchContextFuture, loading: UIs.centerLoading, error: (e, trace) { Loggers.app.warning('WatchOS error', e, trace); diff --git a/lib/view/page/setting/seq/srv_detail_seq.dart b/lib/view/page/setting/seq/srv_detail_seq.dart index a77071b2..e878bd2b 100644 --- a/lib/view/page/setting/seq/srv_detail_seq.dart +++ b/lib/view/page/setting/seq/srv_detail_seq.dart @@ -1,3 +1,4 @@ +import 'dart:ui'; import 'package:fl_lib/fl_lib.dart'; import 'package:flutter/material.dart'; import 'package:server_box/core/extension/context/locale.dart'; @@ -15,68 +16,124 @@ class ServerDetailOrderPage extends StatefulWidget { class _ServerDetailOrderPageState extends State { final prop = Stores.setting.detailCardOrder; + final disabledProp = Stores.setting.detailCardDisabled; + + late List _order; + late Set _enabled; + + @override + void initState() { + super.initState(); + _loadData(); + } + + void _loadData() { + final keys = prop.fetch(); + final disabled = disabledProp.fetch(); + _order = List.from(keys); + for (final d in disabled) { + if (!_order.contains(d)) { + _order.add(d); + } + } + _enabled = Set.from(keys.where((k) => !disabled.contains(k))); + } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(title: Text(l10n.serverDetailOrder)), - body: _buildBody(), + body: SafeArea(child: _buildBody()), + ); + } + + Widget _proxyDecorator(Widget child, int _, Animation animation) { + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + final double animValue = Curves.easeInOut.transform(animation.value); + final double elevation = lerpDouble(1, 6, animValue)!; + final double scale = lerpDouble(1, 1.02, animValue)!; + return Transform.scale( + scale: scale, + child: Card(elevation: elevation, child: child), + ); + }, + child: child, ); } Widget _buildBody() { - return ValBuilder( - listenable: prop.listenable(), - builder: (keys) { - final disabled = ServerDetailCards.names.where((e) => !keys.contains(e)).toList(); - final allKeys = [...keys, ...disabled]; - return ReorderableListView.builder( - padding: const EdgeInsets.all(7), - buildDefaultDragHandles: false, - itemBuilder: (_, idx) { - final key = allKeys[idx]; - return ReorderableDelayedDragStartListener( - key: ValueKey(idx), - index: idx, - child: CardX( - child: ListTile( - contentPadding: const EdgeInsets.only(left: 23, right: 11), - leading: Icon(ServerDetailCards.fromName(key)?.icon), - title: Text(key), - trailing: _buildCheckBox(keys, key, idx, idx < keys.length), - ), - ), - ); - }, - itemCount: allKeys.length, - onReorder: (o, n) { - if (o >= keys.length || n >= keys.length) { - context.showSnackBar(libL10n.disabled); - return; - } - keys.moveByItem(o, n, property: prop); - }, - ); - }, + return ReorderableListView.builder( + key: const PageStorageKey('srv_detail_seq'), + padding: const EdgeInsets.all(7), + buildDefaultDragHandles: false, + itemCount: _order.length, + proxyDecorator: _proxyDecorator, + itemBuilder: (_, idx) => _buildListItem(_order[idx], idx), + onReorder: _handleReorder, ); } - Widget _buildCheckBox(List keys, String key, int idx, bool value) { - return Checkbox( - value: value, - onChanged: (val) { - if (val == null) return; - if (val) { - if (idx >= keys.length) { - keys.add(key); - } else { - keys.insert(idx - 1, key); - } - } else { - keys.remove(key); - } - prop.put(keys); - }, + Widget _buildListItem(String key, int idx) { + final isEnabled = _enabled.contains(key); + return ReorderableDelayedDragStartListener( + key: ValueKey(key), + index: idx, + child: CardX( + child: ListTile( + contentPadding: const EdgeInsets.only(left: 23, right: 11), + leading: Icon(ServerDetailCards.fromName(key)?.icon), + title: Text(key, style: isEnabled ? null : TextStyle(color: Colors.grey)), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildCheckBox(key, isEnabled), + ReorderableDragStartListener(index: idx, child: const Icon(Icons.drag_handle)), + ], + ), + ), + ), ); } + + Widget _buildCheckBox(String key, bool isEnabled) { + return Checkbox( + value: isEnabled, + onChanged: (_) => _toggleEnabled(key), + ); + } + + void _handleReorder(int oldIndex, int newIndex) { + var targetIndex = newIndex; + if (targetIndex > oldIndex) { + targetIndex -= 1; + } + if (targetIndex == oldIndex) { + return; + } + + setState(() { + final item = _order.removeAt(oldIndex); + _order.insert(targetIndex, item); + }); + _saveChanges(); + } + + void _toggleEnabled(String key) { + setState(() { + if (_enabled.contains(key)) { + _enabled.remove(key); + } else { + _enabled.add(key); + } + }); + _saveChanges(); + } + + void _saveChanges() { + prop.put(_order); + final disabledList = _order.where((k) => !_enabled.contains(k)).toList(); + disabledProp.put(disabledList); + } } diff --git a/lib/view/page/setting/seq/srv_func_seq.dart b/lib/view/page/setting/seq/srv_func_seq.dart index b453abed..452b68e7 100644 --- a/lib/view/page/setting/seq/srv_func_seq.dart +++ b/lib/view/page/setting/seq/srv_func_seq.dart @@ -31,27 +31,10 @@ class _ServerDetailOrderPageState extends State { final disabled = ServerFuncBtn.values.map((e) => e.index).where((e) => !keys.contains(e)).toList(); final allKeys = [...keys, ...disabled]; return ReorderableListView.builder( + key: const PageStorageKey('srv_func_seq'), padding: const EdgeInsets.all(7), - itemBuilder: (_, idx) { - final key = allKeys[idx]; - final funcBtn = ServerFuncBtn.values[key]; - return CardX( - key: ValueKey(idx), - child: ListTile( - title: RichText( - text: TextSpan( - children: [ - WidgetSpan(child: Icon(funcBtn.icon)), - const WidgetSpan(child: UIs.width13), - TextSpan(text: funcBtn.toStr, style: UIs.textGrey), - ], - ), - ), - leading: _buildCheckBox(keys, key, idx, idx < keys.length), - ), - ); - }, itemCount: allKeys.length, + itemBuilder: (_, idx) => _buildListItem(allKeys[idx], idx, keys), onReorder: (o, n) { if (o >= keys.length || n >= keys.length) { context.showSnackBar(libL10n.disabled); @@ -64,6 +47,25 @@ class _ServerDetailOrderPageState extends State { ); } + Widget _buildListItem(int key, int idx, List keys) { + final funcBtn = ServerFuncBtn.values[key]; + return CardX( + key: ValueKey(key), + child: ListTile( + title: RichText( + text: TextSpan( + children: [ + WidgetSpan(child: Icon(funcBtn.icon)), + const WidgetSpan(child: UIs.width13), + TextSpan(text: funcBtn.toStr, style: UIs.textGrey), + ], + ), + ), + leading: _buildCheckBox(keys, key, idx, idx < keys.length), + ), + ); + } + Widget _buildCheckBox(List keys, int key, int idx, bool value) { return Checkbox( value: value, diff --git a/lib/view/page/setting/seq/srv_seq.dart b/lib/view/page/setting/seq/srv_seq.dart index 764fc80a..d10c0c65 100644 --- a/lib/view/page/setting/seq/srv_seq.dart +++ b/lib/view/page/setting/seq/srv_seq.dart @@ -38,7 +38,7 @@ class _ServerOrderPageState extends ConsumerState { return Scaffold( appBar: CustomAppBar(title: Text(l10n.serverOrder)), - body: _buildBody(), + body: SafeArea(child: _buildBody()), ); } diff --git a/lib/view/page/setting/seq/virt_key.dart b/lib/view/page/setting/seq/virt_key.dart index 966de777..aee0c64c 100644 --- a/lib/view/page/setting/seq/virt_key.dart +++ b/lib/view/page/setting/seq/virt_key.dart @@ -1,3 +1,4 @@ +import 'dart:ui'; import 'package:fl_lib/fl_lib.dart'; import 'package:flutter/material.dart'; import 'package:server_box/core/extension/context/locale.dart'; @@ -18,19 +19,43 @@ class SSHVirtKeySettingPage extends StatefulWidget { class _SSHVirtKeySettingPageState extends State { final prop = Stores.setting.sshVirtKeys; + final disabledProp = Stores.setting.sshVirtKeysDisabled; + + late List _order; + late Set _enabled; + + @override + void initState() { + super.initState(); + _loadData(); + } + + void _loadData() { + final keys = prop.fetch(); + final disabled = disabledProp.fetch(); + _order = List.from(keys); + for (final d in disabled) { + if (!_order.contains(d)) { + _order.add(d); + } + } + _enabled = Set.from(keys.where((k) => !disabled.contains(k))); + } @override Widget build(BuildContext context) { return Scaffold( appBar: CustomAppBar(title: Text(l10n.editVirtKeys)), - body: Column( - children: [ - Padding( - padding: const EdgeInsets.all(7), - child: _buildOneLineVirtKey().cardx, - ), - Expanded(child: _buildBody()), - ], + body: SafeArea( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(7), + child: _buildOneLineVirtKey().cardx, + ), + Expanded(child: _buildBody()), + ], + ), ), ); } @@ -42,46 +67,62 @@ class _SSHVirtKeySettingPageState extends State { ); } - Widget _buildBody() { - return ValBuilder( - listenable: prop.listenable(), - builder: (keys) { - final disabled = VirtKey.values - .map((e) => e.index) - .where((e) => !keys.contains(e)) - .toList(); - final allKeys = [...keys, ...disabled]; - return ReorderableListView.builder( - padding: const EdgeInsets.all(7), - itemBuilder: (_, idx) { - final key = allKeys[idx]; - final item = VirtKey.values[key]; - final help = item.help; - return CardX( - key: ValueKey(idx), - child: ListTile( - title: _buildTitle(item), - subtitle: help == null ? null : Text(help, style: UIs.textGrey), - leading: _buildCheckBox(keys, key, idx, idx < keys.length), - trailing: isDesktop ? null : const Icon(Icons.drag_handle), - ), - ); - }, - itemCount: allKeys.length, - onReorder: (o, n) { - if (o >= keys.length || n >= keys.length) { - context.showSnackBar(libL10n.disabled); - return; - } - keys.moveByItem(o, n, property: prop); - }, + Widget _proxyDecorator(Widget child, int _, Animation animation) { + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + final double animValue = Curves.easeInOut.transform(animation.value); + final double elevation = lerpDouble(1, 6, animValue)!; + final double scale = lerpDouble(1, 1.02, animValue)!; + return Transform.scale( + scale: scale, + child: Card(elevation: elevation, child: child), ); }, + child: child, ); } - Widget _buildTitle(VirtKey key) { - return key.icon == null + Widget _buildBody() { + return ReorderableListView.builder( + key: const PageStorageKey('virt_key'), + padding: const EdgeInsets.all(7), + buildDefaultDragHandles: false, + itemCount: _order.length, + proxyDecorator: _proxyDecorator, + itemBuilder: (_, idx) => _buildListItem(_order[idx], idx), + onReorder: _handleReorder, + ); + } + + Widget _buildListItem(int key, int idx) { + final item = VirtKey.values[key]; + final help = item.help; + final isEnabled = _enabled.contains(key); + return ReorderableDelayedDragStartListener( + key: ValueKey(key), + index: idx, + child: CardX( + child: ListTile( + title: _buildTitle(item, isEnabled), + subtitle: help == null ? null : Text(help, style: UIs.textGrey), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + _buildCheckBox(key, isEnabled), + if (!isDesktop) ...[ + const SizedBox(width: 7), + ReorderableDragStartListener(index: idx, child: const Icon(Icons.drag_handle)), + ], + ], + ), + ), + ), + ); + } + + Widget _buildTitle(VirtKey key, bool isEnabled) { + final text = key.icon == null ? Text(key.text) : Row( children: [ @@ -90,24 +131,51 @@ class _SSHVirtKeySettingPageState extends State { Icon(key.icon), ], ); - } - - Widget _buildCheckBox(List keys, int key, int idx, bool value) { - return Checkbox( - value: value, - onChanged: (val) { - if (val == null) return; - if (val) { - if (idx >= keys.length) { - keys.add(key); - } else { - keys.insert(idx - 1, key); - } - } else { - keys.remove(key); - } - prop.put(keys); - }, + return IgnorePointer( + child: Opacity( + opacity: isEnabled ? 1.0 : 0.5, + child: text, + ), ); } + + Widget _buildCheckBox(int key, bool isEnabled) { + return Checkbox( + value: isEnabled, + onChanged: (_) => _toggleEnabled(key), + ); + } + + void _handleReorder(int oldIndex, int newIndex) { + var targetIndex = newIndex; + if (targetIndex > oldIndex) { + targetIndex -= 1; + } + if (targetIndex == oldIndex) { + return; + } + + setState(() { + final item = _order.removeAt(oldIndex); + _order.insert(targetIndex, item); + }); + _saveChanges(); + } + + void _toggleEnabled(int key) { + setState(() { + if (_enabled.contains(key)) { + _enabled.remove(key); + } else { + _enabled.add(key); + } + }); + _saveChanges(); + } + + void _saveChanges() { + prop.put(_order); + final disabledList = _order.where((k) => !_enabled.contains(k)).toList(); + disabledProp.put(disabledList); + } } diff --git a/lib/view/page/storage/local.dart b/lib/view/page/storage/local.dart index 75726487..936e48da 100644 --- a/lib/view/page/storage/local.dart +++ b/lib/view/page/storage/local.dart @@ -35,6 +35,7 @@ class LocalFilePage extends ConsumerStatefulWidget { class _LocalFilePageState extends ConsumerState with AutomaticKeepAliveClientMixin { late final _path = LocalPath(widget.args?.initDir ?? Paths.file); final _sortType = _SortType.name.vn; + late Future> _entitiesFuture = _getEntities(); bool get isPickFile => widget.args?.isPickFile ?? false; @override @@ -43,6 +44,13 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee _sortType.dispose(); } + Future _refresh() async { + setStateSafe(() { + _entitiesFuture = _getEntities(); + }); + await _entitiesFuture; + } + @override Widget build(BuildContext context) { super.build(context); @@ -65,7 +73,7 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee await destinationDir.create(recursive: true); } await File(path).copy(_path.path.joinPath(name)); - setState(() {}); + _refresh(); }, icon: const Icon(Icons.add), ), @@ -73,7 +81,7 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee IconButton( icon: const Icon(Icons.refresh), tooltip: MaterialLocalizations.of(context).refreshIndicatorSemanticLabel, - onPressed: () => setState(() {}), + onPressed: _refresh, ), if (!isPickFile) _buildMissionBtn(), _buildSortBtn(), @@ -81,9 +89,7 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee ), body: isMobile ? RefreshIndicator( - onRefresh: () async { - setState(() {}); - }, + onRefresh: _refresh, child: _sortType.listen(_buildBody), ) : _sortType.listen(_buildBody), @@ -91,15 +97,8 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee } Widget _buildBody() { - Future> getEntities() async { - final files = await Directory(_path.path).list().toList(); - final sorted = _sortType.value.sort(files); - final stats = await Future.wait(sorted.map((e) async => (e, await e.stat()))); - return stats; - } - return FutureWidget( - future: getEntities(), + future: _entitiesFuture, loading: UIs.placeholder, success: (items) { items ??= []; @@ -114,7 +113,7 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee title: const Text('..'), onTap: () { _path.update('..'); - setState(() {}); + _refresh(); }, ).cardx; } @@ -171,7 +170,7 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee return; } _path.update(fileName); - setState(() {}); + _refresh(); }, ), ); @@ -184,6 +183,13 @@ class _LocalFilePageState extends ConsumerState with AutomaticKee ); } + Future> _getEntities() async { + final files = await Directory(_path.path).list().toList(); + final stats = await Future.wait(files.map((e) async => (e, await e.stat()))); + stats.sort(_sortType.value.compareTuple); + return stats; + } + Widget _buildSortBtn() { return _sortType.listenVal((value) { return PopupMenuButton<_SortType>( @@ -397,19 +403,12 @@ enum _SortType { size, time; - List sort(List files) { - switch (this) { - case _SortType.name: - files.sort((a, b) => a.path.compareTo(b.path)); - break; - case _SortType.size: - files.sort((a, b) => a.statSync().size.compareTo(b.statSync().size)); - break; - case _SortType.time: - files.sort((a, b) => a.statSync().modified.compareTo(b.statSync().modified)); - break; - } - return files; + int compareTuple((FileSystemEntity, FileStat) a, (FileSystemEntity, FileStat) b) { + return switch (this) { + _SortType.name => a.$1.path.compareTo(b.$1.path), + _SortType.size => a.$2.size.compareTo(b.$2.size), + _SortType.time => a.$2.modified.compareTo(b.$2.modified), + }; } String get i18n => switch (this) { diff --git a/pubspec.lock b/pubspec.lock index 441112e7..2384c458 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -497,8 +497,8 @@ packages: dependency: "direct main" description: path: "." - ref: "v1.0.362" - resolved-ref: "3c75cfe1f07ee664a912d330e2a38bda51bee8d9" + ref: "v1.0.363" + resolved-ref: "4b745be6f33b2e7f274d44f26175df440345cefb" url: "https://github.com/lollipopkit/fl_lib" source: git version: "0.0.1" diff --git a/pubspec.yaml b/pubspec.yaml index c468e8d7..78e61528 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -66,7 +66,7 @@ dependencies: fl_lib: git: url: https://github.com/lollipopkit/fl_lib - ref: v1.0.362 + ref: v1.0.363 dependency_overrides: # webdav_client_plus: