Merge branch 'main' into fix

This commit is contained in:
guozhigq
2024-06-10 13:17:54 +08:00
47 changed files with 706 additions and 231 deletions

BIN
assets/images/coin.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -23,7 +23,7 @@ PODS:
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
- gt3_flutter_plugin (0.0.8):
- gt3_flutter_plugin (0.0.9):
- Flutter
- GT3Captcha-iOS
- GT3Captcha-iOS (0.15.8.3)
@ -171,13 +171,13 @@ SPEC CHECKSUMS:
flutter_volume_controller: e4d5832f08008180f76e30faf671ffd5a425e529
fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: bfa1f26e9a09dc00401514be5ed437f964cabf23
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
saver_gallery: 2b4e584106fde2407ab51560f3851564963e6b78
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625

View File

@ -34,7 +34,7 @@ class HttpError extends StatelessWidget {
fn!();
},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith((states) {
backgroundColor: WidgetStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.primary.withAlpha(20);
}),
),

View File

@ -305,7 +305,7 @@ class VideoContent extends StatelessWidget {
if (source == 'later') ...[
IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => onPressedFn?.call(),
icon: Icon(

View File

@ -189,7 +189,7 @@ class Api {
'https://s.search.bilibili.com/main/suggest';
// 分类搜索
static const String searchByType = '/x/web-interface/search/type';
static const String searchByType = '/x/web-interface/wbi/search/type';
// 记录视频播放进度
// https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/docs/video/report.md

View File

@ -75,6 +75,7 @@ class SearchHttp {
required page,
String? order,
int? duration,
int? tids,
}) async {
var reqData = {
'search_type': searchType.type,
@ -84,6 +85,7 @@ class SearchHttp {
'page': page,
if (order != null) 'order': order,
if (duration != null) 'duration': duration,
if (tids != null && tids != -1) 'tids': tids,
};
var res = await Request().get(Api.searchByType, data: reqData);
if (res.data['code'] == 0 && res.data['data']['numPages'] > 0) {

View File

@ -17,7 +17,6 @@ import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/router/app_pages.dart';
import 'package:pilipala/pages/main/view.dart';
import 'package:pilipala/services/disable_battery_opt.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/app_scheme.dart';
import 'package:pilipala/utils/data.dart';
@ -66,7 +65,6 @@ void main() async {
}
PiliSchame.init();
DisableBatteryOpt();
}
class MyApp extends StatelessWidget {

View File

@ -0,0 +1,93 @@
// 操作类型的枚举值:点赞 不喜欢 收藏 投币 稍后再看 下载封面 后台播放 听视频 分享 下载视频
import 'package:flutter/material.dart';
import 'package:get/get.dart';
enum ActionType {
like,
coin,
collect,
watchLater,
share,
dislike,
downloadCover,
copyLink,
// backgroundPlay,
// listenVideo,
// downloadVideo,
}
extension ActionTypeExtension on ActionType {
String get value => [
'like',
'coin',
'collect',
'watchLater',
'share',
'dislike',
'downloadCover',
'copyLink',
// 'backgroundPlay',
// 'listenVideo',
// 'downloadVideo',
][index];
String get label => [
'点赞视频',
'投币',
'收藏视频',
'稍后再看',
'视频分享',
'不喜欢',
'下载封面',
'复制链接',
// '后台播放',
// '听视频',
// '下载视频',
][index];
}
List<Map> actionMenuConfig = [
{
'icon': const Icon(Icons.thumb_up_alt_outlined),
'label': '点赞视频',
'value': ActionType.like,
},
{
'icon': Image.asset(
'assets/images/coin.png',
width: 26,
color: IconTheme.of(Get.context!).color!.withOpacity(0.65),
),
'label': '投币',
'value': ActionType.coin,
},
{
'icon': const Icon(Icons.star_border),
'label': '收藏视频',
'value': ActionType.collect,
},
{
'icon': const Icon(Icons.watch_later_outlined),
'label': '稍后再看',
'value': ActionType.watchLater,
},
{
'icon': const Icon(Icons.share),
'label': '视频分享',
'value': ActionType.share,
},
{
'icon': const Icon(Icons.thumb_down_alt_outlined),
'label': '不喜欢',
'value': ActionType.dislike,
},
{
'icon': const Icon(Icons.image_outlined),
'label': '下载封面',
'value': ActionType.downloadCover,
},
{
'icon': const Icon(Icons.link_outlined),
'label': '复制链接',
'value': ActionType.copyLink,
},
];

View File

@ -232,11 +232,11 @@ class _BangumiInfoState extends State<BangumiInfo> {
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
EdgeInsets.zero),
padding:
WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
WidgetStateProperty.resolveWith(
(Set<WidgetState> states) {
return t.colorScheme.primaryContainer
.withOpacity(0.7);
}),

View File

@ -139,7 +139,7 @@ class _BangumiPanelState extends State<BangumiPanel> {
height: 34,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
widget.bangumiIntroController?.bottomSheetController =

View File

@ -82,7 +82,7 @@ class AuthorPanel extends StatelessWidget {
height: 32,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showModalBottomSheet(

View File

@ -217,7 +217,7 @@ class VideoContent extends StatelessWidget {
bottom: -4,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
showDialog(

View File

@ -217,11 +217,10 @@ class HistoryItem extends StatelessWidget {
curve: Curves.easeInOut,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
padding: WidgetStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty
.resolveWith(
WidgetStateProperty.resolveWith(
(states) {
return Colors.white
.withOpacity(0.8);

View File

@ -278,8 +278,8 @@ class DefaultUser extends StatelessWidget {
height: 38,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.resolveWith((states) {
padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor: WidgetStateProperty.resolveWith((states) {
return Theme.of(context).colorScheme.onInverseSurface;
}),
),
@ -371,8 +371,8 @@ class CustomChip extends StatelessWidget {
),
backgroundColor: secondaryContainer,
selectedColor: secondaryContainer,
color: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) => secondaryContainer.withAlpha(200)),
color: WidgetStateProperty.resolveWith<Color>(
(Set<WidgetState> states) => secondaryContainer.withAlpha(200)),
padding: const EdgeInsets.fromLTRB(7, 1, 7, 1),
label: Text(label, style: chipTextStyle),
onPressed: () => onTap(),

View File

@ -124,7 +124,7 @@ class _BottomControlState extends State<BottomControl> {
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
bool canUsePiP = false;

View File

@ -31,7 +31,7 @@ class LoginPageController extends GetxController {
// 倒计时60s
RxInt seconds = 60.obs;
late Timer timer;
Timer? timer;
RxBool smsCodeSendStatus = false.obs;
// 默认密码登录
@ -43,7 +43,7 @@ class LoginPageController extends GetxController {
late int webSmsCode;
RxInt validSeconds = 180.obs;
late Timer validTimer;
Timer? validTimer;
late String qrcodeKey;
// 监听pageView切换
@ -329,7 +329,7 @@ class LoginPageController extends GetxController {
var res = await LoginHttp.queryWebQrcodeStatus(qrcodeKey);
if (res['status']) {
await LoginUtils.confirmLogin('', null);
validTimer.cancel();
validTimer?.cancel();
Get.back();
}
}

View File

@ -15,8 +15,10 @@ class _LoginPageState extends State<LoginPage> {
final LoginPageController _loginPageCtr = Get.put(LoginPageController());
@override
void initState() {
super.initState();
void dispose() {
_loginPageCtr.validTimer?.cancel();
_loginPageCtr.timer?.cancel();
super.dispose();
}
@override
@ -51,7 +53,7 @@ class _LoginPageState extends State<LoginPage> {
},
);
},
icon: const Icon(Icons.language),
icon: const Icon(Icons.language, size: 20),
),
IconButton(
tooltip: '二维码登录',
@ -90,7 +92,7 @@ class _LoginPageState extends State<LoginPage> {
Map data = snapshot.data as Map;
return QrImageView(
data: data['data']['url'],
backgroundColor: Colors.transparent,
backgroundColor: Colors.white,
);
} else {
return const Center(
@ -131,9 +133,11 @@ class _LoginPageState extends State<LoginPage> {
);
});
},
);
).then((value) {
_loginPageCtr.validTimer!.cancel();
});
},
icon: const Icon(Icons.qr_code),
icon: const Icon(Icons.qr_code, size: 20),
),
const SizedBox(width: 22),
],
@ -164,17 +168,9 @@ class _LoginPageState extends State<LoginPage> {
fontSize: 34,
fontWeight: FontWeight.w500),
),
Row(
children: [
Text(
'请使用您的 BiliBili 账号登录。',
style: Theme.of(context).textTheme.titleSmall!,
),
GestureDetector(
onTap: () {},
child: const Icon(Icons.info_outline, size: 16),
)
],
Text(
'请使用您的 BiliBili 账号登录。',
style: Theme.of(context).textTheme.titleSmall!,
),
Container(
margin: const EdgeInsets.only(top: 38, bottom: 15),
@ -253,8 +249,7 @@ class _LoginPageState extends State<LoginPage> {
IconButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
WidgetStateProperty.resolveWith((states) {
return Theme.of(context)
.colorScheme
.primary
@ -344,8 +339,7 @@ class _LoginPageState extends State<LoginPage> {
IconButton(
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
WidgetStateProperty.resolveWith((states) {
return Theme.of(context)
.colorScheme
.primary

View File

@ -178,10 +178,10 @@ class _MediaPageState extends State<MediaPage>
child: Center(
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
padding: WidgetStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
WidgetStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme

View File

@ -90,9 +90,8 @@ class _MemberPageState extends State<MemberPage>
() => Text(
_memberController.memberInfo.value.name ?? '',
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onBackground,
color:
Theme.of(context).colorScheme.onSurface,
fontSize: 14),
),
),

View File

@ -33,8 +33,7 @@ class MineController extends GetxController {
onLogin() async {
if (!userLogin.value) {
// RoutePush.loginPush();
Get.toNamed('/loginPage');
Get.toNamed('/loginPage', preventDuplicates: false);
} else {
int mid = userInfo.value.mid!;
String face = userInfo.value.face!;

View File

@ -5,21 +5,25 @@ class SearchText extends StatelessWidget {
final Function? onSelect;
final int? searchTextIdx;
final Function? onLongSelect;
final bool isSelect;
const SearchText({
super.key,
this.searchText,
this.onSelect,
this.searchTextIdx,
this.onLongSelect,
this.isSelect = false,
});
@override
Widget build(BuildContext context) {
return Material(
color: Theme.of(context)
.colorScheme
.surfaceContainerHighest
.withOpacity(0.5),
color: isSelect
? Theme.of(context).colorScheme.primaryContainer
: Theme.of(context)
.colorScheme
.surfaceContainerHighest
.withOpacity(0.5),
borderRadius: BorderRadius.circular(6),
child: Padding(
padding: EdgeInsets.zero,
@ -37,7 +41,10 @@ class SearchText extends StatelessWidget {
child: Text(
searchText!,
style: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant),
color: isSelect
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
),
),

View File

@ -16,14 +16,18 @@ class SearchPanelController extends GetxController {
RxString order = ''.obs;
// 视频时长筛选 仅用于搜索视频
RxInt duration = 0.obs;
// 视频分区筛选 仅用于搜索视频 -1时不传
RxInt tids = (-1).obs;
Future onSearch({type = 'init'}) async {
var result = await SearchHttp.searchByType(
searchType: searchType!,
keyword: keyword!,
page: page.value,
order: searchType!.type != 'video' ? null : order.value,
duration: searchType!.type != 'video' ? null : duration.value);
searchType: searchType!,
keyword: keyword!,
page: page.value,
order: searchType!.type != 'video' ? null : order.value,
duration: searchType!.type != 'video' ? null : duration.value,
tids: searchType!.type != 'video' ? null : tids.value,
);
if (result['status']) {
if (type == 'onRefresh') {
resultList.value = result['data'].list;

View File

@ -3,6 +3,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/search/widgets/search_text.dart';
import 'package:pilipala/pages/search_panel/index.dart';
class SearchVideoPanel extends StatelessWidget {
@ -92,9 +93,9 @@ class SearchVideoPanel extends StatelessWidget {
height: 32,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => controller.onShowFilterDialog(ctr),
onPressed: () => controller.onShowFilterSheet(ctr),
icon: Icon(
Icons.filter_list_outlined,
size: 18,
@ -165,7 +166,33 @@ class VideoPanelController extends GetxController {
{'label': '30-60分钟', 'value': 3},
{'label': '60分钟+', 'value': 4},
];
List<Map<String, dynamic>> partFiltersList = [
{'label': '全部', 'value': -1},
{'label': '动画', 'value': 1},
{'label': '番剧', 'value': 13},
{'label': '国创', 'value': 167},
{'label': '音乐', 'value': 3},
{'label': '舞蹈', 'value': 129},
{'label': '游戏', 'value': 4},
{'label': '知识', 'value': 36},
{'label': '科技', 'value': 188},
{'label': '运动', 'value': 234},
{'label': '汽车', 'value': 223},
{'label': '生活', 'value': 160},
{'label': '美食', 'value': 211},
{'label': '动物', 'value': 217},
{'label': '鬼畜', 'value': 119},
{'label': '时尚', 'value': 155},
{'label': '资讯', 'value': 202},
{'label': '娱乐', 'value': 5},
{'label': '影视', 'value': 181},
{'label': '记录', 'value': 177},
{'label': '电影', 'value': 23},
{'label': '电视', 'value': 11},
];
RxInt currentTimeFilterval = 0.obs;
RxInt currentPartFilterval = (-1).obs;
@override
void onInit() {
@ -219,4 +246,100 @@ class VideoPanelController extends GetxController {
},
);
}
onShowFilterSheet(searchPanelCtr) {
showModalBottomSheet(
context: Get.context!,
builder: (context) {
return StatefulBuilder(
builder: (context, StateSetter setState) {
return Container(
color: Theme.of(Get.context!).colorScheme.surface,
padding: const EdgeInsets.only(top: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ListTile(
title: Text('内容时长'),
),
Padding(
padding: const EdgeInsets.only(
left: 14,
right: 14,
bottom: 14,
),
child: Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (var i in timeFiltersList)
Obx(
() => SearchText(
searchText: i['label'],
searchTextIdx: i['value'],
isSelect:
currentTimeFilterval.value == i['value'],
onSelect: (value) async {
currentTimeFilterval.value = i['value'];
setState(() {});
SmartDialog.showToast("${i['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(
tag: 'video${searchPanelCtr.keyword!}');
ctr.duration.value = i['value'];
Get.back();
SmartDialog.showLoading(msg: '获取中');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongSelect: (value) => {},
),
)
],
),
),
const ListTile(
title: Text('内容分区'),
),
Padding(
padding: const EdgeInsets.only(left: 14, right: 14),
child: Wrap(
spacing: 10,
runSpacing: 10,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (var i in partFiltersList)
SearchText(
searchText: i['label'],
searchTextIdx: i['value'],
isSelect: currentPartFilterval.value == i['value'],
onSelect: (value) async {
currentPartFilterval.value = i['value'];
setState(() {});
SmartDialog.showToast("${i['label']}」的筛选结果");
SearchPanelController ctr =
Get.find<SearchPanelController>(
tag: 'video${searchPanelCtr.keyword!}');
ctr.tids.value = i['value'];
Get.back();
SmartDialog.showLoading(msg: '获取中');
await ctr.onRefresh();
SmartDialog.dismiss();
},
onLongSelect: (value) => {},
)
],
),
)
],
),
);
},
);
},
);
}
}

View File

@ -240,10 +240,10 @@ class _ExtraSettingState extends State<ExtraSetting> {
alignment: Alignment.centerRight,
scale: 0.8,
child: Switch(
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.isNotEmpty &&
states.first == MaterialState.selected) {
states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.

View File

@ -0,0 +1,102 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/models/common/action_type.dart';
import 'package:pilipala/utils/global_data.dart';
import '../../../utils/storage.dart';
class ActionMenuSetPage extends StatefulWidget {
const ActionMenuSetPage({super.key});
@override
State<ActionMenuSetPage> createState() => _ActionMenuSetPageState();
}
class _ActionMenuSetPageState extends State<ActionMenuSetPage> {
Box setting = GStrorage.setting;
late List<String> actionTypeSort;
late List<Map> allLabels;
@override
void initState() {
super.initState();
actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
allLabels = actionMenuConfig;
allLabels.sort((a, b) {
int indexA = actionTypeSort.indexOf((a['value'] as ActionType).value);
int indexB = actionTypeSort.indexOf((b['value'] as ActionType).value);
if (indexA == -1) indexA = actionTypeSort.length;
if (indexB == -1) indexB = actionTypeSort.length;
return indexA.compareTo(indexB);
});
}
void saveEdit() {
List<String> sortedTabbar = allLabels
.where((i) => actionTypeSort.contains((i['value'] as ActionType).value))
.map<String>((i) => (i['value'] as ActionType).value)
.toList();
setting.put(SettingBoxKey.actionTypeSort, sortedTabbar);
GlobalData().actionTypeSort = sortedTabbar;
SmartDialog.showToast('操作成功');
}
void onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final tabsItem = allLabels.removeAt(oldIndex);
allLabels.insert(newIndex, tabsItem);
});
}
@override
Widget build(BuildContext context) {
final listTiles = [
for (int i = 0; i < allLabels.length; i++) ...[
CheckboxListTile(
key: Key((allLabels[i]['value'] as ActionType).value),
value: actionTypeSort
.contains((allLabels[i]['value'] as ActionType).value),
onChanged: (bool? newValue) {
String actionTypeId = (allLabels[i]['value'] as ActionType).value;
if (!newValue!) {
actionTypeSort.remove(actionTypeId);
} else {
actionTypeSort.add(actionTypeId);
}
setState(() {});
},
title: Row(
children: [
allLabels[i]['icon'],
const SizedBox(width: 8),
Text(allLabels[i]['label']),
],
),
secondary: const Icon(Icons.drag_indicator_rounded),
)
]
];
return Scaffold(
appBar: AppBar(
title: const Text('视频操作菜单'),
actions: [
TextButton(onPressed: () => saveEdit(), child: const Text('保存')),
const SizedBox(width: 12)
],
),
body: ReorderableListView(
onReorder: onReorder,
physics: const NeverScrollableScrollPhysics(),
footer: SizedBox(
height: MediaQuery.of(context).padding.bottom + 30,
),
children: listTiles,
),
);
}
}

View File

@ -69,10 +69,10 @@ class _StyleSettingState extends State<StyleSetting> {
alignment: Alignment.centerRight,
scale: 0.8,
child: Switch(
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
thumbIcon: WidgetStateProperty.resolveWith<Icon?>(
(Set<WidgetState> states) {
if (states.isNotEmpty &&
states.first == MaterialState.selected) {
states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.
@ -289,6 +289,11 @@ class _StyleSettingState extends State<StyleSetting> {
onTap: () => Get.toNamed('/navbarSetting'),
title: Text('底部导航栏设置', style: titleStyle),
),
// ListTile(
// dense: false,
// onTap: () => Get.toNamed('/actionMenuSet'),
// title: Text('操作菜单设置', style: titleStyle),
// ),
if (Platform.isAndroid)
ListTile(
dense: false,

View File

@ -70,9 +70,9 @@ class _SetSwitchItemState extends State<SetSwitchItem> {
alignment: Alignment.centerRight, // 缩放Switch的大小后保持右侧对齐, 避免右侧空隙过大
scale: 0.8,
child: Switch(
thumbIcon: MaterialStateProperty.resolveWith<Icon?>(
(Set<MaterialState> states) {
if (states.isNotEmpty && states.first == MaterialState.selected) {
thumbIcon:
WidgetStateProperty.resolveWith<Icon?>((Set<WidgetState> states) {
if (states.isNotEmpty && states.first == WidgetState.selected) {
return const Icon(Icons.done);
}
return null; // All other states will use the default thumbIcon.

View File

@ -53,18 +53,25 @@ class _SubPageState extends State<SubPage> {
if (snapshot.connectionState == ConnectionState.done) {
Map? data = snapshot.data;
if (data != null && data['status']) {
return Obx(
() => ListView.builder(
controller: scrollController,
itemCount: _subController.subFolderData.value.list!.length,
itemBuilder: (context, index) {
return SubItem(
subFolderItem:
_subController.subFolderData.value.list![index],
cancelSub: _subController.cancelSub);
},
),
);
if (_subController.subFolderData.value.list!.isNotEmpty) {
return Obx(
() => ListView.builder(
controller: scrollController,
itemCount: _subController.subFolderData.value.list!.length,
itemBuilder: (context, index) {
return SubItem(
subFolderItem:
_subController.subFolderData.value.list![index],
cancelSub: _subController.cancelSub);
},
),
);
} else {
return const CustomScrollView(
physics: NeverScrollableScrollPhysics(),
slivers: [HttpError(errMsg: '', btnText: '没有数据', fn: null)],
);
}
} else {
return CustomScrollView(
physics: const NeverScrollableScrollPhysics(),

View File

@ -114,16 +114,15 @@ class VideoContent extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SizedBox(
height: 35,
width: 35,
child: IconButton(
onPressed: () => cancelSub?.call(subFolderItem),
style: TextButton.styleFrom(
foregroundColor: Theme.of(context).colorScheme.outline,
padding: const EdgeInsets.fromLTRB(0, 0, 0, 0),
),
icon: const Icon(Icons.delete_outline, size: 18),
IconButton(
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => cancelSub?.call(subFolderItem),
icon: Icon(
Icons.clear_outlined,
color: Theme.of(context).colorScheme.outline,
size: 18,
),
)
],

View File

@ -38,6 +38,8 @@ class VideoIntroController extends GetxController {
RxBool hasCoin = false.obs;
// 是否收藏
RxBool hasFav = false.obs;
// 是否不喜欢
RxBool hasDisLike = false.obs;
Box userInfoCache = GStrorage.userInfo;
bool userLogin = false;
Rx<FavFolderData> favFolderData = FavFolderData().obs;
@ -153,36 +155,16 @@ class VideoIntroController extends GetxController {
SmartDialog.showToast('🙏 UP已经收到了');
return false;
}
SmartDialog.show(
useSystem: true,
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('提示'),
content: const Text('一键三连 给UP送温暖'),
actions: [
TextButton(
onPressed: () => SmartDialog.dismiss(),
child: const Text('点错了')),
TextButton(
onPressed: () async {
var result = await VideoHttp.oneThree(bvid: bvid);
if (result['status']) {
hasLike.value = result["data"]["like"];
hasCoin.value = result["data"]["coin"];
hasFav.value = result["data"]["fav"];
SmartDialog.showToast('三连成功 🎉');
} else {
SmartDialog.showToast(result['msg']);
}
SmartDialog.dismiss();
},
child: const Text('确认'),
)
],
);
},
);
var result = await VideoHttp.oneThree(bvid: bvid);
print('🤣🦴:${result["data"]}');
if (result['status']) {
hasLike.value = result["data"]["like"];
hasCoin.value = result["data"]["coin"];
hasFav.value = result["data"]["fav"];
SmartDialog.showToast('三连成功');
} else {
SmartDialog.showToast(result['msg']);
}
}
// (取消)点赞
@ -193,9 +175,8 @@ class VideoIntroController extends GetxController {
}
var result = await VideoHttp.likeVideo(bvid: bvid, type: !hasLike.value);
if (result['status']) {
// hasLike.value = result["data"] == 1 ? true : false;
if (!hasLike.value) {
SmartDialog.showToast('点赞成功 👍');
SmartDialog.showToast('点赞成功');
hasLike.value = true;
videoDetail.value.stat!.like = videoDetail.value.stat!.like! + 1;
} else if (hasLike.value) {
@ -215,6 +196,10 @@ class VideoIntroController extends GetxController {
SmartDialog.showToast('账号未登录');
return;
}
if (hasCoin.value) {
SmartDialog.showToast('已投过币了');
return;
}
showDialog(
context: Get.context!,
builder: (context) {
@ -236,7 +221,7 @@ class VideoIntroController extends GetxController {
var res = await VideoHttp.coinVideo(
bvid: bvid, multiply: _tempThemeValue);
if (res['status']) {
SmartDialog.showToast('投币成功 👏');
SmartDialog.showToast('投币成功');
hasCoin.value = true;
videoDetail.value.stat!.coin =
videoDetail.value.stat!.coin! + _tempThemeValue;
@ -269,7 +254,7 @@ class VideoIntroController extends GetxController {
if (result['status']) {
// 重新获取收藏状态
await queryHasFavVideo();
SmartDialog.showToast('操作成功');
SmartDialog.showToast('操作成功');
} else {
SmartDialog.showToast(result['msg']);
}
@ -299,7 +284,7 @@ class VideoIntroController extends GetxController {
Get.back();
// 重新获取收藏状态
await queryHasFavVideo();
SmartDialog.showToast('操作成功');
SmartDialog.showToast('操作成功');
} else {
SmartDialog.showToast(result['msg']);
}

View File

@ -1,7 +1,7 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
@ -16,6 +16,7 @@ import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/introduction/controller.dart';
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/global_data.dart';
import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart';
import '../../../../http/user.dart';
@ -147,13 +148,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
RxBool isExpand = false.obs;
late ExpandableController _expandableCtr;
void Function()? handleState(Future Function() action) {
// 一键三连动画
late AnimationController _controller;
late Animation<double> _scaleTransition;
final RxDouble _progress = 0.0.obs;
void Function()? handleState(Future<dynamic> Function() action) {
return isProcessing
? null
: () async {
setState(() => isProcessing = true);
await action();
setState(() => isProcessing = false);
isProcessing = true;
await action.call();
isProcessing = false;
};
}
@ -170,6 +176,26 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
follower = Utils.numFormat(videoIntroController.userStat['follower']);
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
_expandableCtr = ExpandableController(initialExpanded: false);
/// 一键三连动画
_controller = AnimationController(
duration: const Duration(milliseconds: 1500),
reverseDuration: const Duration(milliseconds: 300),
vsync: this,
);
_scaleTransition = Tween<double>(begin: 0.5, end: 1.5).animate(_controller)
..addListener(() async {
_progress.value =
double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3));
if (_progress.value == 1) {
if (_controller.status == AnimationStatus.completed) {
await videoIntroController.actionOneThree();
}
_progress.value = 0;
_scaleTransition.removeListener(() {});
_controller.stop();
}
});
}
// 收藏
@ -250,6 +276,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
@override
void dispose() {
_expandableCtr.dispose();
_controller.dispose();
_scaleTransition.removeListener(() {});
super.dispose();
}
@ -539,62 +567,166 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
}
Widget actionGrid(BuildContext context, videoIntroController) {
final actionTypeSort = GlobalData().actionTypeSort;
Widget progressWidget(progress) {
return SizedBox(
width: 33,
height: 33,
child: CircularProgressIndicator(
value: progress.value,
strokeWidth: 2,
),
);
}
Map<String, Widget> menuListWidgets = {
'like': Obx(
() {
bool likeStatus = videoIntroController.hasLike.value;
ColorScheme colorScheme = Theme.of(context).colorScheme;
return Stack(
children: [
Positioned(child: progressWidget(_progress), top: 15, left: 24),
InkWell(
onTapDown: (details) {
feedBack();
// if (videoIntroController.userInfo == null) {
// SmartDialog.showToast('账号未登录');
// return;
// }
_controller.forward();
},
onTapUp: (TapUpDetails details) {
if (_progress.value == 0) {
feedBack();
EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 200), () {
videoIntroController.actionLikeVideo();
});
}
_controller.reverse();
},
borderRadius: StyleString.mdRadius,
child: SizedBox(
width: (Get.size.width - 24) / 5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder:
(Widget child, Animation<double> animation) {
return ScaleTransition(
scale: animation, child: child);
},
child: Icon(
key: ValueKey<bool>(likeStatus),
likeStatus
? Icons.thumb_up
: Icons.thumb_up_alt_outlined,
color: likeStatus
? colorScheme.primary
: colorScheme.outline,
),
),
const SizedBox(height: 6),
Text(
widget.videoDetail!.stat!.like!.toString(),
style: TextStyle(
color: likeStatus ? colorScheme.primary : null,
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
),
)
],
),
),
),
],
);
},
),
'coin': Obx(
() => Stack(
children: [
Positioned(child: progressWidget(_progress), top: 15, left: 24),
ActionItem(
icon: Image.asset('assets/images/coin.png', width: 30),
onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value,
text: widget.videoDetail!.stat!.coin!.toString(),
),
],
),
),
'collect': Obx(
() => Stack(
children: [
Positioned(child: progressWidget(_progress), top: 15, left: 24),
ActionItem(
icon: const Icon(Icons.star_border),
selectIcon: const Icon(Icons.star),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
text: widget.videoDetail!.stat!.favorite!.toString(),
),
],
),
),
'watchLater': ActionItem(
icon: const Icon(Icons.watch_later_outlined),
onTap: () async {
final res =
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
SmartDialog.showToast(res['msg']);
},
selectStatus: false,
text: '稍后看',
),
'share': ActionItem(
icon: const Icon(Icons.share),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
text: '分享',
),
'dislike': Obx(
() => ActionItem(
icon: const Icon(Icons.thumb_down_alt_outlined),
selectIcon: const Icon(Icons.thumb_down),
onTap: () {},
selectStatus: videoIntroController.hasDisLike.value,
text: '不喜欢',
),
),
'downloadCover': ActionItem(
icon: const Icon(Icons.image_outlined),
onTap: () {},
selectStatus: false,
text: '下载封面',
),
'copyLink': ActionItem(
icon: const Icon(Icons.link_outlined),
onTap: () {},
selectStatus: false,
text: '复制链接',
),
};
final List<Widget> list = [];
for (var i = 0; i < actionTypeSort.length; i++) {
list.add(menuListWidgets[actionTypeSort[i]]!);
}
return LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Container(
margin: const EdgeInsets.only(top: 6, bottom: 4),
height: constraints.maxWidth / 5 * 0.8,
child: GridView.count(
physics: const NeverScrollableScrollPhysics(),
primary: false,
padding: EdgeInsets.zero,
crossAxisCount: 5,
childAspectRatio: 1.25,
children: <Widget>[
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.thumbsUp),
selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp),
onTap: handleState(videoIntroController.actionLikeVideo),
selectStatus: videoIntroController.hasLike.value,
text: widget.videoDetail!.stat!.like!.toString()),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.b),
selectIcon: const Icon(FontAwesomeIcons.b),
onTap: handleState(videoIntroController.actionCoinVideo),
selectStatus: videoIntroController.hasCoin.value,
text: widget.videoDetail!.stat!.coin!.toString(),
),
),
Obx(
() => ActionItem(
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
text: widget.videoDetail!.stat!.favorite!.toString(),
),
),
ActionItem(
icon: const Icon(FontAwesomeIcons.clock),
onTap: () async {
final res =
await UserHttp.toViewLater(bvid: widget.videoDetail!.bvid);
SmartDialog.showToast(res['msg']);
},
selectStatus: false,
text: '稍后看',
),
ActionItem(
icon: const Icon(FontAwesomeIcons.shareFromSquare),
onTap: () => videoIntroController.actionShareVideo(),
selectStatus: false,
text: '分享',
),
],
height: constraints.maxWidth / 5,
child: ListView(
scrollDirection: Axis.horizontal,
children: list,
),
);
});

View File

@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/utils/feed_back.dart';
class ActionItem extends StatelessWidget {
final Icon? icon;
final dynamic icon;
final Icon? selectIcon;
final Function? onTap;
final Function? onLongPress;
@ -31,26 +32,46 @@ class ActionItem extends StatelessWidget {
if (onLongPress != null) {onLongPress!()}
},
borderRadius: StyleString.mdRadius,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
selectStatus
? Icon(selectIcon!.icon!,
size: 18, color: Theme.of(context).colorScheme.primary)
: Icon(icon!.icon!,
size: 18, color: Theme.of(context).colorScheme.outline),
const SizedBox(height: 6),
Text(
text ?? '',
style: TextStyle(
color: selectStatus
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
child: SizedBox(
width: (Get.size.width - 24) / 5,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(height: 4),
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
child: icon is Icon
? Icon(
selectStatus
? selectIcon!.icon ?? icon!.icon
: icon!.icon,
color: selectStatus
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
)
: Image.asset(
key: ValueKey<bool>(selectStatus),
'assets/images/coin.png',
width: 25,
color: selectStatus
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,
),
),
)
],
const SizedBox(height: 6),
Text(
text ?? '',
style: TextStyle(
color:
selectStatus ? Theme.of(context).colorScheme.primary : null,
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
),
),
],
),
),
);
}

View File

@ -106,7 +106,7 @@ class _PagesPanelState extends State<PagesPanel> {
height: 34,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
widget.videoIntroCtr.bottomSheetController =

View File

@ -97,7 +97,7 @@ class ReplyItem extends StatelessWidget {
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(7),
color: colorScheme.background,
color: colorScheme.surface,
),
child: Icon(
Icons.offline_bolt,

View File

@ -27,8 +27,8 @@ class ToolbarIconButton extends StatelessWidget {
? Theme.of(context).colorScheme.onSecondaryContainer
: Theme.of(context).colorScheme.outline,
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.resolveWith((states) {
padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor: WidgetStateProperty.resolveWith((states) {
return selected
? Theme.of(context).colorScheme.secondaryContainer
: null;

View File

@ -259,7 +259,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
size: 22),
label: const Text('转发到动态'),
style: ButtonStyle(
foregroundColor: MaterialStateProperty.all(
foregroundColor: WidgetStateProperty.all(
isForward.value
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.outline,

View File

@ -412,7 +412,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
height: 32,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => vdCtr.showShootDanmakuSheet(),
child:

View File

@ -108,7 +108,7 @@ class AiDetail extends StatelessWidget {
fontSize: 13,
color: Theme.of(context)
.colorScheme
.onBackground,
.onSurface,
height: 1.5,
),
children: [

View File

@ -1248,7 +1248,7 @@ class _HeaderControlState extends State<HeaderControl> {
height: 34,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => showShootDanmakuSheet(),
child: const Text(
@ -1263,7 +1263,7 @@ class _HeaderControlState extends State<HeaderControl> {
child: Obx(
() => IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
_.isOpenDanmu.value = !_.isOpenDanmu.value;
@ -1286,7 +1286,7 @@ class _HeaderControlState extends State<HeaderControl> {
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () async {
bool canUsePiP = false;
@ -1330,7 +1330,7 @@ class _HeaderControlState extends State<HeaderControl> {
height: 34,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => showSetSpeedSheet(),
child: Text(

View File

@ -95,9 +95,9 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor: MaterialStateProperty.resolveWith(
(Set<MaterialState> states) {
padding: WidgetStateProperty.all(EdgeInsets.zero),
backgroundColor: WidgetStateProperty.resolveWith(
(Set<WidgetState> states) {
return Theme.of(context)
.colorScheme
.primaryContainer

View File

@ -279,7 +279,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
widget.showEposideCb?.call();
},
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
child: const Text(
'选集',
@ -294,7 +294,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
child: TextButton(
onPressed: () => _.toggleVideoFit(),
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
child: Obx(
() => Text(
@ -311,7 +311,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
height: 34,
child: TextButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {},
child: Obx(

View File

@ -17,7 +17,7 @@ class ComBtn extends StatelessWidget {
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () {
fuc!();

View File

@ -68,7 +68,7 @@ class PlayOrPauseButtonState extends State<PlayOrPauseButton>
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: player.playOrPause,
color: Colors.white,

View File

@ -35,6 +35,7 @@ import '../pages/search/index.dart';
import '../pages/search_result/index.dart';
import '../pages/setting/extra_setting.dart';
import '../pages/setting/index.dart';
import '../pages/setting/pages/action_menu_set.dart';
import '../pages/setting/pages/color_select.dart';
import '../pages/setting/pages/display_mode.dart';
import '../pages/setting/pages/font_size_select.dart';
@ -174,6 +175,9 @@ class Routes {
// navigation bar
CustomGetPage(
name: '/navbarSetting', page: () => const NavigationBarSetPage()),
// 操作菜单
CustomGetPage(
name: '/actionMenuSet', page: () => const ActionMenuSetPage()),
];
}

View File

@ -11,7 +11,8 @@ class GlobalData {
bool enablePlayerControlAnimation = true;
final bool enableMYBar =
setting.get(SettingBoxKey.enableMYBar, defaultValue: true);
List<String> actionTypeSort = setting.get(SettingBoxKey.actionTypeSort,
defaultValue: ['like', 'coin', 'collect', 'watchLater', 'share']);
// 私有构造函数
GlobalData._();

View File

@ -39,7 +39,7 @@ Future imageSaveDialog(context, videoItem, closeFn) {
const BorderRadius.all(Radius.circular(20))),
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => closeFn!(),
icon: const Icon(

View File

@ -149,7 +149,8 @@ class SettingBoxKey {
tabbarSort = 'tabbarSort', // 首页tabbar
dynamicBadgeMode = 'dynamicBadgeMode',
enableGradientBg = 'enableGradientBg',
navBarSort = 'navBarSort';
navBarSort = 'navBarSort',
actionTypeSort = 'actionTypeSort';
}
class LocalCacheKey {