Compare commits

..

15 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
bae395c5d0 fix: add reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-25 18:03:44 +08:00
bggRGjQaUbCoE
e26ec2ea42 fix: add reply
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-25 16:27:08 +08:00
bggRGjQaUbCoE
f25eb7be82 feat: sponsorblock: post segments (#9)
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-25 14:38:09 +08:00
bggRGjQaUbCoE
c0879ee169 mod: sponsorblock: pass cid
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-25 10:42:43 +08:00
bggRGjQaUbCoE
36aa12025e fix: block settings import #7
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-25 10:23:36 +08:00
armv7a
0ebe4e9a80 Buffering... > 加载中... (#10) 2024-11-25 09:39:57 +08:00
ɴᴇᴋᴏ
a3d345e3ee Update README (#6) 2024-11-25 00:45:45 +08:00
bggRGjQaUbCoE
2e45fafb0b mod: add reply: data cast
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-24 20:04:16 +08:00
bggRGjQaUbCoE
6c62cebdba opt: rank page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-24 16:42:24 +08:00
bggRGjQaUbCoE
d3bd218718 fix: dyn red
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-24 14:21:48 +08:00
bggRGjQaUbCoE
0ff53ddbde fix: nav bar
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-24 13:16:22 +08:00
guozhigq
bcefaa123e feat: navigation Bar编辑
Co-authored-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-24 12:28:43 +08:00
bggRGjQaUbCoE
e2da6a2936 fix: rcmd data
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-23 23:11:28 +08:00
bggRGjQaUbCoE
44bd5afb70 refactor: search page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-23 22:20:18 +08:00
bggRGjQaUbCoE
d8d6cf4d26 opt: ctr tag
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2024-11-23 17:32:35 +08:00
36 changed files with 1076 additions and 591 deletions

View File

@@ -8,9 +8,9 @@
<h1>PiliPalaX</h1>
<div align="center">
![GitHub repo size](https://img.shields.io/github/repo-size/orz12/pilipala)
![GitHub Repo stars](https://img.shields.io/github/stars/orz12/pilipala)
![GitHub all releases](https://img.shields.io/github/downloads/orz12/pilipala/total)
![GitHub repo size](https://img.shields.io/github/repo-size/bggRGjQaUbCoE/PiliPalaX)
![GitHub Repo stars](https://img.shields.io/github/stars/bggRGjQaUbCoE/PiliPalaX)
![GitHub all releases](https://img.shields.io/github/downloads/bggRGjQaUbCoE/PiliPalaX/total)
</div>
<p>使用Flutter开发的BiliBili第三方客户端</p>

View File

@@ -86,7 +86,7 @@ class DynamicSliverAppBar extends StatefulWidget {
final CustomClipper<Path>? appBarClipper;
@override
_DynamicSliverAppBarState createState() => _DynamicSliverAppBarState();
State<DynamicSliverAppBar> createState() => _DynamicSliverAppBarState();
}
class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {

View File

@@ -0,0 +1,27 @@
import 'package:flutter/material.dart';
Widget iconButton({
required BuildContext context,
String? tooltip,
required IconData icon,
required VoidCallback? onPressed,
double size = 36,
}) {
return SizedBox(
width: size,
height: size,
child: IconButton(
tooltip: tooltip,
onPressed: onPressed,
icon: Icon(
icon,
size: size / 2,
color: Theme.of(context).colorScheme.onSecondaryContainer,
),
style: IconButton.styleFrom(
padding: EdgeInsets.all(0),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
),
),
);
}

View File

@@ -67,8 +67,7 @@ class NetworkImgLayer extends StatelessWidget {
child: Builder(
builder: (context) => CachedNetworkImage(
imageUrl:
'${src?.startsWith('//') == true ? 'https:$src' : src}${thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}'
.http2https,
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
width: width,
height: ignoreHeight == null || ignoreHeight == false
? height
@@ -107,6 +106,7 @@ class NetworkImgLayer extends StatelessWidget {
}
Widget placeholder(BuildContext context) {
int cacheWidth = width.cacheSize(context);
return Container(
width: width,
height: height,
@@ -128,7 +128,7 @@ class NetworkImgLayer extends StatelessWidget {
: 'assets/images/loading.png',
width: width,
height: height,
cacheWidth: width.cacheSize(context),
cacheWidth: cacheWidth == 0 ? null : cacheWidth,
// cacheHeight: height.cacheSize(context),
),
),

View File

@@ -33,7 +33,7 @@ class SearchHttp {
return {
'status': false,
'data': [],
'msg': '请求错误 🙅',
'msg': '请求错误',
};
}

View File

@@ -12,7 +12,6 @@ import 'package:hive/hive.dart';
import 'package:PiliPalaX/common/widgets/custom_toast.dart';
import 'package:PiliPalaX/http/init.dart';
import 'package:PiliPalaX/models/common/color_type.dart';
import 'package:PiliPalaX/pages/search/index.dart';
import 'package:PiliPalaX/pages/video/detail/index.dart';
import 'package:PiliPalaX/router/app_pages.dart';
import 'package:PiliPalaX/pages/main/view.dart';
@@ -190,7 +189,6 @@ class MyApp extends StatelessWidget {
},
navigatorObservers: [
VideoDetailPage.routeObserver,
SearchPage.routeObserver,
MainApp.routeObserver,
],
);

View File

@@ -399,6 +399,7 @@ class _BangumiInfoState extends State<BangumiInfo>
: widget.bangumiDetail!.episodes!.first.cid),
changeFuc: bangumiIntroController.changeSeasonOrbangu,
showEpisodes: widget.showEpisodes,
newEp: bangumiItem?.newEp,
)
],
],

View File

@@ -15,6 +15,7 @@ class BangumiPanel extends StatefulWidget {
required this.changeFuc,
required this.showEpisodes,
required this.heroTag,
this.newEp,
});
final List<EpisodeItem> pages;
@@ -22,6 +23,7 @@ class BangumiPanel extends StatefulWidget {
final Function changeFuc;
final Function showEpisodes;
final String heroTag;
final dynamic newEp;
@override
State<BangumiPanel> createState() => _BangumiPanelState();
@@ -126,7 +128,9 @@ class _BangumiPanelState extends State<BangumiPanel> {
cid,
),
child: Text(
'${widget.pages.length}',
widget.newEp?['desc']?.contains('连载') == true
? '连载中,更新至第${widget.newEp?['title']}'
: '${widget.newEp?['title']}',
style: const TextStyle(fontSize: 13),
),
),

View File

@@ -4,6 +4,7 @@ import 'package:PiliPalaX/models/common/reply_type.dart';
import 'package:PiliPalaX/pages/common/common_controller.dart';
import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -120,6 +121,7 @@ abstract class ReplyController extends CommonController {
dynamic oid,
dynamic replyItem,
int index = 0,
ReplyType? replyType,
}) {
dynamic key = oid ?? replyItem.oid + replyItem.id;
Navigator.of(context)
@@ -130,7 +132,9 @@ abstract class ReplyController extends CommonController {
oid: oid ?? replyItem.oid.toInt(),
root: oid != null ? 0 : replyItem.id.toInt(),
parent: oid != null ? 0 : replyItem.id.toInt(),
replyType: ReplyType.video,
replyType: replyItem != null
? ReplyType.values[replyItem.type.toInt()]
: replyType,
replyItem: replyItem,
savedReply: savedReplies[key],
onSaveReply: (reply) {
@@ -155,20 +159,18 @@ abstract class ReplyController extends CommonController {
),
)
.then(
(value) {
// TODO: data cast
if (value != null && value['data'] != null) {
(res) {
if (res != null) {
savedReplies[key] = null;
if (value['data'] is ReplyInfo) {
MainListReply response =
(loadingState.value as Success?)?.response ?? MainListReply();
if (oid != null) {
response.replies.insert(0, value['data']);
} else {
response.replies[index].replies.add(value['data']);
}
loadingState.value = LoadingState.success(response);
ReplyInfo replyInfo = Utils.replyCast(res);
MainListReply response =
(loadingState.value as Success?)?.response ?? MainListReply();
if (oid != null) {
response.replies.insert(hasUpTop ? 1 : 0, replyInfo);
} else {
response.replies[index].replies.add(replyInfo);
}
loadingState.value = LoadingState.success(response);
}
},
);

View File

@@ -4,6 +4,7 @@ import 'dart:math';
import 'package:PiliPalaX/common/widgets/refresh_indicator.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/video/detail/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
@@ -88,8 +89,9 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
if (opusId != null) {
isOpusId = true;
_dynamicDetailController = Get.put(
DynamicDetailController(oid, replyType),
tag: opusId.toString());
DynamicDetailController(oid, replyType),
tag: Utils.makeHeroTag(opusId),
);
await _dynamicDetailController.reqHtmlByOpusId(opusId!);
setState(() {});
}
@@ -99,8 +101,10 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
} catch (_) {}
}
if (!isOpusId) {
_dynamicDetailController =
Get.put(DynamicDetailController(oid, replyType), tag: oid.toString());
_dynamicDetailController = Get.put(
DynamicDetailController(oid, replyType),
tag: Utils.makeHeroTag(oid),
);
}
}
@@ -297,7 +301,11 @@ class _DynamicDetailPageState extends State<DynamicDetailPage>
feedBack();
dynamic oid = _dynamicDetailController.oid ??
IdUtils.bv2av(Get.parameters['bvid']!);
_dynamicDetailController.onReply(context, oid: oid);
_dynamicDetailController.onReply(
context,
oid: oid,
replyType: ReplyType.values[replyType],
);
},
tooltip: '评论动态',
child: const Icon(Icons.reply),

View File

@@ -23,9 +23,7 @@ class DynamicsTabController extends CommonController {
@override
Future onRefresh() async {
if (dynamicsType == 'all') {
if (mainController.navigationBars[1]['count'] != 0) {
mainController.clearUnread();
}
mainController.setCount();
}
offset = '';
await queryData();

View File

@@ -1,5 +1,6 @@
import 'package:PiliPalaX/common/widgets/refresh_indicator.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -25,7 +26,7 @@ class _FansPageState extends State<FansPage> {
void initState() {
super.initState();
mid = Get.parameters['mid']!;
_fansController = Get.put(FansController(), tag: mid);
_fansController = Get.put(FansController(), tag: Utils.makeHeroTag(mid));
_fansController.scrollController.addListener(
() async {
if (_fansController.scrollController.position.pixels >=

View File

@@ -1,4 +1,5 @@
import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType;
import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';
@@ -20,7 +21,8 @@ class _FollowPageState extends State<FollowPage> {
void initState() {
super.initState();
mid = Get.parameters['mid']!;
_followController = Get.put(FollowController(), tag: mid);
_followController =
Get.put(FollowController(), tag: Utils.makeHeroTag(mid));
}
@override

View File

@@ -25,7 +25,7 @@ class HistoryController extends GetxController {
Future queryHistoryList({type = 'init'}) async {
int max = 0;
int viewAt = 0;
if (type == 'onload') {
if (type == 'onload' && historyList.isNotEmpty) {
max = historyList.last.history!.oid!;
viewAt = historyList.last.viewAt!;
}

View File

@@ -307,6 +307,7 @@ class _HtmlRenderPageState extends State<HtmlRenderPage>
_htmlRenderCtr.onReply(
context,
oid: _htmlRenderCtr.oid.value,
replyType: ReplyType.values[type],
);
},
tooltip: '评论动态',

View File

@@ -1,26 +1,20 @@
import 'dart:async';
import 'dart:io';
import 'package:PiliPalaX/grpc/grpc_repo.dart';
import 'package:flutter/services.dart';
import 'package:PiliPalaX/pages/dynamics/view.dart';
import 'package:PiliPalaX/pages/home/view.dart';
import 'package:PiliPalaX/pages/media/view.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/pages/dynamics/index.dart';
import 'package:PiliPalaX/pages/home/view.dart';
import 'package:PiliPalaX/pages/media/index.dart';
import 'package:PiliPalaX/utils/storage.dart';
import '../../models/common/dynamic_badge_mode.dart';
import '../../models/common/nav_bar_config.dart';
class MainController extends GetxController {
List<Widget> pages = <Widget>[
const HomePage(),
// const RankPage(),
const DynamicsPage(),
const MediaPage(),
];
RxList navigationBars = defaultNavigationBars.obs;
List<Widget> pages = <Widget>[];
RxList navigationBars = [].obs;
final StreamController<bool> bottomBarStream =
StreamController<bool>.broadcast();
Box setting = GStorage.setting;
@@ -33,85 +27,53 @@ class MainController extends GetxController {
late bool checkDynamic;
late int dynamicPeriod;
int? _lastCheckAt;
int? dynIndex;
@override
void onInit() {
super.onInit();
checkDynamic = GStorage.checkDynamic;
dynamicPeriod = GStorage.dynamicPeriod;
// if (setting.get(SettingBoxKey.autoUpdate, defaultValue: false)) {
// Utils.checkUpdate();
// }
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: true);
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
selectedIndex = defaultNavigationBars
.indexWhere((item) => item['id'] == defaultHomePage);
dynamic userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
dynamicBadgeType = DynamicBadgeMode.values[setting.get(
SettingBoxKey.dynamicBadgeMode,
defaultValue: DynamicBadgeMode.number.code)];
if (dynamicBadgeType != DynamicBadgeMode.hidden) {
if (checkDynamic) {
_lastCheckAt = DateTime.now().millisecondsSinceEpoch;
}
getUnreadDynamic();
}
}
void onBackPressed(BuildContext context) {
// if (_lastPressedAt == null ||
// DateTime.now().difference(_lastPressedAt!) >
// const Duration(seconds: 2)) {
// 两次点击时间间隔超过2秒重新记录时间戳
// _lastPressedAt = DateTime.now();
if (selectedIndex != 0) {
pageController.jumpTo(0);
} else {
if (Platform.isAndroid) {
const MethodChannel("onUserLeaveHint").invokeMethod('back');
} else {
SystemNavigator.pop(); // 退出应用
setNavBarConfig();
if (dynamicBadgeType != DynamicBadgeMode.hidden) {
dynIndex = navigationBars.indexWhere((e) => e['id'] == 1);
if (dynIndex != -1) {
if (checkDynamic) {
_lastCheckAt = DateTime.now().millisecondsSinceEpoch;
}
getUnreadDynamic();
}
}
// SmartDialog.showToast("再按一次退出PiliPalaX");
// return; // 不退出应用
// }
// SystemNavigator.pop(); // 退出应用
}
void getUnreadDynamic() async {
if (!userLogin.value) {
if (!userLogin.value || dynIndex == -1) {
return;
}
// not needed yet
// int dynamicItemIndex =
// navigationBars.indexWhere((item) => item['label'] == "动态");
// if (dynamicItemIndex == -1) return;
// var res = await CommonHttp.unReadDynamic();
// var data = res['data'];
// navigationBars[1]['count'] =
// data == null ? 0 : data.length; // 修改 count 属性为新的值
await GrpcRepo.dynRed().then((res) {
if (res['status']) {
navigationBars[1]['count'] = res['data'];
setCount(res['data']);
}
});
navigationBars.refresh();
}
void clearUnread() async {
// not needed yet
// int dynamicItemIndex =
// navigationBars.indexWhere((item) => item['label'] == "动态");
// if (dynamicItemIndex == -1) return;
navigationBars[1]['count'] = 0; // 修改 count 属性为新的值
void setCount([int count = 0]) async {
dynIndex ??= navigationBars.indexWhere((e) => e['id'] == 1);
if (dynIndex == -1 || navigationBars[dynIndex!]['count'] == count) return;
navigationBars[dynIndex!]['count'] = count; // 修改 count 属性为新的值
navigationBars.refresh();
}
void checkUnreadDynamic() {
if (!userLogin.value ||
if (dynIndex == -1 ||
!userLogin.value ||
dynamicBadgeType == DynamicBadgeMode.hidden ||
!checkDynamic) return;
int now = DateTime.now().millisecondsSinceEpoch;
@@ -120,4 +82,28 @@ class MainController extends GetxController {
getUnreadDynamic();
}
}
void setNavBarConfig() async {
List defaultNavTabs = [...defaultNavigationBars];
List navBarSort =
setting.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2]);
defaultNavTabs.retainWhere((item) => navBarSort.contains(item['id']));
defaultNavTabs.sort((a, b) =>
navBarSort.indexOf(a['id']).compareTo(navBarSort.indexOf(b['id'])));
navigationBars.value = defaultNavTabs;
int defaultHomePage =
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0) as int;
int defaultIndex =
navigationBars.indexWhere((item) => item['id'] == defaultHomePage);
// 如果找不到匹配项默认索引设置为0或其他合适的值
selectedIndex = defaultIndex != -1 ? defaultIndex : 0;
pages = navigationBars
.map<Widget>((e) => switch (e['id']) {
0 => HomePage(),
1 => DynamicsPage(),
2 => MediaPage(),
_ => throw UnimplementedError(),
})
.toList();
}
}

View File

@@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:io';
import 'package:PiliPalaX/grpc/grpc_client.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/models/common/dynamic_badge_mode.dart';
@@ -108,7 +110,7 @@ class _MainAppState extends State<MainApp>
_lastSelectTime = DateTime.now().millisecondsSinceEpoch;
}
_dynamicController.flag = true;
_mainController.clearUnread();
_mainController.setCount();
} else {
_dynamicController.flag = false;
}
@@ -129,12 +131,15 @@ class _MainAppState extends State<MainApp>
return PopScope(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) async {
_mainController.onBackPressed(context);
if (_dynamicController.flag) {
_dynamicController.flag = false;
}
if (!_homeController.flag) {
_homeController.flag = true;
if (_mainController.selectedIndex != 0) {
setIndex(0);
_mainController.bottomBarStream.add(true);
} else {
if (Platform.isAndroid) {
const MethodChannel("onUserLeaveHint").invokeMethod('back');
} else {
SystemNavigator.pop();
}
}
},
child: LayoutBuilder(
@@ -152,58 +157,71 @@ class _MainAppState extends State<MainApp>
36.801 +
MediaQuery.of(context).padding.left,
child: Obx(
() => NavigationRail(
groupAlignment: 1,
minWidth: context.width * 0.0286 + 28.56,
backgroundColor: Colors.transparent,
selectedIndex: _mainController.selectedIndex,
onDestinationSelected: (value) => setIndex(value),
labelType: NavigationRailLabelType.none,
leading: UserAndSearchVertical(ctr: _homeController),
destinations: _mainController.navigationBars
.map((e) => NavigationRailDestination(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: Text(e['label']),
padding: EdgeInsets.symmetric(
vertical: 0.01 * context.height),
))
.toList(),
trailing: SizedBox(height: 0.1 * context.height),
),
() => _mainController.navigationBars.length > 1
? NavigationRail(
groupAlignment: 1,
minWidth: context.width * 0.0286 + 28.56,
backgroundColor: Colors.transparent,
selectedIndex: _mainController.selectedIndex,
onDestinationSelected: setIndex,
labelType: NavigationRailLabelType.none,
leading:
UserAndSearchVertical(ctr: _homeController),
destinations: _mainController.navigationBars
.map((e) => NavigationRailDestination(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: Text(e['label']),
padding: EdgeInsets.symmetric(
vertical: 0.01 * context.height),
))
.toList(),
trailing: SizedBox(height: 0.1 * context.height),
)
: Container(
padding: EdgeInsets.only(
top: MediaQuery.paddingOf(context).top + 10),
constraints: BoxConstraints(
minWidth: context.width * 0.0286 + 28.56,
),
child:
UserAndSearchVertical(ctr: _homeController),
),
),
),
] else if (!isPortait)
Obx(
() => NavigationRail(
onDestinationSelected: (value) => setIndex(value),
selectedIndex: _mainController.selectedIndex,
destinations: _mainController.navigationBars
.map(
(e) => NavigationRailDestination(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: Text(e['label']),
),
() => _mainController.navigationBars.length > 1
? NavigationRail(
onDestinationSelected: setIndex,
selectedIndex: _mainController.selectedIndex,
destinations: _mainController.navigationBars
.map(
(e) => NavigationRailDestination(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: Text(e['label']),
),
)
.toList(),
)
.toList(),
),
: const SizedBox.shrink(),
),
VerticalDivider(
width: 1,
@@ -240,60 +258,65 @@ class _MainAppState extends State<MainApp>
offset: Offset(0, snapshot.data ? 0 : 1),
child: enableMYBar
? Obx(
() => NavigationBar(
onDestinationSelected: (value) =>
setIndex(value),
selectedIndex: _mainController.selectedIndex,
destinations:
_mainController.navigationBars.map(
(e) {
return NavigationDestination(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: e['label'],
);
},
).toList(),
),
() => _mainController.navigationBars.length > 1
? NavigationBar(
onDestinationSelected: setIndex,
selectedIndex:
_mainController.selectedIndex,
destinations:
_mainController.navigationBars.map(
(e) {
return NavigationDestination(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
selectedIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: e['label'],
);
},
).toList(),
)
: const SizedBox.shrink(),
)
: Obx(
() => BottomNavigationBar(
currentIndex: _mainController.selectedIndex,
onTap: (value) => setIndex(value),
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
type: BottomNavigationBarType.fixed,
// selectedItemColor:
// Theme.of(context).colorScheme.primary, // 选中项的颜色
// unselectedItemColor:
// Theme.of(context).colorScheme.onSurface,
items: _mainController.navigationBars
.map(
(e) => BottomNavigationBarItem(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
activeIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: e['label'],
),
() => _mainController.navigationBars.length > 1
? BottomNavigationBar(
currentIndex:
_mainController.selectedIndex,
onTap: setIndex,
iconSize: 16,
selectedFontSize: 12,
unselectedFontSize: 12,
type: BottomNavigationBarType.fixed,
// selectedItemColor:
// Theme.of(context).colorScheme.primary, // 选中项的颜色
// unselectedItemColor:
// Theme.of(context).colorScheme.onSurface,
items: _mainController.navigationBars
.map(
(e) => BottomNavigationBarItem(
icon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['icon'],
),
activeIcon: _buildIcon(
id: e['id'],
count: e['count'],
icon: e['selectIcon'],
),
label: e['label'],
),
)
.toList(),
)
.toList(),
),
: const SizedBox.shrink(),
),
);
},

View File

@@ -214,6 +214,7 @@ class _EditProfilePageState extends State<EditProfilePage> {
'https://account.bilibili.com/official/mobile/home'),
),
_divider,
SizedBox(height: 25 + MediaQuery.paddingOf(context).bottom),
],
),
),

View File

@@ -30,52 +30,59 @@ class _RankPageState extends State<RankPage>
children: [
Expanded(
flex: 18,
child: ListView.builder(
physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics(),
child: SingleChildScrollView(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
itemCount: _rankController.tabs.length,
itemBuilder: (context, index) => IntrinsicHeight(
child: Obx(
() => InkWell(
onTap: () {
_rankController.initialIndex.value = index;
_rankController.tabController.animateTo(index);
},
child: Container(
color: index == _rankController.initialIndex.value
? Theme.of(context).colorScheme.onInverseSurface
: Colors.transparent,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: double.infinity,
width: 3,
color: index == _rankController.initialIndex.value
? Theme.of(context).colorScheme.primary
: Colors.transparent,
),
Expanded(
flex: 1,
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(vertical: 10),
child: Text(
_rankController.tabs[index]['label'],
style: TextStyle(
color: index ==
_rankController.initialIndex.value
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.onSurface,
fontSize: 15,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
child: Column(
children: List.generate(
_rankController.tabs.length,
(index) => Obx(
() => IntrinsicHeight(
child: InkWell(
onTap: () {
_rankController.initialIndex.value = index;
_rankController.tabController.animateTo(index);
},
child: ColoredBox(
color: index == _rankController.initialIndex.value
? Theme.of(context).colorScheme.onInverseSurface
: Theme.of(context).colorScheme.surface,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: double.infinity,
width: 3,
color: index == _rankController.initialIndex.value
? Theme.of(context).colorScheme.primary
: Colors.transparent,
),
),
Expanded(
flex: 1,
child: Container(
alignment: Alignment.center,
padding:
const EdgeInsets.symmetric(vertical: 10),
child: Text(
_rankController.tabs[index]['label'],
style: TextStyle(
color: index ==
_rankController.initialIndex.value
? Theme.of(context).colorScheme.primary
: Theme.of(context)
.colorScheme
.onSurface,
fontSize: 15,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
),
],
),
],
),
),
),
),

View File

@@ -1,5 +1,4 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/models/home/rcmd/result.dart';
import 'package:PiliPalaX/pages/common/popup_controller.dart';
import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/utils/storage.dart';
@@ -35,9 +34,11 @@ class RcmdController extends PopupController {
@override
List? handleListResponse(List currentList, List dataList) {
return currentPage == 0 && enableSaveLastData && currentList.length < 500
? dataList +
(currentList.isEmpty ? <RecVideoItemAppModel>[] : currentList)
return currentPage == 0 &&
enableSaveLastData &&
currentList.isNotEmpty &&
currentList.length < 500
? dataList + currentList
: null;
}

View File

@@ -1,27 +1,23 @@
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get_rx/src/rx_workers/utils/debouncer.dart';
import 'package:hive/hive.dart';
import 'package:PiliPalaX/http/search.dart';
import 'package:PiliPalaX/models/search/hot.dart';
import 'package:PiliPalaX/models/search/suggest.dart';
import 'package:PiliPalaX/utils/storage.dart';
class SSearchController extends GetxController {
final FocusNode searchFocusNode = FocusNode();
RxString searchKeyWord = ''.obs;
Rx<TextEditingController> controller = TextEditingController().obs;
RxList<HotSearchItem> hotSearchList = <HotSearchItem>[].obs;
Box historyWord = GStorage.historyWord;
List<String> historyCacheList = [];
final searchFocusNode = FocusNode();
final controller = TextEditingController();
RxList<String> historyList = <String>[].obs;
RxList<SearchSuggestItem> searchSuggestList = <SearchSuggestItem>[].obs;
final _debouncer =
Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间
String hintText = '搜索';
RxString defaultSearch = ''.obs;
Box setting = GStorage.setting;
bool enableHotKey = true;
late bool enableHotKey;
Rx<LoadingState> loadingState = LoadingState.loading().obs;
@override
void onInit() {
@@ -33,28 +29,31 @@ class SSearchController extends GetxController {
}
if (Get.parameters['hintText'] != null) {
hintText = Get.parameters['hintText']!;
searchKeyWord.value = hintText;
}
}
historyCacheList = List<String>.from(historyWord.get('cacheList') ?? []);
historyList.value = historyCacheList;
enableHotKey = setting.get(SettingBoxKey.enableHotKey, defaultValue: true);
historyList.value = List.from(GStorage.historyWord.get('cacheList') ?? []);
enableHotKey =
GStorage.setting.get(SettingBoxKey.enableHotKey, defaultValue: true);
if (enableHotKey) {
queryHotSearchList();
}
}
void onChange(value) {
searchKeyWord.value = value;
if (value == '') {
searchSuggestList.value = [];
return;
void onChange(String value) {
if (value.isEmpty) {
searchSuggestList.clear();
} else {
querySearchSuggest(value);
}
_debouncer.call(() => querySearchSuggest(value));
}
void onClear() {
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
controller.value.clear();
searchKeyWord.value = '';
searchSuggestList.value = [];
if (controller.value.text != '') {
controller.clear();
searchSuggestList.clear();
searchFocusNode.requestFocus();
} else {
Get.back();
@@ -62,44 +61,38 @@ class SSearchController extends GetxController {
}
// 搜索
void submit() {
// ignore: unrelated_type_equality_checks
if (searchKeyWord == '') {
if (hintText == '') {
void submit() async {
if (controller.text.isEmpty) {
if (hintText.isEmpty) {
return;
}
searchKeyWord.value = hintText;
controller.text = hintText;
}
List<String> arr =
historyCacheList.where((e) => e != searchKeyWord.value).toList();
arr.insert(0, searchKeyWord.value);
historyCacheList = arr;
historyList.value = historyCacheList;
// 手动刷新
historyList.refresh();
historyWord.put('cacheList', historyCacheList);
historyList.remove(controller.text);
historyList.insert(0, controller.text);
GStorage.historyWord.put('cacheList', historyList);
searchFocusNode.unfocus();
Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value});
await Get.toNamed('/searchResult', parameters: {
'keyword': controller.text,
});
searchFocusNode.requestFocus();
}
// 获取热搜关键词
Future queryHotSearchList() async {
var result = await SearchHttp.hotSearchList();
dynamic result = await SearchHttp.hotSearchList();
if (result['status']) {
hotSearchList.value = result['data'].list;
loadingState.value = LoadingState.success(result['data'].list);
} else {
loadingState.value = LoadingState.error(result['msg']);
}
return result;
}
// 点击热搜关键词
void onClickKeyword(String keyword) {
searchKeyWord.value = keyword;
controller.value.text = keyword;
// 移动光标
controller.value.selection = TextSelection.fromPosition(
TextPosition(offset: controller.value.text.length),
);
controller.text = keyword;
searchSuggestList.clear();
submit();
}
@@ -112,28 +105,20 @@ class SSearchController extends GetxController {
}
}
onSelect(word) {
searchKeyWord.value = word;
controller.value.text = word;
submit();
}
onLongSelect(word) {
int index = historyList.indexOf(word);
historyList.removeAt(index);
historyWord.put('cacheList', historyList);
historyList.remove(word);
GStorage.historyWord.put('cacheList', historyList);
}
onClearHis() {
historyList.value = [];
historyCacheList = [];
historyList.refresh();
historyWord.put('cacheList', []);
onClearHistory() {
historyList.clear();
GStorage.historyWord.put('cacheList', []);
}
@override
void onClose() {
searchFocusNode.dispose();
controller.dispose();
super.onClose();
}
}

View File

@@ -1,6 +1,7 @@
import 'package:PiliPalaX/common/widgets/loading_widget.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/widgets/http_error.dart';
import 'controller.dart';
import 'widgets/hot_keyword.dart';
import 'widgets/search_text.dart';
@@ -10,39 +11,10 @@ class SearchPage extends StatefulWidget {
@override
State<SearchPage> createState() => _SearchPageState();
static final RouteObserver<PageRoute> routeObserver =
RouteObserver<PageRoute>();
}
class _SearchPageState extends State<SearchPage> with RouteAware {
final SSearchController _searchController = Get.put(SSearchController());
late Future? _futureBuilderFuture;
@override
void initState() {
super.initState();
_futureBuilderFuture = _searchController.queryHotSearchList();
}
@override
void dispose() {
SearchPage.routeObserver.unsubscribe(this);
super.dispose();
}
@override
// 返回当前页面时
void didPopNext() async {
_searchController.searchFocusNode.requestFocus();
super.didPopNext();
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
SearchPage.routeObserver
.subscribe(this, ModalRoute.of(context) as PageRoute);
}
@override
Widget build(BuildContext context) {
@@ -63,38 +35,33 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
),
const SizedBox(width: 10)
],
title: Obx(
() => TextField(
autofocus: true,
focusNode: _searchController.searchFocusNode,
controller: _searchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _searchController.onChange(value),
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
tooltip: '清空',
icon: const Icon(Icons.clear, size: 22),
onPressed: () => _searchController.onClear(),
),
title: TextField(
autofocus: true,
focusNode: _searchController.searchFocusNode,
controller: _searchController.controller,
textInputAction: TextInputAction.search,
onChanged: _searchController.onChange,
textAlignVertical: TextAlignVertical.center,
decoration: InputDecoration(
hintText: _searchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
tooltip: '清空',
icon: const Icon(Icons.clear, size: 22),
onPressed: _searchController.onClear,
),
onSubmitted: (String value) => _searchController.submit(),
),
onSubmitted: (value) => _searchController.submit(),
),
),
body: SingleChildScrollView(
child: Column(
children: [
const SizedBox(height: 12),
// 搜索建议
_searchSuggest(),
// 热搜
Visibility(
visible: _searchController.enableHotKey,
child: hotSearch(_searchController),
),
if (_searchController.enableHotKey)
// 热搜
hotSearch(),
// 搜索历史
_history()
],
@@ -107,32 +74,37 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
return Obx(
() => _searchController.searchSuggestList.isNotEmpty &&
_searchController.searchSuggestList.first.term != null &&
_searchController.controller.value.text != ''
? ListView.builder(
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: _searchController.searchSuggestList.length,
itemBuilder: (context, index) {
return InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
onTap: () => _searchController.onClickKeyword(
_searchController.searchSuggestList[index].term!),
child: Padding(
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
child: _searchController.searchSuggestList[index].textRich,
),
);
},
_searchController.controller.text != ''
? Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: _searchController.searchSuggestList
.map(
(item) => InkWell(
customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
onTap: () => _searchController.onClickKeyword(item.term!),
child: Container(
width: double.infinity,
padding: const EdgeInsets.only(
left: 20,
top: 9,
bottom: 9,
),
child: item.textRich,
),
),
)
.toList(),
)
: const SizedBox(),
: const SizedBox.shrink(),
);
}
Widget hotSearch(ctr) {
Widget hotSearch() {
return Padding(
padding: const EdgeInsets.fromLTRB(10, 14, 4, 0),
padding: const EdgeInsets.fromLTRB(10, 25, 4, 25),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -152,10 +124,12 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
height: 34,
child: TextButton.icon(
style: ButtonStyle(
padding: WidgetStateProperty.all(const EdgeInsets.only(
left: 10, top: 6, bottom: 6, right: 10)),
padding: WidgetStateProperty.all(
const EdgeInsets.only(
left: 10, top: 6, bottom: 6, right: 10),
),
),
onPressed: () => ctr.queryHotSearchList(),
onPressed: _searchController.queryHotSearchList,
icon: const Icon(Icons.refresh_outlined, size: 18),
label: const Text('刷新'),
),
@@ -163,60 +137,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
],
),
),
LayoutBuilder(
builder: (context, boxConstraints) {
final double width = boxConstraints.maxWidth;
return FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
return Obx(
() => HotKeyword(
width: width,
// ignore: invalid_use_of_protected_member
hotSearchList: _searchController.hotSearchList.value,
onClick: (keyword) async {
_searchController.searchFocusNode.unfocus();
await Future.delayed(
const Duration(milliseconds: 150));
_searchController.onClickKeyword(keyword);
},
),
);
} else {
return CustomScrollView(
shrinkWrap: true,
slivers: [
HttpError(
errMsg: data['msg'],
callback: () => setState(() {
_futureBuilderFuture =
_searchController.queryHotSearchList();
}),
),
],
);
}
} else {
// 缓存数据
if (_searchController.hotSearchList.isNotEmpty) {
return HotKeyword(
width: width,
hotSearchList: _searchController.hotSearchList,
);
} else {
return const SizedBox();
}
}
},
);
},
),
Obx(() => _buildHotKey(_searchController.loadingState.value)),
],
),
);
@@ -227,13 +148,17 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
() => Container(
width: double.infinity,
padding: EdgeInsets.fromLTRB(
10, 25, 6, MediaQuery.of(context).padding.bottom + 50),
10,
_searchController.enableHotKey ? 0 : 6,
6,
MediaQuery.of(context).padding.bottom + 50,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (_searchController.historyList.isNotEmpty)
Padding(
padding: const EdgeInsets.fromLTRB(6, 0, 0, 2),
padding: const EdgeInsets.fromLTRB(6, 0, 6, 6),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@@ -244,9 +169,23 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
.titleMedium!
.copyWith(fontWeight: FontWeight.bold),
),
TextButton(
onPressed: () => _searchController.onClearHis(),
child: const Text('清空'),
SizedBox(
height: 34,
child: TextButton.icon(
style: ButtonStyle(
padding: WidgetStateProperty.all(
const EdgeInsets.only(
left: 10,
top: 6,
bottom: 6,
right: 10,
),
),
),
onPressed: _searchController.onClearHistory,
icon: const Icon(Icons.clear_all_outlined, size: 18),
label: const Text('清空'),
),
)
],
),
@@ -257,16 +196,15 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
runSpacing: 8,
direction: Axis.horizontal,
textDirection: TextDirection.ltr,
children: [
for (int i = 0; i < _searchController.historyList.length; i++)
SearchText(
searchText: _searchController.historyList[i],
searchTextIdx: i,
onSelect: (value) => _searchController.onSelect(value),
onLongSelect: (value) =>
_searchController.onLongSelect(value),
children: _searchController.historyList
.map(
(item) => SearchText(
searchText: item,
onSelect: _searchController.onClickKeyword,
onLongSelect: _searchController.onLongSelect,
),
)
],
.toList(),
),
),
],
@@ -274,4 +212,23 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
),
);
}
Widget _buildHotKey(LoadingState loadingState) {
return switch (loadingState) {
Success() => (loadingState.response as List?)?.isNotEmpty == true
? LayoutBuilder(
builder: (_, constraints) => HotKeyword(
width: constraints.maxWidth,
hotSearchList: loadingState.response,
onClick: _searchController.onClickKeyword,
),
)
: const SizedBox.shrink(),
Error() => errorWidget(
errMsg: loadingState.errMsg,
callback: _searchController.queryHotSearchList,
),
_ => const SizedBox.shrink(),
};
}
}

View File

@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
class SearchText extends StatelessWidget {
final String? searchText;
final Function? onSelect;
final int? searchTextIdx;
final Function? onLongSelect;
final double? fontSize;
final Color? bgColor;
@@ -13,7 +12,6 @@ class SearchText extends StatelessWidget {
super.key,
this.searchText,
this.onSelect,
this.searchTextIdx,
this.onLongSelect,
this.fontSize,
this.bgColor,

View File

@@ -32,19 +32,13 @@ Widget searchBangumiPanel(BuildContext context, ctr, loadingState) {
var i = loadingState.response[index];
return InkWell(
onTap: () {
/// TODO 番剧详情页面
// Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}', arguments: {
// 'videoItem': i,
// 'heroTag': Utils.makeHeroTag(i.id),
// 'videoType': SearchType.media_bangumi
// });
Utils.viewBangumi(seasonId: i.seasonId);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(
StyleString.safeSpace,
StyleString.safeSpace,
StyleString.safeSpace,
2),
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: StyleString.cardSpace,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -127,57 +121,6 @@ Widget searchBangumiPanel(BuildContext context, ctr, loadingState) {
Text(i.indexShow, style: style),
],
),
const SizedBox(height: 18),
SizedBox(
height: 32,
child: ElevatedButton(
onPressed: () async {
Utils.viewBangumi(seasonId: i.seasonId);
// SmartDialog.showLoading(msg: '获取中...');
// var res = await SearchHttp.bangumiInfo(
// seasonId: i.seasonId);
// SmartDialog.dismiss().then((value) {
// if (res['status']) {
// EpisodeItem episode =
// res['data'].episodes.first;
// int? epId = res['data']
// .userStatus
// ?.progress
// ?.lastEpId;
// if (epId == null) {
// epId = episode.epId;
// } else {
// for (var item
// in res['data'].episodes) {
// if (item.epId == epId) {
// episode = item;
// break;
// }
// }
// }
// String bvid = episode.bvid!;
// int cid = episode.cid!;
// String pic = episode.cover!;
// String heroTag =
// Utils.makeHeroTag(cid);
// Get.toNamed(
// '/video?bvid=$bvid&cid=$cid&seasonId=${i.seasonId}&epId=$epId',
// arguments: {
// 'pic': pic,
// 'heroTag': heroTag,
// 'videoType':
// SearchType.media_bangumi,
// 'bangumiItem': res['data'],
// },
// );
// } else {
// SmartDialog.showToast(res['msg']);
// }
// });
},
child: const Text('观看'),
),
),
],
),
),

View File

@@ -0,0 +1,100 @@
import 'package:PiliPalaX/utils/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import '../../../models/common/nav_bar_config.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 =
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,
physics: const NeverScrollableScrollPhysics(),
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

@@ -359,13 +359,19 @@ class _StyleSettingState extends State<StyleSetting> {
subtitle: Text('删除或调换首页标签页', style: subTitleStyle),
leading: const Icon(Icons.toc_outlined),
),
ListTile(
dense: false,
onTap: () => Get.toNamed('/navbarSetting'),
title: Text('Navbar编辑', style: titleStyle),
leading: const Icon(Icons.toc_outlined),
),
if (Platform.isAndroid)
ListTile(
dense: false,
onTap: () => Get.toNamed('/displayModeSetting'),
title: Text('屏幕帧率', style: titleStyle),
leading: const Icon(Icons.autofps_select_outlined),
)
),
],
),
);

View File

@@ -1,5 +1,8 @@
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:PiliPalaX/common/constants.dart';
import 'package:PiliPalaX/common/widgets/icon_button.dart';
import 'package:PiliPalaX/common/widgets/pair.dart';
import 'package:PiliPalaX/common/widgets/segment_progress_bar.dart';
import 'package:PiliPalaX/http/danmaku.dart';
@@ -7,6 +10,7 @@ import 'package:PiliPalaX/http/init.dart';
import 'package:dio/dio.dart';
import 'package:floating/floating.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
@@ -115,6 +119,19 @@ class SegmentModel {
bool hasSkipped;
}
class PostSegmentModel {
PostSegmentModel({
required this.segment,
required this.category,
required this.actionType,
});
Pair<int, int> segment;
SegmentType category;
ActionType actionType;
}
enum ActionType { skip, mute, full, poi }
class VideoDetailController extends GetxController
with GetSingleTickerProviderStateMixin {
/// 路由传参
@@ -159,6 +176,8 @@ class VideoDetailController extends GetxController
RxInt oid = 0.obs;
final scaffoldKey = GlobalKey<ScaffoldState>();
final childKey = GlobalKey<ScaffoldState>();
RxString bgCover = ''.obs;
PlPlayerController plPlayerController = PlPlayerController.getInstance()
..setCurrBrightness(-1.0);
@@ -183,7 +202,7 @@ class VideoDetailController extends GetxController
late String cacheSecondDecode;
late int cacheAudioQa;
late final bool _enableSponsorBlock;
late final bool enableSponsorBlock;
PlayerStatus? playerStatus;
StreamSubscription<Duration>? positionSubscription;
@@ -244,9 +263,9 @@ class VideoDetailController extends GetxController
cacheAudioQa = setting.get(SettingBoxKey.defaultAudioQa,
defaultValue: AudioQuality.hiRes.code);
oid.value = IdUtils.bv2av(Get.parameters['bvid']!);
_enableSponsorBlock =
enableSponsorBlock =
setting.get(SettingBoxKey.enableSponsorBlock, defaultValue: false);
if (_enableSponsorBlock) {
if (enableSponsorBlock) {
_blockLimit = GStorage.blockLimit;
_blockSettings = GStorage.blockSettings;
_blockColor = GStorage.blockColor;
@@ -263,14 +282,17 @@ class VideoDetailController extends GetxController
_blockColor?[segment.index] ?? segment.color;
Future _vote(String uuid, int type) async {
Request().post(
Request()
.post(
'${GStorage.blockServer}/api/voteOnSponsorTime',
queryParameters: {
'UUID': uuid,
'userID': GStorage.blockUserID,
'type': type,
},
).then((res) {
options: _options,
)
.then((res) {
SmartDialog.showToast(res.statusCode == 200 ? '投票成功' : '投票失败');
});
}
@@ -289,14 +311,17 @@ class VideoDetailController extends GetxController
dense: true,
onTap: () {
Get.back();
Request().post(
Request()
.post(
'${GStorage.blockServer}/api/voteOnSponsorTime',
queryParameters: {
'UUID': segment.UUID,
'userID': GStorage.blockUserID,
'category': item.name,
},
).then((res) {
options: _options,
)
.then((res) {
SmartDialog.showToast(
'类别更改${res.statusCode == 200 ? '成功' : '失败'}');
});
@@ -387,7 +412,7 @@ class VideoDetailController extends GetxController
);
}
void showSponsorBlock(BuildContext context) {
void showSBDetail(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
@@ -502,14 +527,7 @@ class VideoDetailController extends GetxController
);
}
Future _sponsorBlock() async {
dynamic result = await Request().get(
'${GStorage.blockServer}/api/skipSegments',
data: {
'videoID': bvid,
// 'cid': cid.value,
},
options: Options(
Options get _options => Options(
headers: {
'env': '',
'app-key': '',
@@ -519,7 +537,16 @@ class VideoDetailController extends GetxController
HttpHeaders.cookieHeader:
'buvid3= ; SESSDATA= ; bili_jct= ; DedeUserID= ; DedeUserID__ckMd5= ; sid= ',
},
),
);
Future _sponsorBlock() async {
dynamic result = await Request().get(
'${GStorage.blockServer}/api/skipSegments',
data: {
'videoID': bvid,
'cid': cid.value,
},
options: _options,
);
if (result.data is List && result.data.isNotEmpty) {
try {
@@ -608,6 +635,7 @@ class VideoDetailController extends GetxController
Request().post(
'${GStorage.blockServer}/api/viewedVideoSponsorTime',
queryParameters: {'UUID': item.UUID},
options: _options,
);
}
} catch (e) {
@@ -832,7 +860,7 @@ class VideoDetailController extends GetxController
var result = await VideoHttp.videoUrl(cid: cid.value, bvid: bvid);
if (result['status']) {
data = result['data'];
if (_enableSponsorBlock) {
if (enableSponsorBlock) {
await _sponsorBlock();
}
if (data.acceptDesc!.isNotEmpty && data.acceptDesc!.contains('试看')) {
@@ -974,4 +1002,371 @@ class VideoDetailController extends GetxController
}
return result;
}
List<PostSegmentModel>? list;
void onBlock(BuildContext context) {
PersistentBottomSheetController? ctr;
list ??= <PostSegmentModel>[];
if (list!.isEmpty) {
list!.add(
PostSegmentModel(
segment: Pair(first: 0, second: 0),
category: SegmentType.sponsor,
actionType: ActionType.skip,
),
);
}
ctr = plPlayerController.isFullScreen.value
? scaffoldKey.currentState?.showBottomSheet(
enableDrag: false,
(context) => _postPanel(ctr?.close),
)
: childKey.currentState?.showBottomSheet(
enableDrag: false,
(context) => _postPanel(ctr?.close),
);
}
Widget _postPanel(onClose) => StatefulBuilder(
builder: (context, setState) {
List<Widget> segmentWidget({
required int index,
required bool isFirst,
}) {
String value = Utils.timeFormat(isFirst
? list![index].segment.first
: list![index].segment.second);
return [
Text(
'${isFirst ? '开始' : '结束'}: $value',
),
const SizedBox(width: 5),
iconButton(
context: context,
size: 26,
tooltip: '使用当前位置',
icon: Icons.my_location,
onPressed: () {
setState(() {
if (isFirst) {
list![index].segment.first =
plPlayerController.positionSeconds.value;
} else {
list![index].segment.second =
plPlayerController.positionSeconds.value;
}
});
},
),
const SizedBox(width: 5),
iconButton(
context: context,
size: 26,
tooltip: '编辑',
icon: Icons.edit,
onPressed: () {
showDialog(
context: context,
builder: (_) {
String initV = value;
return AlertDialog(
content: TextFormField(
initialValue: value,
autofocus: true,
onChanged: (value) {
initV = value;
},
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[\d:]+'),
),
],
),
actions: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style: TextStyle(
color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () => Get.back(result: initV),
child: Text('确定'),
),
],
);
},
).then((res) {
if (res != null) {
try {
List<int> split = (res as String)
.split(':')
.toList()
.reversed
.toList()
.map((e) => int.parse(e))
.toList();
int duration = 0;
for (int i = 0; i < split.length; i++) {
duration += split[i] * pow(60, i).toInt();
}
if (duration <= (data.timeLength ?? 0) / 1000) {
setState(() {
if (isFirst) {
list![index].segment.first = duration;
} else {
list![index].segment.second = duration;
}
});
}
} catch (e) {
debugPrint(e.toString());
}
}
});
},
),
];
}
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
automaticallyImplyLeading: false,
titleSpacing: 16,
title: const Text('提交片段'),
actions: [
iconButton(
context: context,
tooltip: '添加片段',
onPressed: () {
setState(() {
list?.insert(
0,
PostSegmentModel(
segment: Pair(first: 0, second: 0),
category: SegmentType.sponsor,
actionType: ActionType.skip,
),
);
});
},
icon: Icons.add,
),
const SizedBox(width: 10),
iconButton(
context: context,
tooltip: '关闭',
onPressed: onClose,
icon: Icons.close,
),
const SizedBox(width: 16),
],
),
body: list?.isNotEmpty == true
? Stack(
children: [
SingleChildScrollView(
child: Column(
children: [
...List.generate(
list!.length,
(index) => Container(
margin: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 5,
),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onInverseSurface,
borderRadius: BorderRadius.circular(12),
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
children: [
...segmentWidget(
isFirst: true,
index: index,
),
const SizedBox(width: 16),
...segmentWidget(
isFirst: false,
index: index,
),
const Spacer(),
iconButton(
context: context,
size: 26,
icon: Icons.clear,
onPressed: () {
setState(() {
list!.removeAt(index);
});
},
),
],
),
const SizedBox(height: 8),
Row(
children: [
const Text('分类: '),
PopupMenuButton(
initialValue: list![index].category,
onSelected: (item) async {
setState(() {
list![index].category = item;
});
},
itemBuilder: (context) => SegmentType
.values
.map((item) =>
PopupMenuItem<SegmentType>(
value: item,
child: Text(item.title),
))
.toList(),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
list![index].category.title,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.colorScheme
.primary,
),
),
Icon(
size: 20,
Icons.keyboard_arrow_right,
color: Theme.of(context)
.colorScheme
.primary,
)
],
),
),
const SizedBox(width: 16),
const Text('ActionType: '),
PopupMenuButton(
initialValue: list![index].actionType,
onSelected: (item) async {
setState(() {
list![index].actionType = item;
});
},
itemBuilder: (context) => ActionType
.values
.map((item) =>
PopupMenuItem<ActionType>(
value: item,
child: Text(item.name),
))
.toList(),
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
list![index].actionType.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.colorScheme
.primary,
),
),
Icon(
size: 20,
Icons.keyboard_arrow_right,
color: Theme.of(context)
.colorScheme
.primary,
)
],
),
),
],
)
],
),
),
),
SizedBox(
height: 88 + MediaQuery.paddingOf(context).bottom,
),
],
),
),
Positioned(
right: 16,
bottom: 16 + MediaQuery.paddingOf(context).bottom,
child: FloatingActionButton(
tooltip: '提交',
onPressed: () {
Request()
.post(
'${GStorage.blockServer}/api/skipSegments',
queryParameters: {
'videoID': bvid,
'cid': cid.value,
'userID': GStorage.blockUserID,
'userAgent': Constants.userAgent,
'videoDuration': (data.timeLength ?? 0 / 1000),
},
data: {
'segments': list!
.map(
(item) => {
'segment': [
item.segment.first,
item.segment.second,
],
'category': item.category.name,
'actionType': item.actionType.name,
},
)
.toList(),
},
options: _options,
)
.then(
(res) {
if (res.statusCode == 200) {
Get.back();
SmartDialog.showToast('提交成功');
list?.clear();
} else {
SmartDialog.showToast(
'提交失败: ${{
400: '参数错误',
403: '被自动审核机制拒绝',
429: '重复提交太快',
409: '重复提交'
}[res.statusCode]}',
);
}
},
);
},
child: Icon(Icons.check),
),
)
],
)
: null,
);
},
);
}

View File

@@ -187,7 +187,11 @@ class _VideoReplyPanelState extends State<VideoReplyPanel>
feedBack();
dynamic oid = _videoReplyController.aid ??
IdUtils.bv2av(Get.parameters['bvid']!);
_videoReplyController.onReply(context, oid: oid);
_videoReplyController.onReply(
context,
oid: oid,
replyType: ReplyType.video,
);
},
tooltip: '发表评论',
child: const Icon(Icons.reply),

