Compare commits

...

18 Commits

Author SHA1 Message Date
bggRGjQaUbCoE
d8126a87cd mod: delay requerying fav state
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-15 09:42:13 +08:00
bggRGjQaUbCoE
0f7be5ec30 fix: get whisper content
related #170

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-15 09:27:02 +08:00
bggRGjQaUbCoE
b9d223369a fix: #170 from orz12/main
related #167 #168 #169
2025-01-15 09:22:53 +08:00
bggRGjQaUbCoE
d97b2e223c mod: def preview quality
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-15 09:14:25 +08:00
bggRGjQaUbCoE
230325d171 opt: image radius
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-15 00:30:03 +08:00
bggRGjQaUbCoE
229901de96 mod: quick fav bangumi
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 20:33:29 +08:00
bggRGjQaUbCoE
d8c23a3d8c opt: member page
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 20:14:46 +08:00
bggRGjQaUbCoE
e87a46706e mod: nav bar
Closes #138

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 19:05:19 +08:00
bggRGjQaUbCoE
9ebf4b4533 mod: viewpoint sheet
Closes #166

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 18:12:14 +08:00
bggRGjQaUbCoE
125168cfb9 mod: show total ss
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 15:49:31 +08:00
bggRGjQaUbCoE
5983670c83 feat: show total season/series
Closes #164

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 15:27:34 +08:00
bggRGjQaUbCoE
ba8d7b871c opt: silent info widget
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 13:46:02 +08:00
bggRGjQaUbCoE
e2761836bf fix: webview
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 12:59:36 +08:00
bggRGjQaUbCoE
46cd633c4a opt: logout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 12:39:52 +08:00
bggRGjQaUbCoE
cb3f72959f opt: logout
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 12:26:26 +08:00
bggRGjQaUbCoE
c1104c931b feat: hot rcmd entrance
Closes #151

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 12:13:09 +08:00
bggRGjQaUbCoE
d4b005f6ae mod: remove incorrect ban url
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 10:06:17 +08:00
bggRGjQaUbCoE
471c95abe8 mod: memberpage: show silent status
Closes #158

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-01-14 09:50:40 +08:00
35 changed files with 904 additions and 303 deletions

View File

@@ -47,6 +47,41 @@ Widget imageview(
} else if (picArr.length == 2) {
imageWidth = imageHeight = 2 * imageWidth;
}
BorderRadius borderRadius(index) {
if (picArr.length == 1) {
return BorderRadius.circular(12);
}
final int row = picArr.length == 4 ? 2 : 3;
return BorderRadius.only(
topLeft: Radius.circular(
(index - row >= 0 ||
((index - 1) >= 0 && (index - 1) % row < index % row))
? 0
: 12,
),
topRight: Radius.circular(
(index - row >= 0 ||
((index + 1) < picArr.length &&
(index + 1) % row > index % row))
? 0
: 12,
),
bottomLeft: Radius.circular(
(index + row < picArr.length ||
((index - 1) >= 0 && (index - 1) % row < index % row))
? 0
: 12,
),
bottomRight: Radius.circular(
(index + row < picArr.length ||
((index + 1) < picArr.length &&
(index + 1) % row > index % row))
? 0
: 12,
),
);
}
return NineGridView(
type: NineGridType.weiBo,
margin: const EdgeInsets.only(top: 6),
@@ -75,14 +110,36 @@ Widget imageview(
alignment: Alignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
borderRadius: borderRadius(index),
child: NetworkImgLayer(
radius: 0,
src: picArr[index].url,
width: imageWidth,
height: imageHeight,
isLongPic: () => picArr[index].isLongPic,
callback: () =>
picArr[index].safeWidth <= picArr[index].safeHeight,
getPlaceHolder: () {
return Container(
width: imageWidth,
height: imageHeight,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.4),
borderRadius: borderRadius(index),
),
child: Center(
child: Image.asset(
'assets/images/loading.png',
width: imageWidth,
height: imageHeight,
cacheWidth: imageWidth.cacheSize(context),
),
),
);
},
),
),
if (picArr[index].isLongPic)

View File

