ref:Refactor Settings UI and Fix Performance Issues (#1026)
* refactor(Settings page): Simplify the click handling logic of the cancel button * fix(backup_service): Add a cancel button in the restore backup dialog * refactor(Settings Page): Refactor the ordered list component and optimize state management - Extract the logic for building list items into a separate method to improve maintainability - Add animation effects to enhance the dragging experience - Use PageStorageKey to maintain the scroll position - Optimize the state management logic of the checkbox - Add new contributors in github_id.dart * fix: Add SafeArea to the settings page to prevent content from being obscured Add SafeArea wrapping content in multiple settings pages to prevent content from being obscured by the navigation bar on certain devices, thereby enhancing user experience * refactor: Extract file list retrieval method and optimize asynchronous loading of iOS settings page Extract the `_getEntities` method from an inline function to a class member method to enhance code readability Preload watch context and push token in the iOS settings page to avoid repeatedly creating Futures * fix: Add a `key` attribute to the ChoiceChipX component to avoid rendering issues * refactor(Settings page): Refactor the platform-related settings logic and merge the Android settings into the main page Migrate the Android platform settings from a standalone page to the main settings page, and remove redundant Android settings page files Adjust the platform setting logic, retaining only the special setting entry for the iOS platform * build: Update fl_lib dependency to v1.0.363 * feat(Settings): Add persistent disable state for cards and virtual keys Add persistent storage functionality for server detail cards and SSH virtual key disable status Modify the logic of relevant pages to support the saving and restoration of disabled states * refactor(setting): Simplify save logic and optimize file sorting performance In the settings page, remove the unnecessary `enabledList` filtering and directly save the `_order` list Optimize the sorting logic on the local file page by first retrieving the file status before proceeding with sorting * fix: Optimize data filtering and backup service error handling on the settings page Fix the data filtering logic in the settings page to only process key-value pairs with specific prefixes Add error handling to the backup service, capture and display merge failure exceptions * fix(Settings page): Fixed the issue where disabled items were not included in the order settings and asynchronously saved preference settings Fix the issue where disabled items in the virtual keyboard and service details order settings are not included in the order list Change the preference setting saving method to an asynchronous operation, and add a mounted check to prevent updating the state after the component is unmounted * refactor: Optimize the reordering logic and remove redundant sorting methods Narrow the scope of state updates in the reordering logic to only encompass the parts where data is actually modified Remove the unused sorting methods in `_local.dart` to simplify the code * refactor(view): Optimize the refresh logic of the local file page Refactor the refresh method that directly calls setState into a unified _refresh method Use the `_entitiesFuture` to cache the list of files to obtain results and avoid redundant calculations * Update lib/view/page/storage/local.dart Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This commit is contained in:
@@ -121,12 +121,20 @@ class BackupService {
|
|||||||
await context.showRoundDialog(
|
await context.showRoundDialog(
|
||||||
title: libL10n.restore,
|
title: libL10n.restore,
|
||||||
child: Text(libL10n.askContinue('${libL10n.restore} ${libL10n.backup}(${backup.$2})')),
|
child: Text(libL10n.askContinue('${libL10n.restore} ${libL10n.backup}(${backup.$2})')),
|
||||||
actions: Btn.ok(
|
actions: [
|
||||||
onTap: () async {
|
Btn.cancel(),
|
||||||
await backup.$1.merge(force: true);
|
Btn.ok(
|
||||||
context.pop();
|
onTap: () async {
|
||||||
},
|
try {
|
||||||
).toList,
|
await backup.$1.merge(force: true);
|
||||||
|
context.pop();
|
||||||
|
} catch (e, s) {
|
||||||
|
context.pop();
|
||||||
|
context.showErrDialog(e, s, libL10n.restore);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,7 +136,9 @@ abstract final class GithubIds {
|
|||||||
'Bjups',
|
'Bjups',
|
||||||
'4061N',
|
'4061N',
|
||||||
'itmagpro',
|
'itmagpro',
|
||||||
'atikattar1104'
|
'atikattar1104',
|
||||||
|
'coldboy404',
|
||||||
|
'puskyer'
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,12 @@ class SettingStore extends HiveStore {
|
|||||||
defaultValue: ServerDetailCards.values.map((e) => e.name).toList(),
|
defaultValue: ServerDetailCards.values.map((e) => e.name).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Disabled detail cards (for persistence when toggling visibility)
|
||||||
|
late final detailCardDisabled = listProperty<String>('detailCardDisabled');
|
||||||
|
|
||||||
|
// Disabled SSH virtual keys (for persistence when toggling visibility)
|
||||||
|
late final sshVirtKeysDisabled = listProperty<int>('sshVirtKeysDisabled');
|
||||||
|
|
||||||
// SSH term font size
|
// SSH term font size
|
||||||
late final termFontSize = propertyDefault('termFontSize', 13.0);
|
late final termFontSize = propertyDefault('termFontSize', 13.0);
|
||||||
|
|
||||||
|
|||||||
@@ -83,7 +83,8 @@ class _ServerDetailPageState extends ConsumerState<ServerDetailPage> with Single
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final order = _settings.detailCardOrder.fetch();
|
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);
|
_cardsOrder.addAll(order);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ extension _Widgets on _ServerEditPageState {
|
|||||||
children: List<Widget>.generate(pkis.length, (index) {
|
children: List<Widget>.generate(pkis.length, (index) {
|
||||||
final item = pkis[index];
|
final item = pkis[index];
|
||||||
return ChoiceChipX<int>(
|
return ChoiceChipX<int>(
|
||||||
|
key: ValueKey(index),
|
||||||
label: item.id,
|
label: item.id,
|
||||||
state: state,
|
state: state,
|
||||||
value: index,
|
value: index,
|
||||||
@@ -366,6 +367,7 @@ extension _Widgets on _ServerEditPageState {
|
|||||||
children: List<Widget>.generate(srvs.length, (index) {
|
children: List<Widget>.generate(srvs.length, (index) {
|
||||||
final item = srvs[index];
|
final item = srvs[index];
|
||||||
return ChoiceChipX<Spi>(
|
return ChoiceChipX<Spi>(
|
||||||
|
key: ValueKey(item),
|
||||||
label: item.name,
|
label: item.name,
|
||||||
state: state,
|
state: state,
|
||||||
value: item,
|
value: item,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ part of '../entry.dart';
|
|||||||
|
|
||||||
extension _App on _AppSettingsPageState {
|
extension _App on _AppSettingsPageState {
|
||||||
Widget _buildApp() {
|
Widget _buildApp() {
|
||||||
|
final androidSettings = isAndroid ? _buildAndroidSettings() : null;
|
||||||
final specific = _buildPlatformSetting();
|
final specific = _buildPlatformSetting();
|
||||||
final children = [
|
final children = [
|
||||||
_buildLocale(),
|
_buildLocale(),
|
||||||
@@ -10,6 +11,7 @@ extension _App on _AppSettingsPageState {
|
|||||||
_buildCheckUpdate(),
|
_buildCheckUpdate(),
|
||||||
_buildHomeTabs(),
|
_buildHomeTabs(),
|
||||||
PlatformPublicSettings.buildBioAuth,
|
PlatformPublicSettings.buildBioAuth,
|
||||||
|
if (androidSettings != null) androidSettings,
|
||||||
if (specific != null) specific,
|
if (specific != null) specific,
|
||||||
_buildAppMore(),
|
_buildAppMore(),
|
||||||
];
|
];
|
||||||
@@ -17,18 +19,73 @@ extension _App on _AppSettingsPageState {
|
|||||||
return Column(children: children.map((e) => e.cardx).toList());
|
return Column(children: children.map((e) => e.cardx).toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? _buildPlatformSetting() {
|
Widget _buildAndroidSettings() {
|
||||||
final func = switch (Pfs.type) {
|
return ExpandTile(
|
||||||
Pfs.android => AndroidSettingsPage.route.go,
|
|
||||||
Pfs.ios => IosSettingsPage.route.go,
|
|
||||||
_ => null,
|
|
||||||
};
|
|
||||||
if (func == null) return null;
|
|
||||||
return ListTile(
|
|
||||||
leading: const Icon(Icons.phone_android),
|
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),
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
onTap: () => func(context),
|
onTap: () async {
|
||||||
|
const prefix = 'widget_';
|
||||||
|
final data = <String, String>{};
|
||||||
|
final keys = PrefStore.shared.keys();
|
||||||
|
|
||||||
|
for (final key in keys) {
|
||||||
|
if (!key.startsWith(prefix)) continue;
|
||||||
|
final val = PrefStore.shared.get<String>(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<void> _saveWidgetSP(Map<String, String> map, Map<String, String> 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: [
|
actions: [
|
||||||
Btn.cancel(onTap: () => context.pop(false)),
|
Btn.cancel(),
|
||||||
Btn.ok(onTap: () => _onSaveColor(ctrl.text)),
|
Btn.ok(onTap: () => _onSaveColor(ctrl.text)),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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/private_key/list.dart';
|
||||||
import 'package:server_box/view/page/server/connection_stats.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/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/ios.dart';
|
||||||
import 'package:server_box/view/page/setting/platform/platform_pub.dart';
|
import 'package:server_box/view/page/setting/platform/platform_pub.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';
|
||||||
|
|||||||
@@ -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<AndroidSettingsPage> createState() => _AndroidSettingsPageState();
|
|
||||||
|
|
||||||
static const route = AppRouteNoArg(
|
|
||||||
page: AndroidSettingsPage.new,
|
|
||||||
path: '/settings/android',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const _homeWidgetPrefPrefix = 'widget_';
|
|
||||||
|
|
||||||
class _AndroidSettingsPageState extends State<AndroidSettingsPage> {
|
|
||||||
@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<String, String> map, Map<String, String> 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 = <String, String>{};
|
|
||||||
final keys = PrefStore.shared.keys();
|
|
||||||
|
|
||||||
for (final key in keys) {
|
|
||||||
final val = PrefStore.shared.get<String>(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 {},
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
@@ -16,8 +16,14 @@ class IosSettingsPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _IosSettingsPageState extends State<IosSettingsPage> {
|
class _IosSettingsPageState extends State<IosSettingsPage> {
|
||||||
final _pushToken = ValueNotifier<String?>(null);
|
final _pushToken = ValueNotifier<String?>(null);
|
||||||
|
|
||||||
final wc = WatchConnectivity();
|
final wc = WatchConnectivity();
|
||||||
|
late final _watchContextFuture = _loadWatchContext();
|
||||||
|
late final _pushTokenFuture = getToken();
|
||||||
|
|
||||||
|
Future<Map<String, dynamic>?> _loadWatchContext() async {
|
||||||
|
if (!await wc.isPaired) return null;
|
||||||
|
return await wc.applicationContext;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@@ -58,7 +64,7 @@ class _IosSettingsPageState extends State<IosSettingsPage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
subtitle: FutureWidget<String?>(
|
subtitle: FutureWidget<String?>(
|
||||||
future: getToken(),
|
future: _pushTokenFuture,
|
||||||
loading: const Text('...'),
|
loading: const Text('...'),
|
||||||
error: (error, trace) => Text('${libL10n.error}: $error'),
|
error: (error, trace) => Text('${libL10n.error}: $error'),
|
||||||
success: (text) {
|
success: (text) {
|
||||||
@@ -79,10 +85,7 @@ class _IosSettingsPageState extends State<IosSettingsPage> {
|
|||||||
|
|
||||||
Widget _buildWatchApp() {
|
Widget _buildWatchApp() {
|
||||||
return FutureWidget(
|
return FutureWidget(
|
||||||
future: () async {
|
future: _watchContextFuture,
|
||||||
if (!await wc.isPaired) return null;
|
|
||||||
return await wc.applicationContext;
|
|
||||||
}(),
|
|
||||||
loading: UIs.centerLoading,
|
loading: UIs.centerLoading,
|
||||||
error: (e, trace) {
|
error: (e, trace) {
|
||||||
Loggers.app.warning('WatchOS error', e, trace);
|
Loggers.app.warning('WatchOS error', e, trace);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:ui';
|
||||||
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';
|
||||||
@@ -15,68 +16,124 @@ class ServerDetailOrderPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _ServerDetailOrderPageState extends State<ServerDetailOrderPage> {
|
class _ServerDetailOrderPageState extends State<ServerDetailOrderPage> {
|
||||||
final prop = Stores.setting.detailCardOrder;
|
final prop = Stores.setting.detailCardOrder;
|
||||||
|
final disabledProp = Stores.setting.detailCardDisabled;
|
||||||
|
|
||||||
|
late List<String> _order;
|
||||||
|
late Set<String> _enabled;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadData() {
|
||||||
|
final keys = prop.fetch();
|
||||||
|
final disabled = disabledProp.fetch();
|
||||||
|
_order = List<String>.from(keys);
|
||||||
|
for (final d in disabled) {
|
||||||
|
if (!_order.contains(d)) {
|
||||||
|
_order.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_enabled = Set<String>.from(keys.where((k) => !disabled.contains(k)));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: CustomAppBar(title: Text(l10n.serverDetailOrder)),
|
appBar: CustomAppBar(title: Text(l10n.serverDetailOrder)),
|
||||||
body: _buildBody(),
|
body: SafeArea(child: _buildBody()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _proxyDecorator(Widget child, int _, Animation<double> 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() {
|
Widget _buildBody() {
|
||||||
return ValBuilder(
|
return ReorderableListView.builder(
|
||||||
listenable: prop.listenable(),
|
key: const PageStorageKey('srv_detail_seq'),
|
||||||
builder: (keys) {
|
padding: const EdgeInsets.all(7),
|
||||||
final disabled = ServerDetailCards.names.where((e) => !keys.contains(e)).toList();
|
buildDefaultDragHandles: false,
|
||||||
final allKeys = [...keys, ...disabled];
|
itemCount: _order.length,
|
||||||
return ReorderableListView.builder(
|
proxyDecorator: _proxyDecorator,
|
||||||
padding: const EdgeInsets.all(7),
|
itemBuilder: (_, idx) => _buildListItem(_order[idx], idx),
|
||||||
buildDefaultDragHandles: false,
|
onReorder: _handleReorder,
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCheckBox(List<String> keys, String key, int idx, bool value) {
|
Widget _buildListItem(String key, int idx) {
|
||||||
return Checkbox(
|
final isEnabled = _enabled.contains(key);
|
||||||
value: value,
|
return ReorderableDelayedDragStartListener(
|
||||||
onChanged: (val) {
|
key: ValueKey(key),
|
||||||
if (val == null) return;
|
index: idx,
|
||||||
if (val) {
|
child: CardX(
|
||||||
if (idx >= keys.length) {
|
child: ListTile(
|
||||||
keys.add(key);
|
contentPadding: const EdgeInsets.only(left: 23, right: 11),
|
||||||
} else {
|
leading: Icon(ServerDetailCards.fromName(key)?.icon),
|
||||||
keys.insert(idx - 1, key);
|
title: Text(key, style: isEnabled ? null : TextStyle(color: Colors.grey)),
|
||||||
}
|
trailing: Row(
|
||||||
} else {
|
mainAxisSize: MainAxisSize.min,
|
||||||
keys.remove(key);
|
children: [
|
||||||
}
|
_buildCheckBox(key, isEnabled),
|
||||||
prop.put(keys);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,27 +31,10 @@ class _ServerDetailOrderPageState extends State<ServerFuncBtnsOrderPage> {
|
|||||||
final disabled = ServerFuncBtn.values.map((e) => e.index).where((e) => !keys.contains(e)).toList();
|
final disabled = ServerFuncBtn.values.map((e) => e.index).where((e) => !keys.contains(e)).toList();
|
||||||
final allKeys = [...keys, ...disabled];
|
final allKeys = [...keys, ...disabled];
|
||||||
return ReorderableListView.builder(
|
return ReorderableListView.builder(
|
||||||
|
key: const PageStorageKey('srv_func_seq'),
|
||||||
padding: const EdgeInsets.all(7),
|
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,
|
itemCount: allKeys.length,
|
||||||
|
itemBuilder: (_, idx) => _buildListItem(allKeys[idx], idx, keys),
|
||||||
onReorder: (o, n) {
|
onReorder: (o, n) {
|
||||||
if (o >= keys.length || n >= keys.length) {
|
if (o >= keys.length || n >= keys.length) {
|
||||||
context.showSnackBar(libL10n.disabled);
|
context.showSnackBar(libL10n.disabled);
|
||||||
@@ -64,6 +47,25 @@ class _ServerDetailOrderPageState extends State<ServerFuncBtnsOrderPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _buildListItem(int key, int idx, List<int> 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<int> keys, int key, int idx, bool value) {
|
Widget _buildCheckBox(List<int> keys, int key, int idx, bool value) {
|
||||||
return Checkbox(
|
return Checkbox(
|
||||||
value: value,
|
value: value,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ class _ServerOrderPageState extends ConsumerState<ServerOrderPage> {
|
|||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: CustomAppBar(title: Text(l10n.serverOrder)),
|
appBar: CustomAppBar(title: Text(l10n.serverOrder)),
|
||||||
body: _buildBody(),
|
body: SafeArea(child: _buildBody()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'dart:ui';
|
||||||
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';
|
||||||
@@ -18,19 +19,43 @@ class SSHVirtKeySettingPage extends StatefulWidget {
|
|||||||
|
|
||||||
class _SSHVirtKeySettingPageState extends State<SSHVirtKeySettingPage> {
|
class _SSHVirtKeySettingPageState extends State<SSHVirtKeySettingPage> {
|
||||||
final prop = Stores.setting.sshVirtKeys;
|
final prop = Stores.setting.sshVirtKeys;
|
||||||
|
final disabledProp = Stores.setting.sshVirtKeysDisabled;
|
||||||
|
|
||||||
|
late List<int> _order;
|
||||||
|
late Set<int> _enabled;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_loadData();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _loadData() {
|
||||||
|
final keys = prop.fetch();
|
||||||
|
final disabled = disabledProp.fetch();
|
||||||
|
_order = List<int>.from(keys);
|
||||||
|
for (final d in disabled) {
|
||||||
|
if (!_order.contains(d)) {
|
||||||
|
_order.add(d);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_enabled = Set<int>.from(keys.where((k) => !disabled.contains(k)));
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: CustomAppBar(title: Text(l10n.editVirtKeys)),
|
appBar: CustomAppBar(title: Text(l10n.editVirtKeys)),
|
||||||
body: Column(
|
body: SafeArea(
|
||||||
children: [
|
child: Column(
|
||||||
Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(7),
|
Padding(
|
||||||
child: _buildOneLineVirtKey().cardx,
|
padding: const EdgeInsets.all(7),
|
||||||
),
|
child: _buildOneLineVirtKey().cardx,
|
||||||
Expanded(child: _buildBody()),
|
),
|
||||||
],
|
Expanded(child: _buildBody()),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -42,46 +67,62 @@ class _SSHVirtKeySettingPageState extends State<SSHVirtKeySettingPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody() {
|
Widget _proxyDecorator(Widget child, int _, Animation<double> animation) {
|
||||||
return ValBuilder(
|
return AnimatedBuilder(
|
||||||
listenable: prop.listenable(),
|
animation: animation,
|
||||||
builder: (keys) {
|
builder: (BuildContext context, Widget? child) {
|
||||||
final disabled = VirtKey.values
|
final double animValue = Curves.easeInOut.transform(animation.value);
|
||||||
.map((e) => e.index)
|
final double elevation = lerpDouble(1, 6, animValue)!;
|
||||||
.where((e) => !keys.contains(e))
|
final double scale = lerpDouble(1, 1.02, animValue)!;
|
||||||
.toList();
|
return Transform.scale(
|
||||||
final allKeys = [...keys, ...disabled];
|
scale: scale,
|
||||||
return ReorderableListView.builder(
|
child: Card(elevation: elevation, child: child),
|
||||||
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);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTitle(VirtKey key) {
|
Widget _buildBody() {
|
||||||
return key.icon == null
|
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)
|
? Text(key.text)
|
||||||
: Row(
|
: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -90,24 +131,51 @@ class _SSHVirtKeySettingPageState extends State<SSHVirtKeySettingPage> {
|
|||||||
Icon(key.icon),
|
Icon(key.icon),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
return IgnorePointer(
|
||||||
|
child: Opacity(
|
||||||
Widget _buildCheckBox(List<int> keys, int key, int idx, bool value) {
|
opacity: isEnabled ? 1.0 : 0.5,
|
||||||
return Checkbox(
|
child: text,
|
||||||
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 _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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ class LocalFilePage extends ConsumerStatefulWidget {
|
|||||||
class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKeepAliveClientMixin {
|
class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKeepAliveClientMixin {
|
||||||
late final _path = LocalPath(widget.args?.initDir ?? Paths.file);
|
late final _path = LocalPath(widget.args?.initDir ?? Paths.file);
|
||||||
final _sortType = _SortType.name.vn;
|
final _sortType = _SortType.name.vn;
|
||||||
|
late Future<List<(FileSystemEntity, FileStat)>> _entitiesFuture = _getEntities();
|
||||||
bool get isPickFile => widget.args?.isPickFile ?? false;
|
bool get isPickFile => widget.args?.isPickFile ?? false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -43,6 +44,13 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
_sortType.dispose();
|
_sortType.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _refresh() async {
|
||||||
|
setStateSafe(() {
|
||||||
|
_entitiesFuture = _getEntities();
|
||||||
|
});
|
||||||
|
await _entitiesFuture;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
super.build(context);
|
super.build(context);
|
||||||
@@ -65,7 +73,7 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
await destinationDir.create(recursive: true);
|
await destinationDir.create(recursive: true);
|
||||||
}
|
}
|
||||||
await File(path).copy(_path.path.joinPath(name));
|
await File(path).copy(_path.path.joinPath(name));
|
||||||
setState(() {});
|
_refresh();
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
),
|
),
|
||||||
@@ -73,7 +81,7 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.refresh),
|
icon: const Icon(Icons.refresh),
|
||||||
tooltip: MaterialLocalizations.of(context).refreshIndicatorSemanticLabel,
|
tooltip: MaterialLocalizations.of(context).refreshIndicatorSemanticLabel,
|
||||||
onPressed: () => setState(() {}),
|
onPressed: _refresh,
|
||||||
),
|
),
|
||||||
if (!isPickFile) _buildMissionBtn(),
|
if (!isPickFile) _buildMissionBtn(),
|
||||||
_buildSortBtn(),
|
_buildSortBtn(),
|
||||||
@@ -81,9 +89,7 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
),
|
),
|
||||||
body: isMobile
|
body: isMobile
|
||||||
? RefreshIndicator(
|
? RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: _refresh,
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
child: _sortType.listen(_buildBody),
|
child: _sortType.listen(_buildBody),
|
||||||
)
|
)
|
||||||
: _sortType.listen(_buildBody),
|
: _sortType.listen(_buildBody),
|
||||||
@@ -91,15 +97,8 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBody() {
|
Widget _buildBody() {
|
||||||
Future<List<(FileSystemEntity, FileStat)>> 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(
|
return FutureWidget(
|
||||||
future: getEntities(),
|
future: _entitiesFuture,
|
||||||
loading: UIs.placeholder,
|
loading: UIs.placeholder,
|
||||||
success: (items) {
|
success: (items) {
|
||||||
items ??= [];
|
items ??= [];
|
||||||
@@ -114,7 +113,7 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
title: const Text('..'),
|
title: const Text('..'),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_path.update('..');
|
_path.update('..');
|
||||||
setState(() {});
|
_refresh();
|
||||||
},
|
},
|
||||||
).cardx;
|
).cardx;
|
||||||
}
|
}
|
||||||
@@ -171,7 +170,7 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_path.update(fileName);
|
_path.update(fileName);
|
||||||
setState(() {});
|
_refresh();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -184,6 +183,13 @@ class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKee
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<List<(FileSystemEntity, FileStat)>> _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() {
|
Widget _buildSortBtn() {
|
||||||
return _sortType.listenVal((value) {
|
return _sortType.listenVal((value) {
|
||||||
return PopupMenuButton<_SortType>(
|
return PopupMenuButton<_SortType>(
|
||||||
@@ -397,19 +403,12 @@ enum _SortType {
|
|||||||
size,
|
size,
|
||||||
time;
|
time;
|
||||||
|
|
||||||
List<FileSystemEntity> sort(List<FileSystemEntity> files) {
|
int compareTuple((FileSystemEntity, FileStat) a, (FileSystemEntity, FileStat) b) {
|
||||||
switch (this) {
|
return switch (this) {
|
||||||
case _SortType.name:
|
_SortType.name => a.$1.path.compareTo(b.$1.path),
|
||||||
files.sort((a, b) => a.path.compareTo(b.path));
|
_SortType.size => a.$2.size.compareTo(b.$2.size),
|
||||||
break;
|
_SortType.time => a.$2.modified.compareTo(b.$2.modified),
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String get i18n => switch (this) {
|
String get i18n => switch (this) {
|
||||||
|
|||||||
@@ -497,8 +497,8 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "v1.0.362"
|
ref: "v1.0.363"
|
||||||
resolved-ref: "3c75cfe1f07ee664a912d330e2a38bda51bee8d9"
|
resolved-ref: "4b745be6f33b2e7f274d44f26175df440345cefb"
|
||||||
url: "https://github.com/lollipopkit/fl_lib"
|
url: "https://github.com/lollipopkit/fl_lib"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ dependencies:
|
|||||||
fl_lib:
|
fl_lib:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/lollipopkit/fl_lib
|
url: https://github.com/lollipopkit/fl_lib
|
||||||
ref: v1.0.362
|
ref: v1.0.363
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
# webdav_client_plus:
|
# webdav_client_plus:
|
||||||
|
|||||||
Reference in New Issue
Block a user