View File

@@ -13,7 +13,6 @@ import 'package:get/get.dart';
import 'package:PiliPalaX/http/video.dart';
import 'package:PiliPalaX/models/common/reply_type.dart';
import 'package:PiliPalaX/models/video/reply/emote.dart';
import 'package:PiliPalaX/models/video/reply/item.dart';
import 'package:PiliPalaX/pages/emote/index.dart';
import 'package:PiliPalaX/utils/feed_back.dart';
import 'package:PiliPalaX/pages/emote/view.dart';
@@ -517,9 +516,7 @@ class _ReplyPageState extends State<ReplyPage>
);
if (result['status']) {
SmartDialog.showToast(result['data']['success_toast']);
Get.back(result: {
'data': ReplyItemModel.fromJson(result['data']['reply'], ''),
});
Get.back(result: result['data']['reply']);
} else {
SmartDialog.showToast(result['msg']);
}

View File

@@ -3,6 +3,7 @@ import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPalaX/http/loading_state.dart';
import 'package:PiliPalaX/pages/video/detail/reply/widgets/reply_item_grpc.dart';
import 'package:PiliPalaX/pages/video/detail/reply_new/reply_page.dart';
import 'package:PiliPalaX/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPalaX/common/skeleton/video_reply.dart';
@@ -158,7 +159,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
replyType: widget.replyType,
needDivider: false,
onReply: () {
_onReply(firstFloor!);
_onReply(firstFloor!, -1);
},
upMid: _videoReplyReplyController.upMid,
);
@@ -237,7 +238,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
),
);
void _onReply(ReplyInfo? item) {
void _onReply(ReplyInfo? item, int index) {
dynamic oid = item?.oid.toInt();
dynamic root = item?.id.toInt();
dynamic parent = item?.id.toInt();
@@ -250,7 +251,7 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
oid: oid,
root: root,
parent: parent,
replyType: ReplyType.video,
replyType: widget.replyType,
replyItem: item,
savedReply: _savedReplies[key],
onSaveReply: (reply) {
@@ -274,20 +275,16 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
},
),
)
.then((value) {
// 完成评论,数据添加 // TODO: data cast
if (value != null && value['data'] != null) {
.then((res) {
if (res != null) {
_savedReplies[key] = null;
if (value['data'] is ReplyInfo) {
List<ReplyInfo> list =
_videoReplyReplyController.loadingState.value is Success
? (_videoReplyReplyController.loadingState.value as Success)
.response
: <ReplyInfo>[];
list.add(value['data']);
_videoReplyReplyController.loadingState.value =
LoadingState.success(list);
}
ReplyInfo replyInfo = Utils.replyCast(res);
List list = (_videoReplyReplyController.loadingState.value as Success?)
?.response ??
<ReplyInfo>[];
list.insert(index + 1, replyInfo);
_videoReplyReplyController.loadingState.value =
LoadingState.success(list);
}
});
}
@@ -320,11 +317,11 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
return ColoredBox(
color: _videoReplyReplyController.colorAnimation?.value ??
Theme.of(Get.context!).colorScheme.onInverseSurface,
child: _replyItem(loadingState.response[index]),
child: _replyItem(loadingState.response[index], index),
);
},
)
: _replyItem(loadingState.response[index]);
: _replyItem(loadingState.response[index], index);
}
} else if (loadingState is Error) {
return CustomScrollView(
@@ -355,14 +352,14 @@ class _VideoReplyReplyPanelState extends State<VideoReplyReplyPanel> {
}
}
Widget _replyItem(replyItem) {
Widget _replyItem(replyItem, index) {
return ReplyItemGrpc(
replyItem: replyItem,
replyLevel: widget.isDialogue ? '3' : '2',
showReplyRow: false,
replyType: widget.replyType,
onReply: () {
_onReply(replyItem);
_onReply(replyItem, index);
},
onDelete: (rpid, frpid) {
List list =

View File

@@ -85,8 +85,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// StreamSubscription<Duration>? _bufferedListener;
bool get isFullScreen => plPlayerController?.isFullScreen.value ?? false;
final scaffoldKey = GlobalKey<ScaffoldState>();
@override
void initState() {
super.initState();
@@ -520,7 +518,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
Expanded(
child: Scaffold(
key: scaffoldKey,
key: videoDetailController.childKey,
resizeToAvoidBottomInset: false,
body: Column(
children: [
@@ -578,7 +576,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
Expanded(
child: Scaffold(
key: scaffoldKey,
key: videoDetailController.childKey,
resizeToAvoidBottomInset: false,
body: Column(
children: [
@@ -630,7 +628,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
),
Expanded(
child: Scaffold(
key: scaffoldKey,
key: videoDetailController.childKey,
resizeToAvoidBottomInset: false,
body: Column(
children: [
@@ -685,7 +683,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
Expanded(
child: Expanded(
child: Scaffold(
key: scaffoldKey,
key: videoDetailController.childKey,
resizeToAvoidBottomInset: false,
body: Column(
children: [
@@ -785,7 +783,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
height: context.height -
(removeSafeArea ? 0 : MediaQuery.of(context).padding.top),
child: Scaffold(
key: scaffoldKey,
key: videoDetailController.childKey,
resizeToAvoidBottomInset: false,
body: Column(
children: [
@@ -1289,7 +1287,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// 展示二级回复
void replyReply(replyItem, id) {
scaffoldKey.currentState?.showBottomSheet(
videoDetailController.childKey.currentState?.showBottomSheet(
(context) => VideoReplyReplyPanel(
id: id,
// rcount: replyItem.rcount,
@@ -1304,14 +1302,14 @@ class _VideoDetailPageState extends State<VideoDetailPage>
// ai总结
showAiBottomSheet() {
scaffoldKey.currentState?.showBottomSheet(
videoDetailController.childKey.currentState?.showBottomSheet(
enableDrag: true,
(context) => AiDetail(modelResult: videoIntroController.modelResult),
);
}
showIntroDetail(videoDetail, videoTags) {
scaffoldKey.currentState?.showBottomSheet(
videoDetailController.childKey.currentState?.showBottomSheet(
enableDrag: true,
(context) => videoDetail is BangumiInfoModel
? bangumi.IntroDetail(
@@ -1339,7 +1337,7 @@ class _VideoDetailPageState extends State<VideoDetailPage>
context: context,
scaffoldState: isFullScreen
? videoDetailController.scaffoldKey.currentState
: scaffoldKey.currentState,
: videoDetailController.childKey.currentState,
).buildShowBottomSheet();
}
}

View File

@@ -65,7 +65,6 @@ class _HeaderControlState extends State<HeaderControl> {
RxString now = ''.obs;
Timer? clock;
late String defaultCDNService;
bool get isFullScreen => widget.controller!.isFullScreen.value;
@override
@@ -1450,20 +1449,37 @@ class _HeaderControlState extends State<HeaderControl> {
// ),
// fuc: () => _.screenshot(),
// ),
if (widget.videoDetailCtr?.enableSponsorBlock == true)
SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: '提交片段',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () => widget.videoDetailCtr?.onBlock(context),
icon: const Icon(
Icons.block,
size: 19,
color: Colors.white,
),
),
),
Obx(
() => widget.videoDetailCtr?.segmentList.isNotEmpty == true
? SizedBox(
width: 42,
height: 34,
child: IconButton(
tooltip: 'SponsorBlock',
tooltip: '片段信息',
style: ButtonStyle(
padding: WidgetStateProperty.all(EdgeInsets.zero),
),
onPressed: () =>
widget.videoDetailCtr?.showSponsorBlock(context),
icon: const Icon(
Icons.block,
widget.videoDetailCtr?.showSBDetail(context),
icon: Icon(
MdiIcons.advertisements,
size: 19,
color: Colors.white,
),

View File

@@ -1241,7 +1241,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
Obx(() {
if (plPlayerController.buffered.value ==
Duration.zero) {
return const Text('Buffering...',
return const Text('加载中...',
style: TextStyle(
color: Colors.white, fontSize: 12));
}

View File

@@ -2,6 +2,7 @@
import 'package:PiliPalaX/pages/member/new/member_page.dart';
import 'package:PiliPalaX/pages/member/new/widget/edit_profile_page.dart';
import 'package:PiliPalaX/pages/setting/navigation_bar_set.dart';
import 'package:PiliPalaX/pages/setting/sponsor_block_page.dart';
import 'package:PiliPalaX/pages/video/detail/introduction/widgets/create_fav_page.dart';
import 'package:PiliPalaX/pages/webview/webview_page.dart';
@@ -186,6 +187,9 @@ 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()),
];
}

View File

@@ -24,7 +24,7 @@ class GStorage {
static late final Box<dynamic> video;
static List<Pair<SegmentType, SkipType>> get blockSettings {
List<int> list = setting.get(
List list = setting.get(
SettingBoxKey.blockSettings,
defaultValue: List.generate(SegmentType.values.length, (_) => 1),
);
@@ -37,7 +37,7 @@ class GStorage {
}
static List<Color> get blockColor {
List<String> list = setting.get(
List list = setting.get(
SettingBoxKey.blockColor,
defaultValue: List.generate(SegmentType.values.length, (_) => ''),
);
@@ -255,6 +255,12 @@ class SettingBoxKey {
enableAi = 'enableAi',
disableLikeMsg = 'disableLikeMsg',
defaultHomePage = 'defaultHomePage',
previewQuality = 'previewQuality',
checkDynamic = 'checkDynamic',
dynamicPeriod = 'dynamicPeriod',
schemeVariant = 'schemeVariant',
// Sponsor Block
enableSponsorBlock = 'enableSponsorBlock',
blockSettings = 'blockSettings',
blockLimit = 'blockLimit',
@@ -263,10 +269,6 @@ class SettingBoxKey {
blockToast = 'blockToast',
blockServer = 'blockServer',
blockTrack = 'blockTrack',
previewQuality = 'previewQuality',
checkDynamic = 'checkDynamic',
dynamicPeriod = 'dynamicPeriod',
schemeVariant = 'schemeVariant',
// 弹幕相关设置 权重(云屏蔽) 屏蔽类型 显示区域 透明度 字体大小 弹幕时间 描边粗细 字体粗细
danmakuWeight = 'danmakuWeight',
@@ -304,7 +306,8 @@ class SettingBoxKey {
tabbarSort = 'tabbarSort', // 首页tabbar
dynamicBadgeMode = 'dynamicBadgeMode',
hiddenSettingUnlocked = 'hiddenSettingUnlocked',
enableGradientBg = 'enableGradientBg';
enableGradientBg = 'enableGradientBg',
navBarSort = 'navBarSort';
}
class LocalCacheKey {

View File

@@ -5,6 +5,7 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:PiliPalaX/grpc/app/main/community/reply/v1/reply.pb.dart';
import 'package:PiliPalaX/http/member.dart';
import 'package:PiliPalaX/http/search.dart';
import 'package:PiliPalaX/http/video.dart';
@@ -25,6 +26,27 @@ import 'package:url_launcher/url_launcher.dart';
class Utils {
static final Random random = Random();
static ReplyInfo replyCast(res) {
Map? emote = res['content']['emote'];
emote?.forEach((key, value) {
value['size'] = value['meta']['size'];
});
return ReplyInfo.create()
..mergeFromProto3Json(
res
..['id'] = res['rpid']
..['member']['name'] = res['member']['uname']
..['member']['face'] = res['member']['avatar']
..['member']['level'] = res['member']['level_info']['current_level']
..['member']['vipStatus'] = res['member']['vip']['vipStatus']
..['member']['vipType'] = res['member']['vip']['vipType']
..['member']['officialVerifyType'] =
res['member']['official_verify']['type']
..['content']['emote'] = emote,
ignoreUnknownFields: true,
);
}
static bool isDefault(int attr) {
return (attr & 2) == 0;
}