@@ -92,14 +92,12 @@ class _InteractiveviewerGalleryState extends State<InteractiveviewerGallery>
int? currentIndex;
late List<bool> _thumbList;
late int _quality;
late final int _quality = GStorage.previewQ;
@override
void initState() {
super.initState();
_quality =
GStorage.setting.get(SettingBoxKey.previewQuality, defaultValue: 80);
_thumbList = List.generate(widget.sources.length, (_) => true);
_pageController = PageController(initialPage: widget.initIndex);

View File

@@ -21,6 +21,7 @@ class NetworkImgLayer extends StatelessWidget {
this.imageBuilder,
this.isLongPic,
this.callback,
this.getPlaceHolder,
});
final String? src;
@@ -36,71 +37,61 @@ class NetworkImgLayer extends StatelessWidget {
final ImageWidgetBuilder? imageBuilder;
final Function? isLongPic;
final Function? callback;
final Function? getPlaceHolder;
@override
Widget build(BuildContext context) {
double radius = this.radius != null
? this.radius!
: type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x;
return src.isNullOrEmpty.not
? radius != 0
? ClipRRect(
borderRadius: BorderRadius.circular(radius),
child: _buildImage(context),
)
: _buildImage(context)
: getPlaceHolder?.call() ?? placeholder(context);
}
Widget _buildImage(context) {
late final int defaultImgQuality = GlobalData().imgQuality;
bool thumbnail = true;
int? memCacheWidth, memCacheHeight;
if (callback?.call() == true || width <= height) {
memCacheWidth = width.cacheSize(context);
} else {
memCacheHeight = height.cacheSize(context);
}
Widget res = src != '' && src != null
? ClipRRect(
clipBehavior: Clip.antiAlias,
borderRadius: BorderRadius.circular(
radius != null
? radius!
: type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x,
),
child: Builder(
builder: (context) => CachedNetworkImage(
imageUrl:
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
width: width,
height: ignoreHeight == null || ignoreHeight == false
? height
: null,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
fit: BoxFit.cover,
alignment: isLongPic?.call() == true
? Alignment.topCenter
: Alignment.center,
fadeOutDuration:
fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration:
fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.low,
// errorWidget: (BuildContext context, String url, Object error) =>
// placeholder(context),
placeholder: (BuildContext context, String url) =>
placeholder(context),
imageBuilder: imageBuilder,
// errorListener: (value) {
// thumbnail = false;
// if (context.mounted) {
// (context as Element).markNeedsBuild();
// }
// },
),
),
)
: placeholder(context);
if (semanticsLabel != null) {
return Semantics(
label: semanticsLabel,
child: res,
);
}
return res;
return CachedNetworkImage(
imageUrl:
'${src?.startsWith('//') == true ? 'https:$src' : src?.http2https}${type != 'emote' && thumbnail ? '@${quality ?? defaultImgQuality}q.webp' : ''}',
width: width,
height: ignoreHeight == null || ignoreHeight == false ? height : null,
memCacheWidth: memCacheWidth,
memCacheHeight: memCacheHeight,
fit: BoxFit.cover,
alignment:
isLongPic?.call() == true ? Alignment.topCenter : Alignment.center,
fadeOutDuration: fadeOutDuration ?? const Duration(milliseconds: 120),
fadeInDuration: fadeInDuration ?? const Duration(milliseconds: 120),
filterQuality: FilterQuality.low,
// errorWidget: (BuildContext context, String url, Object error) =>
// placeholder(context),
placeholder: (BuildContext context, String url) =>
getPlaceHolder?.call() ?? placeholder(context),
imageBuilder: imageBuilder,
// errorListener: (value) {
// thumbnail = false;
// if (context.mounted) {
// (context as Element).markNeedsBuild();
// }
// },
);
}
Widget placeholder(BuildContext context) {
@@ -108,14 +99,15 @@ class NetworkImgLayer extends StatelessWidget {
return Container(
width: width,
height: height,
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface.withOpacity(0.4),
borderRadius: BorderRadius.circular(type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x),
borderRadius: BorderRadius.circular(
type == 'avatar'
? 50
: type == 'emote'
? 0
: StyleString.imgRadius.x,
),
),
child: type == 'bg'
? const SizedBox()

View File

@@ -328,6 +328,8 @@ class Api {
static const String spaceFav = '/x/v3/fav/folder/space';
static const String seasonSeries = '/x/polymer/web-space/seasons_series_list';
// 用户名片信息
static const String memberCardInfo = '/x/web-interface/card';

View File

@@ -154,6 +154,25 @@ class MemberHttp {
}
}
static Future<LoadingState> seasonSeriesList({
required int? mid,
required int pn,
}) async {
dynamic res = await Request().get(
Api.seasonSeries,
queryParameters: {
'mid': mid,
'page_num': pn,
'page_size': 10,
},
);
if (res.data['code'] == 0) {
return LoadingState.success(res.data['data']['items_lists']);
} else {
return LoadingState.error(res.data['message']);
}
}
static Future<LoadingState> spaceArchive({
required ContributeType type,
required int? mid,

View File

@@ -53,6 +53,7 @@ class Data {
dynamic digitalButton;
dynamic entry;
dynamic live;
UgcSeason? ugcSeason;
Data({
this.relation,
@@ -79,9 +80,22 @@ class Data {
this.digitalButton,
this.entry,
this.live,
this.ugcSeason,
});
factory Data.fromJson(Map<String, dynamic> json) => _$DataFromJson(json);
Map<String, dynamic> toJson() => _$DataToJson(this);
}
class UgcSeason {
int? count;
UgcSeason({
this.count,
});
UgcSeason.fromJson(Map<String, dynamic> json) {
count = json['count'];
}
}

View File

@@ -62,6 +62,9 @@ Data _$DataFromJson(Map<String, dynamic> json) => Data(
digitalButton: json['digital_button'],
entry: json['entry'],
live: json['live'],
ugcSeason: json['ugc_season'] != null
? UgcSeason.fromJson(json['ugc_season'])
: null,
);
Map<String, dynamic> _$DataToJson(Data instance) => <String, dynamic>{

View File

@@ -30,6 +30,8 @@ class _AboutPageState extends State<AboutPage> {
'https://github.com/guozhigq/pilipala';
static const String _upstreamUrl = 'https://github.com/orz12/PiliPalaX';
late int _pressCount = 0;
@override
void initState() {
super.initState();
@@ -51,12 +53,36 @@ class _AboutPageState extends State<AboutPage> {
appBar: AppBar(title: Text('关于')),
body: ListView(
children: [
ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150),
child: ExcludeSemantics(
GestureDetector(
onTap: () {
_pressCount++;
if (_pressCount == 5) {
_pressCount = 0;
showDialog(
context: context,
builder: (context) {
String text = '';
return AlertDialog(
content: TextField(
onChanged: (value) => text = value,
onSubmitted: (value) {
Get.back();
Get.toNamed('/webview', parameters: {'url': text});
},
),
);
},
);
}
},
child: ConstrainedBox(
constraints: const BoxConstraints(maxHeight: 150),
child: ExcludeSemantics(
child: Image.asset(
'assets/images/logo/logo_android_2.png',
)),
'assets/images/logo/logo_android_2.png',
),
),
),
),
ListTile(
title: Text('PiliPlus',

View File

@@ -62,6 +62,9 @@ class BangumiIntroController extends CommonController {
List delMediaIdsNew = [];
dynamic userInfo;
late final enableQuickFav =
GStorage.setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
@override
void onInit() {
super.onInit();
@@ -281,7 +284,30 @@ class BangumiIntroController extends CommonController {
}
// (取消)收藏 bangumi
Future actionFavVideo() async {
Future actionFavVideo({type = 'choose'}) async {
// 收藏至默认文件夹
if (type == 'default') {
SmartDialog.showLoading(msg: '请求中');
await queryVideoInFolder();
int defaultFolderId = favFolderData.value.list!.first.id!;
int favStatus = favFolderData.value.list!.first.favState!;
var result = await VideoHttp.favVideo(
aid: epId,
type: 24,
addIds: favStatus == 0 ? '$defaultFolderId' : '',
delIds: favStatus == 1 ? '$defaultFolderId' : '',
);
SmartDialog.dismiss();
if (result['status']) {
// 重新获取收藏状态
await Future.delayed(const Duration(milliseconds: 255));
await queryBangumiLikeCoinFav();
SmartDialog.showToast('✅ 快速收藏/取消收藏成功');
} else {
SmartDialog.showToast(result['msg']);
}
return;
}
try {
for (var i in favFolderData.value.list!.toList()) {
if (i.favState == 1) {

View File

@@ -173,11 +173,26 @@ class _BangumiInfoState extends State<BangumiInfo>
}
// 收藏
showFavBottomSheet() {
showFavBottomSheet({type = 'tap'}) {
if (bangumiIntroController.userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
// 快速收藏 &
// 点按 收藏至默认文件夹
// 长按选择文件夹
if (bangumiIntroController.enableQuickFav) {
if (type == 'tap') {
bangumiIntroController.actionFavVideo(type: 'default');
} else {
_showFavBottomSheet();
}
} else if (type != 'longPress') {
_showFavBottomSheet();
}
}
_showFavBottomSheet() {
showModalBottomSheet(
context: context,
useSafeArea: true,
@@ -522,6 +537,7 @@ class _BangumiInfoState extends State<BangumiInfo>
icon: const Icon(FontAwesomeIcons.star),
selectIcon: const Icon(FontAwesomeIcons.solidStar),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: bangumiIntroController.hasFav.value,
loadingStatus: false,
semanticsLabel: '收藏',
@@ -592,6 +608,7 @@ class _BangumiInfoState extends State<BangumiInfo>
() => ActionRowItem(
icon: const Icon(FontAwesomeIcons.heart),
onTap: () => showFavBottomSheet(),
onLongPress: () => showFavBottomSheet(type: 'longPress'),
selectStatus: videoIntroController.hasFav.value,
loadingStatus: widget.isLoading,
text: !widget.isLoading

View File

@@ -36,11 +36,14 @@ class _HomePageState extends State<HomePage>
@override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(toolbarHeight: 0),
body: Column(
children: [
if (!_homeController.useSideBar) customAppBar,
if (!_homeController.useSideBar &&
context.orientation == Orientation.portrait)
customAppBar,
if (_homeController.tabs.length > 1) ...[
const SizedBox(height: 4),
Material(

View File

@@ -1,10 +1,14 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:get/get.dart';
class HotController extends CommonController {
// int idx = 0;
late RxBool showHotRcmd = GStorage.showHotRcmd.obs;
@override
void onInit() {
super.onInit();

View File

@@ -3,6 +3,9 @@ import 'dart:async';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/video_card_h.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/tab_type.dart';
import 'package:PiliPlus/pages/rank/view.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:get/get.dart';
@@ -56,6 +59,27 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
super.dispose();
}
Widget _buildEntranceItem({
required String iconUrl,
required String title,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(width: 35, height: 35, iconUrl),
const SizedBox(height: 2),
Text(
title,
style: TextStyle(fontSize: 12),
),
],
),
);
}
@override
Widget build(BuildContext context) {
super.build(context);
@@ -67,6 +91,67 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
physics: const AlwaysScrollableScrollPhysics(),
controller: _hotController.scrollController,
slivers: [
SliverToBoxAdapter(
child: Obx(
() => _hotController.showHotRcmd.value
? Padding(
padding:
const EdgeInsets.only(left: 12, top: 12, right: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildEntranceItem(
iconUrl:
'http://i0.hdslb.com/bfs/archive/a3f11218aaf4521b4967db2ae164ecd3052586b9.png',
title: '排行榜',
onTap: () {
try {
HomeController homeController =
Get.find<HomeController>();
int index = homeController.tabs.indexWhere(
(item) => item['type'] == TabType.rank,
);
if (index != -1) {
homeController.tabController.animateTo(index);
} else {
Get.to(
Scaffold(
appBar: AppBar(title: const Text('排行榜')),
body: RankPage(),
),
);
}
} catch (_) {}
},
),
_buildEntranceItem(
iconUrl:
'http://i0.hdslb.com/bfs/archive/552ebe8c4794aeef30ebd1568b59ad35f15e21ad.png',
title: '每周必看',
onTap: () {
Utils.handleWebview(
'https://www.bilibili.com/h5/weekly-recommend',
inApp: true,
);
},
),
_buildEntranceItem(
iconUrl:
'http://i0.hdslb.com/bfs/archive/3693ec9335b78ca57353ac0734f36a46f3d179a9.png',
title: '入站必刷',
onTap: () {
Utils.handleWebview(
'https://www.bilibili.com/h5/good-history',
inApp: true,
);
},
),
],
),
)
: const SizedBox.shrink(),
),
),
SliverPadding(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,

View File

@@ -165,105 +165,64 @@ class _MainAppState extends State<MainApp>
}
}
},
child: LayoutBuilder(
builder: (context, constriants) {
bool isPortait = constriants.maxHeight > constriants.maxWidth;
return Scaffold(
resizeToAvoidBottomInset: false,
extendBody: true,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (useSideBar) ...[
SizedBox(
width: context.width * 0.04 +
40 +
MediaQuery.of(context).padding.left,
child: Obx(
() => _mainController.navigationBars.length > 1
? NavigationRail(
groupAlignment: 1,
minWidth: context.width * 0.0286 + 28.56,
backgroundColor: Colors.transparent,
selectedIndex:
_mainController.selectedIndex.value,
onDestinationSelected: setIndex,
labelType: NavigationRailLabelType.none,
leading: userAndSearchVertical,
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: Scaffold(
resizeToAvoidBottomInset: false,
extendBody: true,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (useSideBar || context.orientation == Orientation.landscape)
Obx(
() => _mainController.navigationBars.length > 1
? NavigationRail(
groupAlignment: 0.5,
selectedIndex: _mainController.selectedIndex.value,
onDestinationSelected: setIndex,
labelType: NavigationRailLabelType.selected,
leading: userAndSearchVertical,
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']),
),
child: userAndSearchVertical,
),
),
),
] else if (!isPortait)
Obx(
() => _mainController.navigationBars.length > 1
? NavigationRail(
onDestinationSelected: setIndex,
selectedIndex: _mainController.selectedIndex.value,
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(),
)
: const SizedBox.shrink(),
),
VerticalDivider(
width: 1,
indent: MediaQuery.of(context).padding.top,
endIndent: MediaQuery.of(context).padding.bottom,
color:
Theme.of(context).colorScheme.outline.withOpacity(0.06),
),
Expanded(
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.pageController,
children: _mainController.pages,
),
),
if (useSideBar) SizedBox(width: context.width * 0.004),
],
)
.toList(),
)
: Container(
padding: EdgeInsets.only(
top: MediaQuery.paddingOf(context).top + 10,
),
width: 56,
child: userAndSearchVertical,
),
),
VerticalDivider(
width: 1,
indent: MediaQuery.of(context).padding.top,
endIndent: MediaQuery.of(context).padding.bottom,
color: Theme.of(context).colorScheme.outline.withOpacity(0.06),
),
bottomNavigationBar: useSideBar || !isPortait
Expanded(
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: _mainController.pageController,
children: _mainController.pages,
),
),
],
),
bottomNavigationBar:
useSideBar || context.orientation == Orientation.landscape
? null
: StreamBuilder(
stream: _mainController.hideTabBar
@@ -313,10 +272,6 @@ class _MainAppState extends State<MainApp>
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(
@@ -340,8 +295,6 @@ class _MainAppState extends State<MainApp>
);
},
),
);
},
),
);
}

View File

@@ -0,0 +1,33 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
class SeasonSeriesController extends CommonController {
SeasonSeriesController(this.mid);
final int mid;
@override
void onInit() {
super.onInit();
queryData();
}
@override
bool customHandleResponse(Success response) {
Map data = response.response;
List list = ((data['seasons_list'] as List?) ?? []) +
((data['series_list'] as List?) ?? []);
if (currentPage != 0 && loadingState.value is Success) {
list.insertAll(0, (loadingState.value as Success).response);
}
isEnd = list.length >= ((data['page']['total'] as int?) ?? 0);
loadingState.value = LoadingState.success(list);
return true;
}
@override
Future<LoadingState> customGetData() => MemberHttp.seasonSeriesList(
mid: mid,
pn: currentPage,
);
}

View File

@@ -0,0 +1,112 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/controller.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/widget/season_series_card.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class SeasonSeriesPage extends StatefulWidget {
const SeasonSeriesPage({
super.key,
required this.mid,
this.heroTag,
});
final int mid;
final String? heroTag;
@override
State<SeasonSeriesPage> createState() => _SeasonSeriesPageState();
}
class _SeasonSeriesPageState extends State<SeasonSeriesPage>
with AutomaticKeepAliveClientMixin {
late final _controller = Get.put(
SeasonSeriesController(widget.mid),
tag: widget.heroTag,
);
@override
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
super.build(context);
return Obx(() => _buildBody(_controller.loadingState.value));
}
Widget _buildBody(LoadingState loadingState) {
return switch (loadingState) {
Loading() => loadingWidget,
Success() => (loadingState.response as List?)?.isNotEmpty == true
? CustomScrollView(
slivers: [
SliverPadding(
padding: EdgeInsets.only(
top: StyleString.safeSpace - 5,
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
sliver: SliverGrid(
gridDelegate: SliverGridDelegateWithExtentAndRatio(
mainAxisSpacing: 2,
maxCrossAxisExtent: Grid.mediumCardWidth * 2,
childAspectRatio: StyleString.aspectRatio * 2.2,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (index == loadingState.response.length - 1) {
_controller.onLoadMore();
}
return SeasonSeriesCard(
item: loadingState.response[index],
onTap: () {
dynamic item = loadingState.response[index];
bool isSeason = item['meta']['season_id'] != null;
dynamic id = isSeason
? item['meta']['season_id']
: item['meta']['series_id'];
Get.to(
Scaffold(
appBar: AppBar(
title: Text(item['meta']['name']),
),
body: MemberVideo(
type: isSeason
? ContributeType.season
: ContributeType.series,
heroTag: widget.heroTag,
mid: widget.mid,
seasonId: isSeason ? id : null,
seriesId: isSeason ? null : id,
title: item['meta']['name'],
),
),
);
},
);
},
childCount: loadingState.response.length,
),
),
),
],
)
: scrollErrorWidget(
callback: () {
_controller.onReload();
},
),
Error() => scrollErrorWidget(
errMsg: loadingState.errMsg,
callback: () {
_controller.onReload();
},
),
LoadingState() => throw UnimplementedError(),
};
}
}

View File

@@ -0,0 +1,103 @@
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image_save.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
class SeasonSeriesCard extends StatelessWidget {
const SeasonSeriesCard({
super.key,
required this.item,
required this.onTap,
});
final dynamic item;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return InkWell(
onLongPress: () {
imageSaveDialog(
context: context,
title: item['meta']['name'],
cover: item['meta']['cover'],
);
},
onTap: onTap,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace,
vertical: 5,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AspectRatio(
aspectRatio: StyleString.aspectRatio,
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints boxConstraints) {
final double maxWidth = boxConstraints.maxWidth;
final double maxHeight = boxConstraints.maxHeight;
return Stack(
children: [
NetworkImgLayer(
src: item['meta']['cover'],
width: maxWidth,
height: maxHeight,
),
PBadge(
text:
'${item['meta']['season_id'] != null ? '合集' : '列表'}: ${item['meta']['total']}',
bottom: 6.0,
right: 6.0,
),
],
);
},
),
),
videoContent(context)
],
),
),
);
}
Widget videoContent(context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 6, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item['meta']['name'],
textAlign: TextAlign.start,
style: TextStyle(
fontSize: Theme.of(context).textTheme.bodyMedium!.fontSize,
height: 1.42,
letterSpacing: 0.3,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
const Spacer(),
Text(
Utils.dateFormat(item['meta']['ptime']),
maxLines: 1,
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
height: 1,
color: Theme.of(context).colorScheme.outline,
overflow: TextOverflow.clip,
),
),
const Spacer(),
],
),
),
);
}
}

View File

@@ -74,24 +74,30 @@ class _MemberVideoState extends State<MemberVideo>
delegate: CustomSliverPersistentHeaderDelegate(
extent: 40,
bgColor: Theme.of(context).colorScheme.surface,
child: Container(
child: SizedBox(
height: 40,
padding: const EdgeInsets.fromLTRB(12, 0, 6, 0),
child: Row(
children: [
const SizedBox(width: 8),
Obx(
() => Text(
_controller.count.value != -1
? '${_controller.count.value}视频'
: '',
style: const TextStyle(fontSize: 13),
() => Padding(
padding: const EdgeInsets.only(left: 6),
child: Text(
_controller.count.value != -1
? '${_controller.count.value}视频'
: '',
style: const TextStyle(fontSize: 13),
),
),
),
Obx(
() => _controller.episodicButton.value.uri != null
? Container(
height: 35,
padding: const EdgeInsets.only(left: 5),
padding: EdgeInsets.only(
left: _controller.count.value != -1
? 6
: 0),
child: TextButton.icon(
onPressed: _controller.toViewPlayAll,
icon: Icon(
@@ -146,6 +152,7 @@ class _MemberVideoState extends State<MemberVideo>
),
),
),
const SizedBox(width: 8),
],
),
),

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/article/member_article.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/audio/member_audio.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/season_series/season_series_page.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/content/video/member_video.dart';
import 'package:PiliPlus/pages/member/new/content/member_contribute/member_contribute_ctr.dart';
import 'package:flutter/material.dart';
@@ -110,6 +111,10 @@ class _MemberContributeState extends State<MemberContribute>
seriesId: item.seriesId,
title: item.title,
),
'ugcSeason' => SeasonSeriesPage(
mid: widget.mid,
heroTag: widget.heroTag,
),
_ => Center(child: Text(item.title!))
},
)

View File

@@ -32,6 +32,27 @@ class MemberContributeCtr extends CommonController
if (contribute.items?.isNullOrEmpty == false &&
contribute.items!.length > 1) {
items = contribute.items;
// if (_ctr.ugcSeasonCount != null) {
// int currentSeasonCount =
// items!.where((item) => item.param == 'season_video').length;
// if (currentSeasonCount < _ctr.ugcSeasonCount!) {
// items!.add(
// Item(
// param: 'ugcSeason',
// title: '全部合集/列表',
// ),
// );
// }
// }
// show if exist
if (_ctr.hasSeasonOrSeries == true) {
items!.add(
Item(
param: 'ugcSeason',
title: '全部合集/列表',
),
);
}
tabs = items!.map((item) => Tab(text: item.title)).toList();
tabController = TabController(
vsync: this,

View File

@@ -1,6 +1,7 @@
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/models/space/tab2.dart';
import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/utils/storage.dart';
@@ -8,6 +9,7 @@ import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:share_plus/share_plus.dart';
enum MemberTabType { none, home, dynamic, contribute, favorite, bangumi }
@@ -30,6 +32,7 @@ class MemberControllerNew extends CommonController
List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs;
double? top;
bool? hasSeasonOrSeries;
@override
void onInit() {
@@ -40,17 +43,33 @@ class MemberControllerNew extends CommonController
dynamic live;
int? silence;
String? endTime;
@override
bool customHandleResponse(Success response) {
username = response.response?.card?.name ?? '';
isFollow.value = response.response?.card?.relation?.isFollow == 1;
relation.value = response.response?.relSpecial == 1
? 2
: response.response?.relation ?? 1;
tab2 = response.response.tab2;
live = response.response?.live;
Data data = response.response;
username = data.card?.name ?? '';
isFollow.value = data.card?.relation?.isFollow == 1;
relation.value = data.relSpecial == 1 ? 2 : data.relation ?? 1;
tab2 = data.tab2;
live = data.live;
silence = data.card?.silence;
if ((data.ugcSeason?.count != null && data.ugcSeason?.count != 0) ||
data.series?.item?.isNotEmpty == true) {
hasSeasonOrSeries = true;
}
if (data.card?.endTime != null) {
if (data.card!.endTime == 0) {
endTime = ': 永久封禁';
} else if (data.card!.endTime! >
DateTime.now().millisecondsSinceEpoch ~/ 1000) {
endTime =
':至 ${DateFormat('yyyy-MM-dd HH:mm:ss').format(DateTime.fromMillisecondsSinceEpoch(data.card!.endTime! * 1000))}';
}
}
if (tab2 != null && tab2!.isNotEmpty) {
if (!response.response.tab.toJson().values.contains(true) &&
if (!data.tab!.toJson().values.contains(true) &&
tab2!.first.param == 'home') {
// remove empty home tab
tab2!.removeAt(0);
@@ -64,11 +83,11 @@ class MemberControllerNew extends CommonController
});
}
if (initialIndex == -1) {
if (response.response.defaultTab == 'video') {
response.response.defaultTab = 'dynamic';
if (data.defaultTab == 'video') {
data.defaultTab = 'dynamic';
}
initialIndex = tab2!.indexWhere((item) {
return item.param == response.response.defaultTab;
return item.param == data.defaultTab;
});
}
tabs = tab2!.map((item) => Tab(text: item.title ?? '')).toList();

View File

@@ -318,6 +318,8 @@ class _MemberPageNewState extends State<MemberPageNew>
images: userState.response.images,
onFollow: () => _userController.onFollow(context),
live: _userController.live,
silence: _userController.silence,
endTime: _userController.endTime,
),
),
),

View File

@@ -19,6 +19,8 @@ class UserInfoCard extends StatelessWidget {
required this.isFollow,
required this.onFollow,
this.live,
this.silence,
this.endTime,
});
final bool isV;
@@ -29,6 +31,8 @@ class UserInfoCard extends StatelessWidget {
final space.Images images;
final VoidCallback onFollow;
final dynamic live;
final int? silence;
final String? endTime;
@override
Widget build(BuildContext context) {
@@ -172,48 +176,47 @@ class UserInfoCard extends StatelessWidget {
),
),
if (card.officialVerify?.desc?.isNotEmpty == true)
Padding(
padding: const EdgeInsets.only(left: 20, top: 8, right: 20),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.onInverseSurface),
child: Text.rich(
TextSpan(
children: [
if (card.officialVerify?.icon?.isNotEmpty == true) ...[
WidgetSpan(
child: Container(
padding: const EdgeInsets.all(0.1),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
),
child: CachedNetworkImage(
width: 18,
height: 18,
imageUrl: card.officialVerify!.icon!.http2https,
),
Container(
margin: const EdgeInsets.only(left: 20, top: 8, right: 20),
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.onInverseSurface,
),
child: Text.rich(
TextSpan(
children: [
if (card.officialVerify?.icon?.isNotEmpty == true) ...[
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
),
child: CachedNetworkImage(
width: 18,
height: 18,
imageUrl: card.officialVerify!.icon!.http2https,
),
),
TextSpan(
text: ' ',
)
],
),
TextSpan(
text: card.officialVerify!.spliceTitle!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.7),
),
text: ' ',
)
],
),
TextSpan(
text: card.officialVerify!.spliceTitle!,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.colorScheme
.onSurface
.withOpacity(0.7),
),
)
],
),
),
),
@@ -259,17 +262,45 @@ class UserInfoCard extends StatelessWidget {
],
),
),
// if (card.spaceTagBottom != null && card.spaceTagBottom!.isNotEmpty)
// Padding(
// padding: const EdgeInsets.only(left: 20, top: 8, right: 20),
// child: Wrap(
// spacing: 5,
// runSpacing: 8,
// children: card.spaceTagBottom!
// .map((item) => Text(item.title ?? ''))
// .toList(),
// ),
// ),
if (silence == 1)
Builder(builder: (context) {
bool isLight = Theme.of(context).brightness == Brightness.light;
return Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6),
color: isLight
? Theme.of(context).colorScheme.errorContainer
: Theme.of(context).colorScheme.error,
),
margin: const EdgeInsets.only(left: 20, top: 8, right: 20),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Text.rich(
TextSpan(
children: [
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: Icon(
Icons.info,
size: 17,
color: isLight
? Theme.of(context).colorScheme.onErrorContainer
: Theme.of(context).colorScheme.onError,
),
),
TextSpan(
text: ' 该账号封禁中${endTime ?? ''}',
style: TextStyle(
color: isLight
? Theme.of(context).colorScheme.onErrorContainer
: Theme.of(context).colorScheme.onError,
),
),
],
),
),
);
}),
];
_buildRight(BuildContext context) => Column(
@@ -393,25 +424,24 @@ class UserInfoCard extends StatelessWidget {
],
);
_buildBadge(BuildContext context) => Container(
padding: const EdgeInsets.all(0.01),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
_buildBadge(BuildContext context) => IgnorePointer(
child: DecoratedBox(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
),
child: card.officialVerify?.icon?.isNotEmpty == true
? CachedNetworkImage(
imageUrl: card.officialVerify!.icon!.http2https,
width: 24,
height: 24,
)
: Image.asset(
'assets/images/big-vip.png',
width: 24,
height: 24,
),
),
child: card.officialVerify?.icon?.isNotEmpty == true
? NetworkImgLayer(
src: card.officialVerify?.icon,
radius: null,
width: 24,
height: 24,
quality: 100,
)
: Image.asset(
'assets/images/big-vip.png',
width: 24,
height: 24,
),
);
_buildAvatar(BuildContext context) => Hero(

View File

@@ -1,3 +1,4 @@
import 'package:PiliPlus/utils/login.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
@@ -56,12 +57,18 @@ class MineController extends GetxController {
userInfo.value = res['data'];
GStorage.userInfo.put('userInfoCache', res['data']);
isLogin.value = true;
} else {
LoginUtils.onLogout();
return;
}
} else {
SmartDialog.showToast(res['msg']);
if (res['msg'] == '账号未登录') {
LoginUtils.onLogout();
return;
}
}
await queryUserStatOwner();
return res;
queryUserStatOwner();
}
Future queryUserStatOwner() async {
@@ -69,7 +76,6 @@ class MineController extends GetxController {
if (res['status']) {
userStat.value = res['data'];
}
return res;
}
static onChangeAnonymity(BuildContext context) {

View File

@@ -15,6 +15,7 @@ import 'package:PiliPlus/models/video/play/CDN.dart';
import 'package:PiliPlus/models/video/play/quality.dart';
import 'package:PiliPlus/models/video/play/subtitle.dart';
import 'package:PiliPlus/pages/home/controller.dart';
import 'package:PiliPlus/pages/hot/controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/pages/member/new/controller.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
@@ -1762,6 +1763,19 @@ List<SettingsModel> get extraSettings => [
setKey: SettingBoxKey.mergeDanmaku,
defaultVal: false,
),
SettingsModel(
settingsType: SettingsType.sw1tch,
title: '显示热门推荐',
subtitle: '热门页面显示每周必看等推荐内容入口',
leading: Icon(Icons.local_fire_department_outlined),
setKey: SettingBoxKey.showHotRcmd,
defaultVal: false,
onChanged: (value) {
try {
Get.find<HotController>().showHotRcmd.value = value;
} catch (_) {}
},
),
SettingsModel(
settingsType: SettingsType.sw1tch,
enableFeedback: true,

View File

@@ -83,6 +83,8 @@ class VideoIntroController extends GetxController
late final showArgueMsg = GStorage.showArgueMsg;
late final enableAi =
GStorage.setting.get(SettingBoxKey.enableAi, defaultValue: false);
late final enableQuickFav =
GStorage.setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
@override
void onInit() {

View File

@@ -165,16 +165,14 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
children: [
Icon(
Icons.shield_outlined,
size:
MediaQuery.textScalerOf(context).scale(16),
size: 16,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
Icon(
Icons.play_arrow_rounded,
size:
MediaQuery.textScalerOf(context).scale(12),
size: 12,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
@@ -183,6 +181,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
),
Text(
videoDetailCtr.videoLabel.value,
textScaler: TextScaler.linear(1),
strutStyle: StrutStyle(leading: 0, height: 1),
style: TextStyle(
height: 1,
@@ -278,12 +277,10 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
SmartDialog.showToast('账号未登录');
return;
}
final bool enableDragQuickFav =
GStorage.setting.get(SettingBoxKey.enableQuickFav, defaultValue: false);
// 快速收藏 &
// 点按 收藏至默认文件夹
// 长按选择文件夹
if (enableDragQuickFav) {
if (videoIntroController.enableQuickFav) {
if (type == 'tap') {
videoIntroController.actionFavVideo(type: 'default');
} else {

View File

@@ -1890,7 +1890,8 @@ class _VideoDetailPageState extends State<VideoDetailPage>
videoDetailController.bsController!.close();
videoDetailController.bsController = null;
} else {
setState(() {});
Get.back();
// setState(() {});
}
}
: null,

View File

@@ -2,6 +2,8 @@ import 'dart:async';
import 'package:PiliPlus/http/constants.dart';
import 'package:PiliPlus/http/init.dart';
import 'package:PiliPlus/utils/app_scheme.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/id_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
@@ -178,21 +180,24 @@ class _WebviewPageNewState extends State<WebviewPageNew> {
: null,
),
onLoadStop: (controller, url) {
_webViewController?.evaluateJavascript(
source: '''
document.styleSheets[0].insertRule('div.open-app-btn.bili-btn-warp {display:none;}', 0);
document.styleSheets[0].insertRule('#app__display-area > div.control-panel {display:none;}', 0);
''',
);
_webViewController?.evaluateJavascript(
source: '''
document.querySelector('#internationalHeader').remove();
document.querySelector('#message-navbar').remove();
''',
);
if (url.toString().startsWith('https://live.bilibili.com')) {
_webViewController?.evaluateJavascript(
source: '''
document.styleSheets[0].insertRule('div.open-app-btn.bili-btn-warp {display:none;}', 0);
document.styleSheets[0].insertRule('#app__display-area > div.control-panel {display:none;}', 0);
''',
);
}
// _webViewController?.evaluateJavascript(
// source: '''
// document.querySelector('#internationalHeader').remove();
// document.querySelector('#message-navbar').remove();
// ''',
// );
},
shouldOverrideUrlLoading: (controller, navigationAction) async {
final String str = navigationAction.request.url!.pathSegments[0];
final String? str =
navigationAction.request.url!.pathSegments.getOrNull(0);
final Map matchRes = IdUtils.matchAvorBv(input: str);
final List matchKeys = matchRes.keys.toList();
if (matchKeys.isNotEmpty) {
@@ -206,12 +211,28 @@ class _WebviewPageNewState extends State<WebviewPageNew> {
}
var url = navigationAction.request.url!.toString();
if (!url.startsWith('http')) {
if (url.startsWith('http')) {
if (RegExp(r'https://www.bilibili.com/video/BV[a-zA-Z\d]+')
.hasMatch(url)) {
PiliScheme.routePush(Uri.parse(url));
return NavigationActionPolicy.CANCEL;
} else if (url.startsWith('http://m.bilibili.com/playlist/')) {
try {
String? bvid =
RegExp(r'bvid=(BV[a-zA-Z\d]+)').firstMatch(url)?.group(1);
if (bvid != null) {
PiliScheme.videoPush(null, bvid);
return NavigationActionPolicy.CANCEL;
}
} catch (_) {}
}
} else {
if (url.startsWith('bilibili://video/')) {
String str = Uri.parse(url).pathSegments[0];
String? str = Uri.parse(url).pathSegments.getOrNull(0);
Get.offAndToNamed(
'/searchResult',
parameters: {'keyword': str},
parameters: {'keyword': str ?? ''},
);
} else {
var snackBar = SnackBar(

View File

@@ -63,6 +63,7 @@ class _WhisperPageState extends State<WhisperPage> {
'url': 'https://message.bilibili.com',
'type': 'whisper',
'pageTitle': '消息中心',
'ua': 'pc',
});
},
),
@@ -175,7 +176,7 @@ class _WhisperPageState extends State<WhisperPage> {
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, int i) {
dynamic content =
sessionList[i].lastMsg.content;
sessionList[i]?.lastMsg?.content;
if (content == null || content == "") {
content = '不支持的消息类型';
} else {

View File

@@ -800,6 +800,10 @@ class PlPlayerController {
videoPlayerController!.stream.error.listen((String event) {
// 直播的错误提示没有参考价值,均不予显示
if (videoType.value == 'live') return;
if (event.startsWith("Failed to open .") ||
event.startsWith("Cannot open file ''")) {
SmartDialog.showToast('视频源为空');
}
if (event.startsWith("Failed to open https://") ||
event.startsWith("Can not open external file https://") ||
//tcp: ffurl_read returned 0xdfb9b0bb
@@ -807,13 +811,15 @@ class PlPlayerController {
event.startsWith('tcp: ffurl_read returned ')) {
EasyThrottle.throttle('videoPlayerController!.stream.error.listen',
const Duration(milliseconds: 10000), () {
Future.delayed(const Duration(milliseconds: 3000), () {
Future.delayed(const Duration(milliseconds: 3000), () async {
debugPrint("isBuffering.value: ${isBuffering.value}");
debugPrint("_buffered.value: ${_buffered.value}");
if (isBuffering.value && _buffered.value == Duration.zero) {
refreshPlayer();
SmartDialog.showToast('视频链接打开失败,重试中',
displayTime: const Duration(milliseconds: 500));
if (!await refreshPlayer()) {
debugPrint("failed");
}
}
});
});

View File

@@ -52,11 +52,11 @@ class LoginUtils {
..loadingState.value = LoadingState.loading();
} catch (_) {}
try {
for (int i = 0; i < tabsConfig.length; i++) {
for (int i = 0; i < tabsConfig.length; i++) {
try {
Get.find<DynamicsTabController>(tag: tabsConfig[i]['tag']).onRefresh();
}
} catch (_) {}
} catch (_) {}
}
}
static String buvid() {

View File

@@ -116,7 +116,7 @@ class GStorage {
setting.get(SettingBoxKey.defaultHomePage, defaultValue: 0);
static int get previewQ =>
setting.get(SettingBoxKey.previewQuality, defaultValue: 80);
setting.get(SettingBoxKey.previewQuality, defaultValue: 100);
static double get mediumCardWidth =>
setting.get(SettingBoxKey.mediumCardWidth, defaultValue: 280.0);
@@ -342,6 +342,9 @@ class GStorage {
static bool get mergeDanmaku =>
GStorage.setting.get(SettingBoxKey.mergeDanmaku, defaultValue: false);
static bool get showHotRcmd =>
GStorage.setting.get(SettingBoxKey.showHotRcmd, defaultValue: false);
static List<double> get dynamicDetailRatio => List<double>.from(setting
.get(SettingBoxKey.dynamicDetailRatio, defaultValue: [60.0, 40.0]));
@@ -562,6 +565,7 @@ class SettingBoxKey {
refreshDisplacement = 'refreshDisplacement',
showVipDanmaku = 'showVipDanmaku',
mergeDanmaku = 'mergeDanmaku',
showHotRcmd = 'showHotRcmd',
// Sponsor Block
enableSponsorBlock = 'enableSponsorBlock',

View File

@@ -13,8 +13,10 @@ import 'package:PiliPlus/http/search.dart';
import 'package:PiliPlus/http/user.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/bangumi/info.dart';
import 'package:PiliPlus/models/common/dynamics_type.dart';
import 'package:PiliPlus/models/common/search_type.dart';
import 'package:PiliPlus/pages/dynamics/controller.dart';
import 'package:PiliPlus/pages/dynamics/tab/controller.dart';
import 'package:PiliPlus/pages/home/controller.dart';
import 'package:PiliPlus/pages/live/controller.dart';
import 'package:PiliPlus/pages/media/controller.dart';
@@ -63,8 +65,12 @@ class Utils {
);
}
static void handleWebview(String url, {bool off = false}) {
if (GStorage.openInBrowser) {
static void handleWebview(
String url, {
bool off = false,
bool inApp = false,
}) {
if (inApp.not && GStorage.openInBrowser) {
launchURL(url);
} else {
if (off) {
@@ -185,6 +191,13 @@ class Utils {
..onRefresh();
} catch (_) {}
for (int i = 0; i < tabsConfig.length; i++) {
try {
Get.find<DynamicsTabController>(tag: tabsConfig[i]['tag'])
.onRefresh();
} catch (_) {}
}
try {
Get.find<MediaController>()
..mid = result['data'].mid

View File

@@ -1,5 +1,6 @@
import 'package:PiliPlus/models/video/play/CDN.dart';
import 'package:PiliPlus/models/video/play/url.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage.dart';
import 'package:flutter/material.dart';
@@ -20,11 +21,15 @@ class VideoUtils {
if (item is AudioItem) {
if (GStorage.setting
.get(SettingBoxKey.disableAudioCDN, defaultValue: true)) {
return item.backupUrl ?? item.baseUrl ?? "";
return item.backupUrl.isNullOrEmpty.not
? item.backupUrl!
: item.baseUrl ?? "";
}
}
if (defaultCDNService == CDNService.baseUrl.code) {
return item.baseUrl ?? "";
return item.baseUrl.isNullOrEmpty.not
? item.baseUrl
: item.backupUrl ?? "";
}
if (item is CodecItem) {
backupUrl = (item.urlInfo?.first.host)! +
@@ -34,20 +39,20 @@ class VideoUtils {
backupUrl = item.backupUrl;
}
if (defaultCDNService == CDNService.backupUrl.code) {
return backupUrl ?? item.baseUrl ?? "";
return backupUrl.isNullOrEmpty.not ? backupUrl : item.baseUrl ?? "";
}
videoUrl = (backupUrl == null || isMCDNorPCDN(backupUrl))
videoUrl = (backupUrl.isNullOrEmpty || isMCDNorPCDN(backupUrl!))
? item.baseUrl
: backupUrl;
if (videoUrl == null) {
if (videoUrl.isNullOrEmpty) {
return "";
}
debugPrint("videoUrl:$videoUrl");
String defaultCDNHost = CDNServiceCode.fromCode(defaultCDNService)!.host;
debugPrint("defaultCDNHost:$defaultCDNHost");
if (videoUrl.contains("szbdyd.com")) {
if (videoUrl!.contains("szbdyd.com")) {
String hostname =
Uri.parse(videoUrl).queryParameters['xy_usource'] ?? defaultCDNHost;
videoUrl =