opt: bar set (#862)

* opt: bar set

* opt: navbar

* fix: type
This commit is contained in:
My-Responsitories
2025-05-21 02:14:08 +08:00
committed by GitHub
parent acb3784071
commit 218e829fd4
19 changed files with 288 additions and 556 deletions

View File

@@ -0,0 +1,4 @@
abstract class EnumWithLabel {
String get label;
int get index;
}

View File

@@ -1,5 +1,7 @@
import 'package:PiliPlus/models/common/enum_with_label.dart';
import 'package:PiliPlus/pages/bangumi/controller.dart';
import 'package:PiliPlus/pages/bangumi/view.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/hot/controller.dart';
import 'package:PiliPlus/pages/hot/view.dart';
import 'package:PiliPlus/pages/live/controller.dart';
@@ -8,9 +10,10 @@ import 'package:PiliPlus/pages/rank/controller.dart';
import 'package:PiliPlus/pages/rank/view.dart';
import 'package:PiliPlus/pages/rcmd/controller.dart';
import 'package:PiliPlus/pages/rcmd/view.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
enum HomeTabType {
enum HomeTabType implements EnumWithLabel {
live('直播'),
rcmd('推荐'),
hot('热门'),
@@ -18,45 +21,26 @@ enum HomeTabType {
bangumi('番剧'),
cinema('影视');
final String description;
const HomeTabType(this.description);
}
@override
final String label;
const HomeTabType(this.label);
List get homeTabsConfig => [
{
'label': '直播',
'type': HomeTabType.live,
'ctr': Get.find<LiveController>,
'page': const LivePage(),
},
{
'label': '推荐',
'type': HomeTabType.rcmd,
'ctr': Get.find<RcmdController>,
'page': const RcmdPage(),
},
{
'label': '热门',
'type': HomeTabType.hot,
'ctr': Get.find<HotController>,
'page': const HotPage(),
},
{
'label': '分区',
'type': HomeTabType.rank,
'ctr': Get.find<RankController>,
'page': const RankPage(),
},
{
'label': '番剧',
'type': HomeTabType.bangumi,
'ctr': Get.find<BangumiController>,
'page': const BangumiPage(tabType: HomeTabType.bangumi),
},
{
'label': '影视',
'type': HomeTabType.cinema,
'ctr': Get.find<BangumiController>,
'page': const BangumiPage(tabType: HomeTabType.cinema),
},
];
ScrollOrRefreshMixin Function() get ctr => switch (this) {
HomeTabType.live => Get.find<LiveController>,
HomeTabType.rcmd => Get.find<RcmdController>,
HomeTabType.hot => Get.find<HotController>,
HomeTabType.rank =>
(Get.find<RankController>) as ScrollOrRefreshMixin Function(),
HomeTabType.bangumi || HomeTabType.cinema => () =>
Get.find<BangumiController>(tag: name),
};
Widget get page => switch (this) {
HomeTabType.live => const LivePage(),
HomeTabType.rcmd => const RcmdPage(),
HomeTabType.hot => const HotPage(),
HomeTabType.rank => const RankPage(),
HomeTabType.bangumi => const BangumiPage(tabType: HomeTabType.bangumi),
HomeTabType.cinema => const BangumiPage(tabType: HomeTabType.cinema),
};
}

View File

@@ -1,68 +1,34 @@
import 'package:PiliPlus/models/common/enum_with_label.dart';
import 'package:PiliPlus/pages/dynamics/view.dart';
import 'package:PiliPlus/pages/home/view.dart';
import 'package:PiliPlus/pages/media/view.dart';
import 'package:flutter/material.dart';
// enum NavigationBarType {
// home(
// '首页',
// Icon(Icons.home_outlined, size: 23),
// Icon(Icons.home, size: 21),
// ),
// dynamics(
// '动态',
// Icon(Icons.motion_photos_on_outlined, size: 21),
// Icon(Icons.motion_photos_on, size: 21),
// ),
// media(
// '媒体库',
// Icon(Icons.video_collection_outlined, size: 21),
// Icon(Icons.video_collection, size: 21),
// );
enum NavigationBarType implements EnumWithLabel {
home(
'首页',
Icon(Icons.home_outlined, size: 23),
Icon(Icons.home, size: 21),
HomePage(),
),
dynamics(
'动态',
Icon(Icons.motion_photos_on_outlined, size: 21),
Icon(Icons.motion_photos_on, size: 21),
DynamicsPage(),
),
media(
'媒体库',
Icon(Icons.video_collection_outlined, size: 21),
Icon(Icons.video_collection, size: 21),
MediaPage(),
);
// final Icon icon;
// final Icon selectIcon;
// final String label;
@override
final String label;
final Icon icon;
final Icon selectIcon;
final Widget page;
// const NavigationBarType(this.label, this.icon, this.selectIcon);
// }
// TODO enum
List defaultNavigationBars = [
{
'id': 0,
'icon': const Icon(
Icons.home_outlined,
size: 23,
),
'selectIcon': const Icon(
Icons.home,
size: 23,
),
'label': "首页",
'count': 0,
},
{
'id': 1,
'icon': const Icon(
Icons.motion_photos_on_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.motion_photos_on,
size: 21,
),
'label': "动态",
'count': 0,
},
{
'id': 2,
'icon': const Icon(
Icons.video_collection_outlined,
size: 21,
),
'selectIcon': const Icon(
Icons.video_collection,
size: 21,
),
'label': "媒体库",
'count': 0,
}
];
const NavigationBarType(this.label, this.icon, this.selectIcon, this.page);
}

View File

@@ -6,7 +6,7 @@ import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
abstract mixin class ScrollOrRefreshMixin {
mixin ScrollOrRefreshMixin {
ScrollController get scrollController;
void animateToTop() => scrollController.animToTop();

View File

@@ -6,6 +6,7 @@ import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/models/dynamics/result.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/dynamics/controller.dart';
@@ -82,7 +83,7 @@ class _DynamicsTabPageState
@override
void listener() {
if (_mainController.navigationBars[0]['id'] != 1 &&
if (_mainController.navigationBars[0] != NavigationBarType.dynamics &&
_mainController.selectedIndex.value == 0) {
return;
}

View File

@@ -13,8 +13,7 @@ import 'package:get/get.dart';
class HomeController extends GetxController
with GetSingleTickerProviderStateMixin, ScrollOrRefreshMixin {
late List tabs;
late List tabsCtrList;
late List<HomeTabType> tabs;
late TabController tabController;
RxBool isLogin = false.obs;
@@ -28,15 +27,7 @@ class HomeController extends GetxController
late RxString defaultSearch = ''.obs;
late int lateCheckSearchAt = 0;
ScrollOrRefreshMixin get controller {
final index = tabController.index;
return tabsCtrList[index]!(
tag: switch (tabs[index]['type']) {
HomeTabType.bangumi => HomeTabType.bangumi.name,
HomeTabType.cinema => HomeTabType.cinema.name,
_ => null,
});
}
ScrollOrRefreshMixin get controller => tabs[tabController.index].ctr();
@override
ScrollController get scrollController => controller.scrollController;
@@ -73,21 +64,12 @@ class HomeController extends GetxController
}
void setTabConfig() {
final defaultTabs = [...homeTabsConfig];
final tabbarSort = GStorage.tabbarSort;
defaultTabs
..retainWhere(
(item) => tabbarSort.contains((item['type'] as HomeTabType).name))
..sort((a, b) => tabbarSort
.indexOf((a['type'] as HomeTabType).name)
.compareTo(tabbarSort.indexOf((b['type'] as HomeTabType).name)));
tabs = defaultTabs;
tabsCtrList = tabs.map((e) => e['ctr']).toList();
List<int>? localTabs = GStorage.setting.get(SettingBoxKey.tabBarSort);
tabs = localTabs?.map((i) => HomeTabType.values[i]).toList() ??
HomeTabType.values;
tabController = TabController(
initialIndex: max(0, tabbarSort.indexOf(HomeTabType.rcmd.name)),
initialIndex: max(0, tabs.indexOf(HomeTabType.rcmd)),
length: tabs.length,
vsync: this,
);

View File

@@ -49,7 +49,7 @@ class _HomePageState extends State<HomePage>
child: TabBar(
controller: _homeController.tabController,
tabs: [
for (var i in _homeController.tabs) Tab(text: i['label'])
for (var i in _homeController.tabs) Tab(text: i.label)
],
isScrollable: true,
dividerColor: Colors.transparent,
@@ -71,8 +71,7 @@ class _HomePageState extends State<HomePage>
Expanded(
child: tabBarView(
controller: _homeController.tabController,
children:
_homeController.tabs.map<Widget>((e) => e['page']).toList(),
children: _homeController.tabs.map((e) => e.page).toList(),
),
),
],

View File

@@ -80,9 +80,8 @@ class _HotPageState extends CommonPageState<HotPage, HotController>
try {
HomeController homeController =
Get.find<HomeController>();
int index = homeController.tabs.indexWhere(
(item) => item['type'] == HomeTabType.rank,
);
int index = homeController.tabs
.indexOf(HomeTabType.rank);
if (index != -1) {
homeController.tabController.animateTo(index);
} else {

View File

@@ -5,18 +5,14 @@ import 'package:PiliPlus/grpc/im.dart';
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
import 'package:PiliPlus/models/common/msg/msg_unread_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/pages/dynamics/view.dart';
import 'package:PiliPlus/pages/home/view.dart';
import 'package:PiliPlus/pages/media/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MainController extends GetxController {
List<Widget> pages = <Widget>[];
RxList navigationBars = [].obs;
RxList<NavigationBarType> navigationBars = <NavigationBarType>[].obs;
int dynCount = 0;
StreamController<bool>? bottomBarStream;
late bool hideTabBar;
@@ -58,7 +54,7 @@ class MainController extends GetxController {
SettingBoxKey.dynamicBadgeMode,
defaultValue: DynamicBadgeMode.number.index)];
dynIndex = navigationBars.indexWhere((e) => e['id'] == 1);
dynIndex = navigationBars.indexOf(NavigationBarType.dynamics);
if (dynamicBadgeMode != DynamicBadgeMode.hidden) {
if (dynIndex != -1) {
if (checkDynamic) {
@@ -68,7 +64,7 @@ class MainController extends GetxController {
}
}
homeIndex = navigationBars.indexWhere((e) => e['id'] == 0);
homeIndex = navigationBars.indexOf(NavigationBarType.home);
if (msgBadgeMode != DynamicBadgeMode.hidden) {
if (homeIndex != -1) {
lastCheckUnreadAt = DateTime.now().millisecondsSinceEpoch;
@@ -141,8 +137,8 @@ class MainController extends GetxController {
}
Future<void> setCount([int count = 0]) async {
if (dynIndex == -1 || navigationBars[dynIndex]['count'] == count) return;
navigationBars[dynIndex]['count'] = count;
if (dynIndex == -1 || dynCount == count) return;
dynCount = count;
navigationBars.refresh();
}
@@ -161,28 +157,19 @@ class MainController extends GetxController {
}
void setNavBarConfig() {
List defaultNavTabs = [...defaultNavigationBars];
List navBarSort =
GStorage.setting.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2]);
defaultNavTabs
..retainWhere((item) => navBarSort.contains(item['id']))
..sort((a, b) =>
navBarSort.indexOf(a['id']).compareTo(navBarSort.indexOf(b['id'])));
navigationBars.value = defaultNavTabs;
int defaultHomePage = GStorage.setting
.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
int defaultIndex =
navigationBars.indexWhere((item) => item['id'] == defaultHomePage);
// 如果找不到匹配项默认索引设置为0或其他合适的值
selectedIndex.value = defaultIndex != -1 ? defaultIndex : 0;
pages = navigationBars
.map<Widget>((e) => switch (e['id']) {
0 => const HomePage(),
1 => const DynamicsPage(),
2 => const MediaPage(),
_ => throw UnimplementedError(),
})
.toList();
List<int>? navBarSort =
(GStorage.setting.get(SettingBoxKey.navBarSort) as List?)?.cast();
int defaultHomePage = GStorage.defaultHomePage;
late final List<NavigationBarType> navigationBars;
if (navBarSort == null) {
navigationBars = NavigationBarType.values;
} else {
navigationBars =
navBarSort.map((i) => NavigationBarType.values[i]).toList();
if (!navBarSort.contains(defaultHomePage)) defaultHomePage = 0;
}
this.navigationBars.value = navigationBars;
selectedIndex.value = defaultHomePage;
}
@override

View File

@@ -4,6 +4,7 @@ import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/tabs.dart';
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/pages/dynamics/controller.dart';
import 'package:PiliPlus/pages/dynamics/view.dart';
import 'package:PiliPlus/pages/home/controller.dart';
@@ -94,8 +95,8 @@ class _MainAppState extends State<MainApp>
void _checkDefaultSearch([bool shouldCheck = false]) {
if (_mainController.homeIndex != -1 && _homeController.enableSearchWord) {
if (shouldCheck &&
_mainController.pages[_mainController.selectedIndex.value]
is! HomePage) {
_mainController.navigationBars[_mainController.selectedIndex.value] !=
NavigationBarType.home) {
return;
}
int now = DateTime.now().millisecondsSinceEpoch;
@@ -112,8 +113,8 @@ class _MainAppState extends State<MainApp>
_mainController.homeIndex != -1 &&
_mainController.msgBadgeMode != DynamicBadgeMode.hidden) {
if (shouldCheck &&
_mainController.pages[_mainController.selectedIndex.value]
is! HomePage) {
_mainController.navigationBars[_mainController.selectedIndex.value] !=
NavigationBarType.home) {
return;
}
int now = DateTime.now().millisecondsSinceEpoch;
@@ -128,6 +129,7 @@ class _MainAppState extends State<MainApp>
void setIndex(int value) {
feedBack();
final currentPage = _mainController.navigationBars[value].page;
if (value != _mainController.selectedIndex.value) {
_mainController.selectedIndex.value = value;
if (_mainController.mainTabBarView) {
@@ -135,7 +137,6 @@ class _MainAppState extends State<MainApp>
} else {
_mainController.controller.jumpToPage(value);
}
dynamic currentPage = _mainController.pages[value];
if (currentPage is HomePage) {
_checkDefaultSearch();
_checkUnread();
@@ -143,8 +144,6 @@ class _MainAppState extends State<MainApp>
_mainController.setCount();
}
} else {
dynamic currentPage = _mainController.pages[value];
int now = DateTime.now().millisecondsSinceEpoch;
if (now - _lastSelectTime < 500) {
EasyThrottle.throttle('topOrRefresh', const Duration(milliseconds: 500),
@@ -241,16 +240,23 @@ class _MainAppState extends State<MainApp>
..._mainController.navigationBars
.map((e) {
return NavigationDrawerDestination(
label: Text(e['label']),
label: Text(e.label),
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
type: e,
count: e ==
NavigationBarType
.dynamics
? _mainController.dynCount
: 0,
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
type: e,
count: e ==
NavigationBarType
.dynamics
? _mainController.dynCount
: 0,
selected: true,
),
);
}),
@@ -271,17 +277,20 @@ class _MainAppState extends State<MainApp>
destinations: _mainController.navigationBars
.map(
(e) => NavigationRailDestination(
label: Text(e.label),
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
type: e,
count: e == NavigationBarType.dynamics
? _mainController.dynCount
: 0,
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
type: e,
count: e == NavigationBarType.dynamics
? _mainController.dynCount
: 0,
selected: true,
),
label: Text(e['label']),
),
)
.toList(),
@@ -315,12 +324,16 @@ class _MainAppState extends State<MainApp>
isPortrait ? Axis.horizontal : Axis.vertical,
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.controller,
children: _mainController.pages,
children: _mainController.navigationBars
.map((i) => i.page)
.toList(),
)
: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.controller,
children: _mainController.pages,
children: _mainController.navigationBars
.map((i) => i.page)
.toList(),
),
),
),
@@ -353,17 +366,22 @@ class _MainAppState extends State<MainApp>
_mainController.navigationBars.map(
(e) {
return NavigationDestination(
label: e.label,
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
type: e,
count: e ==
NavigationBarType.dynamics
? _mainController.dynCount
: 0,
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
type: e,
count: e ==
NavigationBarType.dynamics
? _mainController.dynCount
: 0,
selected: true,
),
label: e['label'],
);
},
).toList(),
@@ -383,17 +401,24 @@ class _MainAppState extends State<MainApp>
items: _mainController.navigationBars
.map(
(e) => BottomNavigationBarItem(
label: e.label,
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
type: e,
count: e ==
NavigationBarType
.dynamics
? _mainController.dynCount
: 0,
),
activeIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
type: e,
count: e ==
NavigationBarType
.dynamics
? _mainController.dynCount
: 0,
selected: true,
),
label: e['label'],
),
)
.toList(),
@@ -409,21 +434,21 @@ class _MainAppState extends State<MainApp>
}
Widget _buildIcon({
required int id,
required NavigationBarType type,
required int count,
required Widget icon,
}) =>
id == 1 &&
_mainController.dynamicBadgeMode != DynamicBadgeMode.hidden &&
count > 0
? Badge(
label: _mainController.dynamicBadgeMode == DynamicBadgeMode.number
? Text(count.toString())
: null,
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
child: icon,
)
: icon;
bool selected = false,
}) {
final icon = selected ? type.selectIcon : type.icon;
return count > 0
? Badge(
label: _mainController.dynamicBadgeMode == DynamicBadgeMode.number
? Text(count.toString())
: null,
padding: const EdgeInsets.fromLTRB(6, 0, 6, 0),
child: icon,
)
: icon;
}
Widget userAndSearchVertical(ThemeData theme) {
return Column(

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/models/user/fav_folder.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/main/controller.dart';
@@ -29,7 +30,7 @@ class _MediaPageState extends CommonPageState<MediaPage, MediaController>
@override
void listener() {
if (_mainController.navigationBars[0]['id'] != 2 &&
if (_mainController.navigationBars[0] != NavigationBarType.media &&
_mainController.selectedIndex.value == 0) {
return;
}

View File

@@ -1,98 +0,0 @@
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class NavigationBarSetPage extends StatefulWidget {
const NavigationBarSetPage({super.key});
@override
State<NavigationBarSetPage> createState() => _NavigationbarSetPageState();
}
class _NavigationbarSetPageState extends State<NavigationBarSetPage> {
late List defaultNavTabs;
late List<int> navBarSort;
@override
void initState() {
super.initState();
defaultNavTabs = defaultNavigationBars;
navBarSort = List<int>.from(GStorage.setting
.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2]));
// 对 tabData 进行排序
defaultNavTabs.sort((a, b) {
int indexA = navBarSort.indexOf(a['id']);
int indexB = navBarSort.indexOf(b['id']);
// 如果类型在 sortOrder 中不存在,则放在末尾
if (indexA == -1) indexA = navBarSort.length;
if (indexB == -1) indexB = navBarSort.length;
return indexA.compareTo(indexB);
});
}
void saveEdit() {
List<int> sortedTabbar = defaultNavTabs
.where((i) => navBarSort.contains(i['id']))
.map<int>((i) => i['id'])
.toList();
if (sortedTabbar.isEmpty) {
sortedTabbar = [0, 1, 2];
}
GStorage.setting.put(SettingBoxKey.navBarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = defaultNavTabs.removeAt(oldIndex);
defaultNavTabs.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Navbar编辑'),
actions: [
TextButton(
onPressed: saveEdit,
child: const Text('保存'),
),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
),
children: defaultNavTabs
.map(
(item) => CheckboxListTile(
key: Key(item['label']),
value: navBarSort.contains(item['id']),
onChanged: (bool? newValue) {
int tabTypeId = item['id'];
if (!newValue!) {
navBarSort.remove(tabTypeId);
} else {
navBarSort.add(tabTypeId);
}
setState(() {});
},
title: Text(item['label']),
secondary: const Icon(Icons.drag_indicator_rounded),
),
)
.toList(),
),
);
}
}

View File

@@ -0,0 +1,94 @@
import 'package:PiliPlus/models/common/enum_with_label.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class BarSetPage extends StatefulWidget {
const BarSetPage({super.key});
@override
State<BarSetPage> createState() => _BarSetPageState();
}
class _BarSetPageState extends State<BarSetPage> {
late final List<EnumWithLabel> defaultBars;
late final Map<int, int> barIndex;
late final String key;
late final String title;
@override
void initState() {
super.initState();
Map<String, dynamic> args = Get.arguments;
key = args['key'];
title = args['title'] ?? '';
defaultBars = List<EnumWithLabel>.from(args['defaultBars']);
List<int>? bars = (GStorage.setting.get(key) as List?)?.cast();
if (bars != null) {
barIndex = {for (var (k, v) in bars.indexed) v: k};
// 对 tabData 进行排序
defaultBars.sort((a, b) {
final indexA = barIndex[a.index] ?? barIndex.length;
final indexB = barIndex[b.index] ?? barIndex.length;
return indexA.compareTo(indexB);
});
} else {
barIndex = {for (var (k, v) in defaultBars.indexed) v.index: k};
}
}
void saveEdit() {
List<int> sortedBar = defaultBars
.where((i) => barIndex.containsKey(i.index))
.map((i) => i.index)
.toList();
GStorage.setting.put(key, sortedBar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex -= 1;
defaultBars.insert(newIndex, defaultBars.removeAt(oldIndex));
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('$title编辑'),
actions: [
TextButton(onPressed: saveEdit, child: const Text('保存')),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
child: const Align(
alignment: Alignment.centerRight, child: Text('*长按拖动排序 ')),
),
children: defaultBars
.map((i) => CheckboxListTile(
key: Key(i.label),
value: barIndex.containsKey(i.index),
onChanged: (bool? value) {
if (value!) {
barIndex[i.index] = -1;
} else {
barIndex.remove(i.index);
}
setState(() {});
},
title: Text(i.label),
secondary: const Icon(Icons.drag_indicator_rounded),
))
.toList(),
),
);
}
}

View File

@@ -257,11 +257,11 @@ class _ColorSelectPageState extends State<ColorSelectPage> {
),
IgnorePointer(
child: NavigationBar(
destinations: defaultNavigationBars
destinations: NavigationBarType.values
.map(
(item) => NavigationDestination(
icon: item['icon'],
label: item['label'],
icon: item.icon,
label: item.label,
),
)
.toList(),

View File

@@ -1,96 +0,0 @@
import 'package:PiliPlus/models/common/home_tab_type.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
class TabbarSetPage extends StatefulWidget {
const TabbarSetPage({super.key});
@override
State<TabbarSetPage> createState() => _TabbarSetPageState();
}
class _TabbarSetPageState extends State<TabbarSetPage> {
late List defaultTabs;
late List<String> tabbarSort;
@override
void initState() {
super.initState();
defaultTabs = homeTabsConfig;
tabbarSort = GStorage.tabbarSort;
// 对 tabData 进行排序
defaultTabs.sort((a, b) {
int indexA = tabbarSort.indexOf((a['type'] as HomeTabType).name);
int indexB = tabbarSort.indexOf((b['type'] as HomeTabType).name);
// 如果类型在 sortOrder 中不存在,则放在末尾
if (indexA == -1) indexA = tabbarSort.length;
if (indexB == -1) indexB = tabbarSort.length;
return indexA.compareTo(indexB);
});
}
void saveEdit() {
List<String> sortedTabbar = defaultTabs
.where((i) => tabbarSort.contains((i['type'] as HomeTabType).name))
.map<String>((i) => (i['type'] as HomeTabType).name)
.toList();
GStorage.setting.put(SettingBoxKey.tabbarSort, sortedTabbar);
SmartDialog.showToast('保存成功,下次启动时生效');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = defaultTabs.removeAt(oldIndex);
defaultTabs.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
final listTiles = [
for (int i = 0; i < defaultTabs.length; i++) ...[
CheckboxListTile(
key: Key(defaultTabs[i]['label']),
value:
tabbarSort.contains((defaultTabs[i]['type'] as HomeTabType).name),
onChanged: (bool? newValue) {
String tabTypeId = (defaultTabs[i]['type'] as HomeTabType).name;
if (!newValue!) {
tabbarSort.remove(tabTypeId);
} else {
tabbarSort.add(tabTypeId);
}
setState(() {});
},
title: Text(defaultTabs[i]['label']),
secondary: const Icon(Icons.drag_indicator_rounded),
)
]
];
return Scaffold(
appBar: AppBar(
title: const Text('Tabbar编辑'),
actions: [
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
child: const Align(
alignment: Alignment.centerRight, child: Text('*长按拖动排序 ')),
),
children: listTiles,
),
);
}
}

View File

@@ -14,6 +14,7 @@ import 'package:PiliPlus/models/common/audio_normalization.dart';
import 'package:PiliPlus/models/common/dynamic/dynamic_badge_mode.dart';
import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart';
import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart';
import 'package:PiliPlus/models/common/home_tab_type.dart';
import 'package:PiliPlus/models/common/member/tab_type.dart';
import 'package:PiliPlus/models/common/msg/msg_unread_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
@@ -591,8 +592,8 @@ List<SettingsModel> get styleSettings => [
return SelectDialog<int>(
title: '首页启动页',
value: GStorage.defaultHomePage,
values: defaultNavigationBars.map((e) {
return (e['id'] as int, e['label'] as String);
values: NavigationBarType.values.map((e) {
return (e.index, e.label);
}).toList(),
);
},
@@ -606,7 +607,7 @@ List<SettingsModel> get styleSettings => [
leading: const Icon(Icons.home_outlined),
title: '默认启动页',
getSubtitle: () =>
'当前启动页:${defaultNavigationBars.firstWhere((e) => e['id'] == GStorage.defaultHomePage)['label']}',
'当前启动页:${NavigationBarType.values.firstWhere((e) => e.index == GStorage.defaultHomePage).label}',
),
SettingsModel(
settingsType: SettingsType.normal,
@@ -699,15 +700,24 @@ List<SettingsModel> get styleSettings => [
),
SettingsModel(
settingsType: SettingsType.normal,
onTap: (setState) => Get.toNamed('/tabbarSetting'),
onTap: (setState) => Get.toNamed('/barSetting', arguments: {
'key': SettingBoxKey.tabBarSort,
'defaultBars': HomeTabType.values,
'title': '首页标签页'
}),
title: '首页标签页',
subtitle: '删除或调换首页标签页',
leading: const Icon(Icons.toc_outlined),
),
SettingsModel(
settingsType: SettingsType.normal,
onTap: (setState) => Get.toNamed('/navbarSetting'),
onTap: (setState) => Get.toNamed('/barSetting', arguments: {
'key': SettingBoxKey.navBarSort,
'defaultBars': NavigationBarType.values,
'title': 'Navbar'
}),
title: 'Navbar编辑',
subtitle: '删除或调换Navbar',
leading: const Icon(Icons.toc_outlined),
),
if (Platform.isAndroid)

View File

@@ -1,121 +0,0 @@
import 'package:PiliPlus/models/common/video/audio_quality.dart';
import 'package:PiliPlus/models/common/video/video_decode_type.dart';
import 'package:PiliPlus/models/common/video/video_quality.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
class SetSelectItem extends StatefulWidget {
final String? title;
final String? subTitle;
final String? setKey;
const SetSelectItem({
this.title,
this.subTitle,
this.setKey,
super.key,
});
@override
State<SetSelectItem> createState() => _SetSelectItemState();
}
class _SetSelectItemState extends State<SetSelectItem> {
late dynamic currentVal;
late int currentIndex;
late List menus;
late List<PopupMenuEntry> popMenuItems;
Box get setting => GStorage.setting;
@override
void initState() {
super.initState();
late String defaultVal;
switch (widget.setKey) {
case 'defaultVideoQa':
defaultVal = VideoQuality.values.last.description;
List<VideoQuality> list = menus = VideoQuality.values.reversed.toList();
currentVal = setting.get(widget.setKey, defaultValue: defaultVal);
currentIndex =
list.firstWhere((i) => i.description == currentVal).index;
popMenuItems = [
for (var i in list) ...[
PopupMenuItem(
value: i.code,
child: Text(i.description),
)
]
];
break;
case 'defaultAudioQa':
defaultVal = AudioQuality.values.last.description;
List<AudioQuality> list = menus = AudioQuality.values.reversed.toList();
currentVal = setting.get(widget.setKey, defaultValue: defaultVal);
currentIndex =
list.firstWhere((i) => i.description == currentVal).index;
popMenuItems = [
for (var i in list) ...[
PopupMenuItem(
value: i.index,
child: Text(i.description),
),
]
];
break;
case 'defaultDecode':
defaultVal = VideoDecodeFormatType.values[0].description;
currentVal = setting.get(widget.setKey, defaultValue: defaultVal);
List<VideoDecodeFormatType> list = menus = VideoDecodeFormatType.values;
currentIndex =
list.firstWhere((i) => i.description == currentVal).index;
popMenuItems = [
for (var i in list) ...[
PopupMenuItem(
value: i.index,
child: Text(i.description),
),
]
];
break;
case 'defaultVideoSpeed':
defaultVal = '1.0';
currentVal = setting.get(widget.setKey, defaultValue: defaultVal);
break;
}
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
TextStyle subTitleStyle =
theme.textTheme.labelMedium!.copyWith(color: theme.colorScheme.outline);
return ListTile(
onTap: () {},
title: Text(widget.title!),
subtitle: Text(
'当前${widget.title!} $currentVal',
style: subTitleStyle,
),
trailing: PopupMenuButton(
initialValue: currentIndex,
icon: const Icon(
Icons.arrow_forward_rounded,
size: 22,
),
onSelected: (item) {
currentVal = menus.firstWhere((e) => e.code == item).first;
setState(() {});
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry>[...popMenuItems],
),
);
}
}

View File

@@ -34,11 +34,10 @@ import 'package:PiliPlus/pages/search/view.dart';
import 'package:PiliPlus/pages/search_result/view.dart';
import 'package:PiliPlus/pages/search_trending/view.dart';
import 'package:PiliPlus/pages/setting/extra_setting.dart';
import 'package:PiliPlus/pages/setting/navigation_bar_set.dart';
import 'package:PiliPlus/pages/setting/pages/bar_set.dart';
import 'package:PiliPlus/pages/setting/pages/color_select.dart';
import 'package:PiliPlus/pages/setting/pages/display_mode.dart';
import 'package:PiliPlus/pages/setting/pages/font_size_select.dart';
import 'package:PiliPlus/pages/setting/pages/home_tabbar_set.dart';
import 'package:PiliPlus/pages/setting/pages/logs.dart';
import 'package:PiliPlus/pages/setting/pages/play_speed_set.dart';
import 'package:PiliPlus/pages/setting/play_setting.dart';
@@ -116,8 +115,6 @@ class Routes {
//
CustomGetPage(name: '/blackListPage', page: () => const BlackListPage()),
CustomGetPage(name: '/colorSetting', page: () => const ColorSelectPage()),
// 首页tabbar
CustomGetPage(name: '/tabbarSetting', page: () => const TabbarSetPage()),
CustomGetPage(
name: '/fontSizeSetting', page: () => const FontSizeSelectPage()),
// 屏幕帧率
@@ -165,9 +162,6 @@ class Routes {
CustomGetPage(name: '/sponsorBlock', page: () => const SponsorBlockPage()),
CustomGetPage(name: '/createFav', page: () => const CreateFavPage()),
CustomGetPage(name: '/editProfile', page: () => const EditProfilePage()),
// navigation bar
CustomGetPage(
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
CustomGetPage(
name: '/settingsSearch', page: () => const SettingsSearchPage()),
CustomGetPage(
@@ -176,6 +170,7 @@ class Routes {
name: '/searchTrending', page: () => const SearchTrendingPage()),
CustomGetPage(name: '/dynTopic', page: () => const DynTopicPage()),
CustomGetPage(name: '/articleList', page: () => const ArticleListPage()),
CustomGetPage(name: '/barSetting', page: () => const BarSetPage()),
];
}

View File

@@ -56,9 +56,9 @@ class GStorage {
),
);
static List<String> get tabbarSort =>
List<String>.from(setting.get(SettingBoxKey.tabbarSort) ??
HomeTabType.values.map((item) => item.name).toList());
static List<int> get tabbarSort =>
List<int>.from(setting.get(SettingBoxKey.tabBarSort) ??
HomeTabType.values.map((item) => item.index).toList());
static List<Pair<SegmentType, SkipType>> get blockSettings {
List<int> list = List<int>.from(setting.get(SettingBoxKey.blockSettings) ??
@@ -846,7 +846,7 @@ class SettingBoxKey {
enableMYBar = 'enableMYBar',
hideSearchBar = 'hideSearchBar', // 收起顶栏
hideTabBar = 'hideTabBar', // 收起底栏
tabbarSort = 'tabbarSort', // 首页tabbar
tabBarSort = 'tabBarSort', // 首页tabbar
dynamicBadgeMode = 'dynamicBadgeMode',
msgBadgeMode = 'msgBadgeMode',
msgUnReadTypeV2 = 'msgUnReadTypeV2',