mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2026-01-31 13:25:10 +01:00
new: custom tabs (#889)
This commit is contained in:
@@ -33,6 +33,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
|
||||
late final _notifier = ref.read(serversNotifierProvider.notifier);
|
||||
late final _provider = ref.read(serversNotifierProvider);
|
||||
late List<AppTab> _tabs = Stores.setting.homeTabs.fetch();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -51,13 +52,30 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
SystemUIs.switchStatusBar(hide: false);
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
// avoid index out of range
|
||||
if (_selectIndex.value >= AppTab.values.length || _selectIndex.value < 0) {
|
||||
if (_selectIndex.value >= _tabs.length || _selectIndex.value < 0) {
|
||||
_selectIndex.value = 0;
|
||||
}
|
||||
_pageController = PageController(initialPage: _selectIndex.value);
|
||||
if (Stores.setting.generalWakeLock.fetch()) {
|
||||
WakelockPlus.enable();
|
||||
}
|
||||
|
||||
// Listen to homeTabs changes
|
||||
Stores.setting.homeTabs.listenable().addListener(() {
|
||||
final newTabs = Stores.setting.homeTabs.fetch();
|
||||
if (mounted && newTabs != _tabs) {
|
||||
setState(() {
|
||||
_tabs = newTabs;
|
||||
// Ensure current page index is valid
|
||||
if (_selectIndex.value >= _tabs.length) {
|
||||
_selectIndex.value = _tabs.length - 1;
|
||||
}
|
||||
if (_selectIndex.value < 0 && _tabs.isNotEmpty) {
|
||||
_selectIndex.value = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -119,9 +137,9 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
Expanded(
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
itemCount: AppTab.values.length,
|
||||
itemCount: _tabs.length,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (_, index) => AppTab.values[index].page,
|
||||
itemBuilder: (_, index) => _tabs[index].page,
|
||||
onPageChanged: (value) {
|
||||
FocusScope.of(context).unfocus();
|
||||
if (!_switchingPage) {
|
||||
@@ -146,7 +164,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
animationDuration: const Duration(milliseconds: 250),
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected,
|
||||
destinations: AppTab.navDestinations,
|
||||
destinations: _tabs.map((tab) => tab.navDestination).toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -165,7 +183,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
trailing: extended ? const SizedBox(height: 20) : null,
|
||||
labelType: extended ? NavigationRailLabelType.none : NavigationRailLabelType.all,
|
||||
selectedIndex: idx,
|
||||
destinations: AppTab.navRailDestinations,
|
||||
destinations: _tabs.map((tab) => tab.navRailDestination).toList(),
|
||||
onDestinationSelected: _onDestinationSelected,
|
||||
),
|
||||
),
|
||||
@@ -236,6 +254,7 @@ class _HomePageState extends ConsumerState<HomePage>
|
||||
|
||||
void _onDestinationSelected(int index) {
|
||||
if (_selectIndex.value == index) return;
|
||||
if (index < 0 || index >= _tabs.length) return;
|
||||
_selectIndex.value = index;
|
||||
_switchingPage = true;
|
||||
_pageController.animateToPage(
|
||||
|
||||
@@ -8,6 +8,7 @@ extension _App on _AppSettingsPageState {
|
||||
_buildThemeMode(),
|
||||
_buildAppColor(),
|
||||
_buildCheckUpdate(),
|
||||
_buildHomeTabs(),
|
||||
PlatformPublicSettings.buildBioAuth,
|
||||
if (specific != null) specific,
|
||||
_buildAppMore(),
|
||||
@@ -274,4 +275,15 @@ extension _App on _AppSettingsPageState {
|
||||
trailing: StoreSwitch(prop: _setting.hideTitleBar),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildHomeTabs() {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.tab),
|
||||
title: Text(l10n.homeTabs),
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
onTap: () {
|
||||
HomeTabsConfigPage.route.go(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
130
lib/view/page/setting/entries/home_tabs.dart
Normal file
130
lib/view/page/setting/entries/home_tabs.dart
Normal file
@@ -0,0 +1,130 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/tab.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class HomeTabsConfigPage extends ConsumerStatefulWidget {
|
||||
const HomeTabsConfigPage({super.key});
|
||||
|
||||
static final route = AppRouteNoArg(page: HomeTabsConfigPage.new, path: '/settings/home-tabs');
|
||||
|
||||
@override
|
||||
ConsumerState<HomeTabsConfigPage> createState() => _HomeTabsConfigPageState();
|
||||
}
|
||||
|
||||
class _HomeTabsConfigPageState extends ConsumerState<HomeTabsConfigPage> {
|
||||
final _availableTabs = AppTab.values;
|
||||
var _selectedTabs = List<AppTab>.from(Stores.setting.homeTabs.fetch());
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(
|
||||
title: Text(l10n.homeTabs),
|
||||
actions: [
|
||||
TextButton(onPressed: _resetToDefault, child: Text(libL10n.reset)),
|
||||
TextButton(onPressed: _saveAndExit, child: Text(libL10n.save)),
|
||||
],
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(l10n.homeTabsCustomizeDesc, style: context.theme.textTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
child: ReorderableListView.builder(
|
||||
itemCount: _selectedTabs.length,
|
||||
onReorder: _onReorder,
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (context, index) {
|
||||
final tab = _selectedTabs[index];
|
||||
return _buildTabItem(tab, index, true);
|
||||
},
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(l10n.availableTabs, style: context.theme.textTheme.titleMedium),
|
||||
),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemCount: _availableTabs.length,
|
||||
itemBuilder: (context, index) {
|
||||
final tab = _availableTabs[index];
|
||||
if (_selectedTabs.contains(tab)) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _buildTabItem(tab, index, false);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTabItem(AppTab tab, int index, bool isSelected) {
|
||||
final canRemove = _selectedTabs.length > 1;
|
||||
final child = ListTile(
|
||||
leading: tab.navDestination.icon,
|
||||
title: Text(tab.navDestination.label),
|
||||
trailing: isSelected
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: canRemove ? () => _removeTab(tab) : null,
|
||||
color: canRemove ? null : Theme.of(context).disabledColor,
|
||||
tooltip: canRemove ? libL10n.delete : l10n.atLeastOneTab,
|
||||
)
|
||||
: IconButton(icon: const Icon(Icons.add), onPressed: () => _addTab(tab)),
|
||||
onTap: isSelected && canRemove ? () => _removeTab(tab) : null,
|
||||
);
|
||||
|
||||
return Card(
|
||||
key: ValueKey(tab.name),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
child: isSelected ? ReorderableDragStartListener(index: index, child: child) : child,
|
||||
);
|
||||
}
|
||||
|
||||
void _onReorder(int oldIndex, int newIndex) {
|
||||
setState(() {
|
||||
if (newIndex > oldIndex) {
|
||||
newIndex -= 1;
|
||||
}
|
||||
final tab = _selectedTabs.removeAt(oldIndex);
|
||||
_selectedTabs.insert(newIndex, tab);
|
||||
});
|
||||
}
|
||||
|
||||
void _addTab(AppTab tab) {
|
||||
setState(() {
|
||||
_selectedTabs.add(tab);
|
||||
});
|
||||
}
|
||||
|
||||
void _removeTab(AppTab tab) {
|
||||
if (_selectedTabs.length <= 1) {
|
||||
context.showSnackBar(l10n.atLeastOneTab);
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_selectedTabs.remove(tab);
|
||||
});
|
||||
}
|
||||
|
||||
void _saveAndExit() {
|
||||
Stores.setting.homeTabs.put(_selectedTabs);
|
||||
context.pop();
|
||||
}
|
||||
|
||||
void _resetToDefault() {
|
||||
setState(() {
|
||||
_selectedTabs = List<AppTab>.from(AppTab.values);
|
||||
});
|
||||
Stores.setting.homeTabs.put(_selectedTabs);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import 'package:server_box/generated/l10n/l10n.dart';
|
||||
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';
|
||||
|
||||
Reference in New Issue
Block a user