Merge remote-tracking branch 'upstream/main'

This commit is contained in:
orz12
2024-02-17 07:26:55 +08:00
24 changed files with 406 additions and 335 deletions

View File

@@ -22,20 +22,27 @@ class HttpError extends StatelessWidget {
"assets/images/error.svg", "assets/images/error.svg",
height: 200, height: 200,
), ),
const SizedBox(height: 20), const SizedBox(height: 30),
Text( Text(
errMsg ?? '请求异常', errMsg ?? '请求异常',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
const SizedBox(height: 30), const SizedBox(height: 20),
OutlinedButton.icon( FilledButton.tonal(
onPressed: () { onPressed: () {
fn!(); fn!();
}, },
icon: const Icon(Icons.arrow_forward_outlined, size: 20), style: ButtonStyle(
label: Text(btnText ?? '点击重试'), backgroundColor: MaterialStateProperty.resolveWith((states) {
) return Theme.of(context).colorScheme.primary.withAlpha(20);
}),
),
child: Text(
btnText ?? '点击重试',
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
),
], ],
), ),
), ),

View File

@@ -302,8 +302,7 @@ class VideoStat extends StatelessWidget {
maxLines: 1, maxLines: 1,
text: TextSpan( text: TextSpan(
style: TextStyle( style: TextStyle(
fontSize: MediaQuery.textScalerOf(context) fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
.scale(Theme.of(context).textTheme.labelSmall!.fontSize!),
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)), text: Utils.formatTimestampToRelativeTime(videoItem.pubdate)),

View File

@@ -131,7 +131,7 @@ class VideoHttp {
} }
return {'status': true, 'data': list}; return {'status': true, 'data': list};
} else { } else {
return {'status': false, 'data': []}; return {'status': false, 'data': [], 'msg': res.data['message']};
} }
} catch (err) { } catch (err) {
return {'status': false, 'data': [], 'msg': err}; return {'status': false, 'data': [], 'msg': err};

View File

@@ -1,3 +1,5 @@
import 'package:pilipala/utils/id_utils.dart';
class RecVideoItemAppModel { class RecVideoItemAppModel {
RecVideoItemAppModel({ RecVideoItemAppModel({
this.id, this.id,
@@ -50,14 +52,15 @@ class RecVideoItemAppModel {
? json['player_args']['aid'] ? json['player_args']['aid']
: int.parse(json['param'] ?? '-1'); : int.parse(json['param'] ?? '-1');
aid = json['player_args'] != null ? json['player_args']['aid'] : -1; aid = json['player_args'] != null ? json['player_args']['aid'] : -1;
bvid = null; bvid = json['player_args'] != null
? IdUtils.av2bv(json['player_args']['aid'])
: '';
cid = json['player_args'] != null ? json['player_args']['cid'] : -1; cid = json['player_args'] != null ? json['player_args']['cid'] : -1;
pic = json['cover']; pic = json['cover'];
stat = RcmdStat.fromJson(json); stat = RcmdStat.fromJson(json);
// 改用player_args中的duration作为原始数据秒数 // 改用player_args中的duration作为原始数据秒数
duration = json['player_args'] != null duration =
? json['player_args']['duration'] json['player_args'] != null ? json['player_args']['duration'] : -1;
: -1;
//duration = json['cover_right_text']; //duration = json['cover_right_text'];
title = json['title']; title = json['title'];
owner = RcmdOwner.fromJson(json); owner = RcmdOwner.fromJson(json);

View File

@@ -9,7 +9,6 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/view.dart';
import '../../utils/grid.dart'; import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
@@ -200,7 +199,10 @@ class _BangumiPageState extends State<BangumiPage>
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => {}, fn: () {
_futureBuilderFuture =
_bangumidController.queryBangumiListFeed();
},
); );
} }
} else { } else {
@@ -209,7 +211,6 @@ class _BangumiPageState extends State<BangumiPage>
}, },
), ),
), ),
const LoadingMore()
], ],
), ),
); );

View File

@@ -195,22 +195,6 @@ class _DynamicsPageState extends State<DynamicsPage>
) )
], ],
), ),
// Obx(
// () => Visibility(
// visible: _dynamicsController.userLogin.value,
// child: Positioned(
// right: 4,
// top: 0,
// bottom: 0,
// child: IconButton(
// padding: EdgeInsets.zero,
// onPressed: () =>
// {feedBack(), _dynamicsController.resetSearch()},
// icon: const Icon(Icons.history, size: 21),
// ),
// ),
// ),
// ),
], ],
), ),
), ),
@@ -232,7 +216,8 @@ class _DynamicsPageState extends State<DynamicsPage>
return Obx(() => UpPanel(_dynamicsController.upData.value)); return Obx(() => UpPanel(_dynamicsController.upData.value));
} else { } else {
return const SliverToBoxAdapter( return const SliverToBoxAdapter(
child: SizedBox(height: 80)); child: SizedBox(height: 80),
);
} }
} else { } else {
return const SliverToBoxAdapter( return const SliverToBoxAdapter(
@@ -243,15 +228,6 @@ class _DynamicsPageState extends State<DynamicsPage>
} }
}, },
), ),
SliverToBoxAdapter(
child: Container(
height: 6,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.5),
),
),
FutureBuilder( FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {

View File

@@ -36,8 +36,7 @@ class _UpPanelState extends State<UpPanel> {
} }
upList.insert( upList.insert(
0, 0,
UpItem( UpItem(face: '', uname: '全部动态', mid: -1),
face: 'https://files.catbox.moe/8uc48f.png', uname: '全部动态', mid: -1),
); );
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
upList.insert( upList.insert(
@@ -56,7 +55,7 @@ class _UpPanelState extends State<UpPanel> {
floating: true, floating: true,
pinned: false, pinned: false,
delegate: _SliverHeaderDelegate( delegate: _SliverHeaderDelegate(
height: 124, height: 126,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -121,6 +120,13 @@ class _UpPanelState extends State<UpPanel> {
], ],
), ),
), ),
Container(
height: 6,
color: Theme.of(context)
.colorScheme
.onInverseSurface
.withOpacity(0.5),
),
], ],
)), )),
); );
@@ -171,6 +177,9 @@ class _UpPanelState extends State<UpPanel> {
}, },
onLongPress: () { onLongPress: () {
feedBack(); feedBack();
if (data.mid == -1) {
return;
}
String heroTag = Utils.makeHeroTag(data.mid); String heroTag = Utils.makeHeroTag(data.mid);
Get.toNamed('/member?mid=${data.mid}', Get.toNamed('/member?mid=${data.mid}',
arguments: {'face': data.face, 'heroTag': heroTag}); arguments: {'face': data.face, 'heroTag': heroTag});
@@ -198,11 +207,18 @@ class _UpPanelState extends State<UpPanel> {
backgroundColor: data.type == 'live' backgroundColor: data.type == 'live'
? Theme.of(context).colorScheme.secondaryContainer ? Theme.of(context).colorScheme.secondaryContainer
: Theme.of(context).colorScheme.primary, : Theme.of(context).colorScheme.primary,
child: NetworkImgLayer( child: data.face != ''
width: 49, ? NetworkImgLayer(
height: 49, width: 50,
height: 50,
src: data.face, src: data.face,
type: 'avatar', type: 'avatar',
)
: const CircleAvatar(
radius: 25,
backgroundImage: AssetImage(
'assets/images/noface.jpeg',
),
), ),
), ),
Padding( Padding(
@@ -271,13 +287,11 @@ class UpPanelSkeleton extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Container( Container(
width: 49, width: 50,
height: 49, height: 50,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onInverseSurface, color: Theme.of(context).colorScheme.onInverseSurface,
borderRadius: const BorderRadius.all( borderRadius: BorderRadius.circular(50),
Radius.circular(24),
),
), ),
), ),
Container( Container(

View File

@@ -24,11 +24,13 @@ class _FavDetailPageState extends State<FavDetailPage> {
Get.put(FavDetailController()); Get.put(FavDetailController());
late StreamController<bool> titleStreamC; // a late StreamController<bool> titleStreamC; // a
Future? _futureBuilderFuture; Future? _futureBuilderFuture;
late String mediaId;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_futureBuilderFuture = _favDetailController.queryUserFavFolderDetail(); _futureBuilderFuture = _favDetailController.queryUserFavFolderDetail();
mediaId = Get.parameters['mediaId']!;
titleStreamC = StreamController<bool>(); titleStreamC = StreamController<bool>();
_controller.addListener( _controller.addListener(
() { () {
@@ -94,8 +96,8 @@ class _FavDetailPageState extends State<FavDetailPage> {
), ),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => Get.toNamed( onPressed: () =>
'/favSearch?searchType=0&mediaId=${Get.parameters['mediaId']!}'), Get.toNamed('/favSearch?searchType=0&mediaId=$mediaId'),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
// IconButton( // IconButton(

View File

@@ -15,9 +15,14 @@ import '../../../common/widgets/badge.dart';
class FavVideoCardH extends StatelessWidget { class FavVideoCardH extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final Function? callFn; final Function? callFn;
final int? searchType;
const FavVideoCardH({Key? key, required this.videoItem, this.callFn}) const FavVideoCardH({
: super(key: key); Key? key,
required this.videoItem,
this.callFn,
this.searchType,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -107,7 +112,11 @@ class FavVideoCardH extends StatelessWidget {
}, },
), ),
), ),
VideoContent(videoItem: videoItem, callFn: callFn) VideoContent(
videoItem: videoItem,
callFn: callFn,
searchType: searchType,
)
], ],
), ),
); );
@@ -123,7 +132,13 @@ class FavVideoCardH extends StatelessWidget {
class VideoContent extends StatelessWidget { class VideoContent extends StatelessWidget {
final dynamic videoItem; final dynamic videoItem;
final Function? callFn; final Function? callFn;
const VideoContent({super.key, required this.videoItem, this.callFn}); final int? searchType;
const VideoContent({
super.key,
required this.videoItem,
this.callFn,
this.searchType,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -189,7 +204,8 @@ class VideoContent extends StatelessWidget {
), ),
], ],
), ),
Positioned( searchType != 1
? Positioned(
right: 0, right: 0,
bottom: -4, bottom: -4,
child: IconButton( child: IconButton(
@@ -209,8 +225,9 @@ class VideoContent extends StatelessWidget {
child: Text( child: Text(
'取消', '取消',
style: TextStyle( style: TextStyle(
color: color: Theme.of(context)
Theme.of(context).colorScheme.outline), .colorScheme
.outline),
)), )),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
@@ -230,7 +247,8 @@ class VideoContent extends StatelessWidget {
size: 18, size: 18,
), ),
), ),
), )
: const SizedBox(),
], ],
), ),
), ),

View File

@@ -1,8 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/user/fav_detail.dart'; import 'package:pilipala/models/user/fav_detail.dart';
import '../../http/video.dart';
class FavSearchController extends GetxController { class FavSearchController extends GetxController {
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
Rx<TextEditingController> controller = TextEditingController().obs; Rx<TextEditingController> controller = TextEditingController().obs;
@@ -72,4 +75,21 @@ class FavSearchController extends GetxController {
if (!hasMore) return; if (!hasMore) return;
searchFav(type: 'onLoad'); searchFav(type: 'onLoad');
} }
onCancelFav(int id) async {
var result = await VideoHttp.favVideo(
aid: id, addIds: '', delIds: mediaId.toString());
if (result['status']) {
if (result['data']['prompt']) {
List dataList = favList;
for (var i in dataList) {
if (i.id == id) {
dataList.remove(i);
break;
}
}
SmartDialog.showToast('取消收藏');
}
}
}
} }

View File

@@ -8,9 +8,7 @@ import 'package:pilipala/pages/fav_detail/widget/fav_video_card.dart';
import 'controller.dart'; import 'controller.dart';
class FavSearchPage extends StatefulWidget { class FavSearchPage extends StatefulWidget {
final int? sourceType; const FavSearchPage({super.key});
final int? mediaId;
const FavSearchPage({super.key, this.sourceType, this.mediaId});
@override @override
State<FavSearchPage> createState() => _FavSearchPageState(); State<FavSearchPage> createState() => _FavSearchPageState();
@@ -19,11 +17,12 @@ class FavSearchPage extends StatefulWidget {
class _FavSearchPageState extends State<FavSearchPage> { class _FavSearchPageState extends State<FavSearchPage> {
final FavSearchController _favSearchCtr = Get.put(FavSearchController()); final FavSearchController _favSearchCtr = Get.put(FavSearchController());
late ScrollController scrollController; late ScrollController scrollController;
late int searchType;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
searchType = int.parse(Get.parameters['searchType']!);
scrollController = _favSearchCtr.scrollController; scrollController = _favSearchCtr.scrollController;
scrollController.addListener( scrollController.addListener(
() { () {
@@ -100,7 +99,11 @@ class _FavSearchPageState extends State<FavSearchPage> {
} else { } else {
return FavVideoCardH( return FavVideoCardH(
videoItem: _favSearchCtr.favList[index], videoItem: _favSearchCtr.favList[index],
callFn: () => null, searchType: searchType,
callFn: () => searchType != 1
? _favSearchCtr
.onCancelFav(_favSearchCtr.favList[index].id!)
: {},
); );
} }
}, },

View File

@@ -121,7 +121,12 @@ class _HotPageState extends State<HotPage> with AutomaticKeepAliveClientMixin {
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () {
setState(() {
_futureBuilderFuture =
_hotController.queryHotFeed('init');
});
},
); );
} }
} else { } else {

View File

@@ -10,8 +10,7 @@ class LiveController extends GetxController {
int count = 12; int count = 12;
int _currentPage = 1; int _currentPage = 1;
RxInt crossAxisCount = 2.obs; RxInt crossAxisCount = 2.obs;
RxList<LiveItemModel> liveList = [LiveItemModel()].obs; RxList<LiveItemModel> liveList = <LiveItemModel>[].obs;
bool isLoadingMore = false;
bool flag = false; bool flag = false;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@@ -37,7 +36,6 @@ class LiveController extends GetxController {
} }
_currentPage += 1; _currentPage += 1;
} }
isLoadingMore = false;
return res; return res;
} }

View File

@@ -11,7 +11,6 @@ import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/overlay_pop.dart'; import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/rcmd/index.dart';
import '../../utils/grid.dart'; import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
@@ -46,8 +45,8 @@ class _LivePageState extends State<LivePage>
() { () {
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) { scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle('liveList', const Duration(seconds: 1), () { EasyThrottle.throttle('liveList', const Duration(milliseconds: 200),
_liveController.isLoadingMore = true; () {
_liveController.onLoad(); _liveController.onLoad();
}); });
} }
@@ -109,24 +108,20 @@ class _LivePageState extends State<LivePage>
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => {}, fn: () {
setState(() {
_futureBuilderFuture =
_liveController.queryLiveList('init');
});
},
); );
} }
} else { } else {
// 缓存数据
if (_liveController.liveList.length > 1) {
return contentGrid(
_liveController, _liveController.liveList);
}
// 骨架屏
else {
return contentGrid(_liveController, []); return contentGrid(_liveController, []);
} }
}
}, },
), ),
), ),
LoadingMore(ctr: _liveController)
], ],
), ),
), ),

View File

@@ -3,7 +3,6 @@ import 'package:pilipala/http/constants.dart';
import 'package:pilipala/http/live.dart'; import 'package:pilipala/http/live.dart';
import 'package:pilipala/models/live/room_info.dart'; import 'package:pilipala/models/live/room_info.dart';
import 'package:pilipala/plugin/pl_player/index.dart'; import 'package:pilipala/plugin/pl_player/index.dart';
import '../../models/live/room_info_h5.dart'; import '../../models/live/room_info_h5.dart';
class LiveRoomController extends GetxController { class LiveRoomController extends GetxController {
@@ -16,13 +15,6 @@ class LiveRoomController extends GetxController {
RxBool volumeOff = false.obs; RxBool volumeOff = false.obs;
PlPlayerController plPlayerController = PlPlayerController plPlayerController =
PlPlayerController.getInstance(videoType: 'live'); PlPlayerController.getInstance(videoType: 'live');
// MeeduPlayerController meeduPlayerController = MeeduPlayerController(
// colorTheme: Theme.of(Get.context!).colorScheme.primary,
// pipEnabled: true,
// controlsStyle: ControlsStyle.live,
// enabledButtons: const EnabledButtons(pip: true),
// );
Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs; Rx<RoomInfoH5Model> roomInfoH5 = RoomInfoH5Model().obs;
@override @override
@@ -39,8 +31,6 @@ class LiveRoomController extends GetxController {
cover = liveItem.cover; cover = liveItem.cover;
} }
} }
queryLiveInfo();
queryLiveInfoH5();
} }
playerInit(source) async { playerInit(source) async {

View File

@@ -29,22 +29,18 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
plPlayerController = _liveRoomController.plPlayerController;
plPlayerController!.onPlayerStatusChanged.listen(
(PlayerStatus status) {
if (status == PlayerStatus.playing) {
isShowCover = false;
setState(() {});
}
},
);
if (Platform.isAndroid) { if (Platform.isAndroid) {
floating = Floating(); floating = Floating();
} }
_futureBuilder = _liveRoomController.queryLiveInfoH5(); videoSourceInit();
_futureBuilderFuture = _liveRoomController.queryLiveInfo(); _futureBuilderFuture = _liveRoomController.queryLiveInfo();
} }
Future<void> videoSourceInit() async {
_futureBuilder = _liveRoomController.queryLiveInfoH5();
plPlayerController = _liveRoomController.plPlayerController;
}
@override @override
void dispose() { void dispose() {
plPlayerController!.dispose(); plPlayerController!.dispose();

View File

@@ -105,7 +105,7 @@ class _MemberPageState extends State<MemberPage>
actions: [ actions: [
IconButton( IconButton(
onPressed: () => Get.toNamed( onPressed: () => Get.toNamed(
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'), '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'),
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
PopupMenuButton( PopupMenuButton(

View File

@@ -45,7 +45,7 @@ class _RcmdPageState extends State<RcmdPage>
if (scrollController.position.pixels >= if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) { scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle( EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 500), () { 'my-throttler', const Duration(milliseconds: 200), () {
_rcmdController.isLoadingMore = true; _rcmdController.isLoadingMore = true;
_rcmdController.onLoad(); _rcmdController.onLoad();
}); });
@@ -114,6 +114,7 @@ class _RcmdPageState extends State<RcmdPage>
errMsg: data['msg'], errMsg: data['msg'],
fn: () { fn: () {
setState(() { setState(() {
_rcmdController.isLoadingMore = true;
_futureBuilderFuture = _futureBuilderFuture =
_rcmdController.queryRcmdFeed('init'); _rcmdController.queryRcmdFeed('init');
}); });
@@ -126,7 +127,6 @@ class _RcmdPageState extends State<RcmdPage>
}, },
), ),
), ),
LoadingMore(ctr: _rcmdController),
], ],
), ),
), ),
@@ -177,33 +177,3 @@ class _RcmdPageState extends State<RcmdPage>
); );
} }
} }
class LoadingMore extends StatelessWidget {
final dynamic ctr;
const LoadingMore({super.key, this.ctr});
@override
Widget build(BuildContext context) {
return SliverToBoxAdapter(
child: Container(
height: MediaQuery.of(context).padding.bottom + 80,
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: GestureDetector(
onTap: () {
if (ctr != null) {
ctr!.isLoadingMore = true;
ctr!.onLoad();
}
},
child: Center(
child: Text(
'点击加载更多 👇',
style: TextStyle(
color: Theme.of(context).colorScheme.outline, fontSize: 13),
),
),
),
),
);
}
}

View File

@@ -187,9 +187,13 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
), ),
); );
} else { } else {
return HttpError( return CustomScrollView(
slivers: [
HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () => setState(() {}),
)
],
); );
} }
} else { } else {

View File

@@ -105,7 +105,11 @@ class _SearchPanelState extends State<SearchPanel>
slivers: [ slivers: [
HttpError( HttpError(
errMsg: data['msg'], errMsg: data['msg'],
fn: () => setState(() {}), fn: () {
setState(() {
_searchPanelController.onSearch();
});
},
), ),
], ],
); );
@@ -116,7 +120,11 @@ class _SearchPanelState extends State<SearchPanel>
slivers: [ slivers: [
HttpError( HttpError(
errMsg: '没有相关数据', errMsg: '没有相关数据',
fn: () => setState(() {}), fn: () {
setState(() {
_searchPanelController.onSearch();
});
},
), ),
], ],
); );

View File

@@ -18,6 +18,7 @@ import 'package:pilipala/utils/id_utils.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import '../related/index.dart';
import 'widgets/group_panel.dart'; import 'widgets/group_panel.dart';
class VideoIntroController extends GetxController { class VideoIntroController extends GetxController {
@@ -478,11 +479,15 @@ class VideoIntroController extends GetxController {
// 重新获取视频资源 // 重新获取视频资源
final VideoDetailController videoDetailCtr = final VideoDetailController videoDetailCtr =
Get.find<VideoDetailController>(tag: heroTag); Get.find<VideoDetailController>(tag: heroTag);
final ReleatedController releatedCtr =
Get.find<ReleatedController>(tag: heroTag);
videoDetailCtr.bvid = bvid; videoDetailCtr.bvid = bvid;
videoDetailCtr.oid.value = aid; videoDetailCtr.oid.value = aid;
videoDetailCtr.cid.value = cid; videoDetailCtr.cid.value = cid;
videoDetailCtr.danmakuCid.value = cid; videoDetailCtr.danmakuCid.value = cid;
videoDetailCtr.queryVideoUrl(); videoDetailCtr.queryVideoUrl();
releatedCtr.bvid = bvid;
releatedCtr.queryRelatedVideo();
// 重新请求评论 // 重新请求评论
try { try {
/// 未渲染回复组件时可能异常 /// 未渲染回复组件时可能异常

View File

@@ -1,14 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/video.dart'; import 'package:pilipala/http/video.dart';
import '../../../../models/model_hot_video_item.dart';
class ReleatedController extends GetxController { class ReleatedController extends GetxController {
// 视频aid // 视频aid
String bvid = Get.parameters['bvid'] ?? ""; String bvid = Get.parameters['bvid'] ?? "";
// 推荐视频列表 // 推荐视频列表
List relatedVideoList = []; RxList relatedVideoList = <HotVideoItemModel>[].obs;
OverlayEntry? popupDialog; OverlayEntry? popupDialog;
Future<dynamic> queryRelatedVideo() => VideoHttp.relatedVideoList(bvid: bvid); Future<dynamic> queryRelatedVideo() async {
return VideoHttp.relatedVideoList(bvid: bvid).then((value) {
if (value['status']) {
relatedVideoList.value = value['data'];
}
return value;
});
}
} }

View File

@@ -7,35 +7,58 @@ import 'package:pilipala/common/widgets/overlay_pop.dart';
import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/common/widgets/video_card_h.dart';
import './controller.dart'; import './controller.dart';
class RelatedVideoPanel extends StatelessWidget { class RelatedVideoPanel extends StatefulWidget {
final ReleatedController _releatedController = const RelatedVideoPanel({super.key});
@override
State<RelatedVideoPanel> createState() => _RelatedVideoPanelState();
}
class _RelatedVideoPanelState extends State<RelatedVideoPanel>
with AutomaticKeepAliveClientMixin {
late ReleatedController _releatedController;
late Future _futureBuilder;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_releatedController =
Get.put(ReleatedController(), tag: Get.arguments?['heroTag']); Get.put(ReleatedController(), tag: Get.arguments?['heroTag']);
RelatedVideoPanel({super.key}); _futureBuilder = _releatedController.queryRelatedVideo();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return FutureBuilder( return FutureBuilder(
future: _releatedController.queryRelatedVideo(), future: _futureBuilder,
builder: (BuildContext context, AsyncSnapshot snapshot) { builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) { if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) { if (snapshot.data == null) {
return const SliverToBoxAdapter(child: SizedBox()); return const SliverToBoxAdapter(child: SizedBox());
} }
if (snapshot.data!['status']) { if (snapshot.data!['status'] && snapshot.data != null) {
RxList relatedVideoList = _releatedController.relatedVideoList;
// 请求成功 // 请求成功
return SliverList( return Obx(
() => SliverList(
delegate: SliverChildBuilderDelegate((context, index) { delegate: SliverChildBuilderDelegate((context, index) {
if (index == snapshot.data['data'].length) { if (index == relatedVideoList.length) {
return SizedBox(height: MediaQuery.of(context).padding.bottom); return SizedBox(
height: MediaQuery.of(context).padding.bottom);
} else { } else {
return Material( return Material(
child: VideoCardH( child: VideoCardH(
videoItem: snapshot.data['data'][index], videoItem: relatedVideoList[index],
showPubdate: true, showPubdate: true,
longPress: () { longPress: () {
try { try {
_releatedController.popupDialog = _releatedController.popupDialog =
_createPopupDialog(snapshot.data['data'][index]); _createPopupDialog(_releatedController
.relatedVideoList[index]);
Overlay.of(context) Overlay.of(context)
.insert(_releatedController.popupDialog!); .insert(_releatedController.popupDialog!);
} catch (err) { } catch (err) {
@@ -48,7 +71,9 @@ class RelatedVideoPanel extends StatelessWidget {
), ),
); );
} }
}, childCount: snapshot.data['data'].length + 1)); }, childCount: relatedVideoList.length + 1),
),
);
} else { } else {
// 请求错误 // 请求错误
return HttpError(errMsg: '出错了', fn: () {}); return HttpError(errMsg: '出错了', fn: () {});

View File

@@ -1,5 +1,6 @@
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@@ -46,6 +47,17 @@ class ReplyItem extends StatelessWidget {
replyReply!(replyItem); replyReply!(replyItem);
} }
}, },
onLongPress: () {
feedBack();
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: replyItem);
},
);
},
child: Column( child: Column(
children: [ children: [
Padding( Padding(
@@ -121,98 +133,6 @@ class ReplyItem extends StatelessWidget {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
// 头像、昵称
// SizedBox(
// width: double.infinity,
// child: Stack(
// children: [
// GestureDetector(
// behavior: HitTestBehavior.opaque,
// onTap: () {
// feedBack();
// Get.toNamed('/member?mid=${replyItem!.mid}', arguments: {
// 'face': replyItem!.member!.avatar!,
// 'heroTag': heroTag
// });
// },
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisSize: MainAxisSize.min,
// children: <Widget>[
// lfAvtar(context, heroTag),
// const SizedBox(width: 12),
// Text(
// replyItem!.member!.uname!,
// style: TextStyle(
// color: replyItem!.member!.vip!['vipStatus'] > 0
// ? const Color.fromARGB(255, 251, 100, 163)
// : Theme.of(context).colorScheme.outline,
// fontSize: 13,
// ),
// ),
// const SizedBox(width: 6),
// Image.asset(
// 'assets/images/lv/lv${replyItem!.member!.level}.png',
// height: 11,
// ),
// const SizedBox(width: 6),
// if (replyItem!.isUp!)
// const PBadge(
// text: 'UP',
// size: 'small',
// stack: 'normal',
// fs: 9,
// ),
// ],
// ),
// ),
// Positioned(
// top: 0,
// left: 0,
// right: 0,
// child: Container(
// width: double.infinity,
// height: 45,
// decoration: BoxDecoration(
// image: replyItem!.member!.userSailing!.cardbg != null
// ? DecorationImage(
// alignment: Alignment.centerRight,
// fit: BoxFit.fitHeight,
// image: NetworkImage(
// replyItem!.member!.userSailing!.cardbg!['image'],
// ),
// )
// : null,
// ),
// ),
// ),
// if (replyItem!.member!.userSailing!.cardbg != null &&
// replyItem!.member!.userSailing!.cardbg!['fan']['number'] > 0)
// Positioned(
// top: 10,
// left: Get.size.width / 7 * 5.8,
// child: DefaultTextStyle(
// style: TextStyle(
// fontFamily: 'fansCard',
// fontSize: 9,
// color: Theme.of(context).colorScheme.primary,
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// const Text('NO.'),
// Text(
// replyItem!.member!.userSailing!.cardbg!['fan']
// ['num_desc'],
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// ),
/// fix Stack内GestureDetector onTap无效 /// fix Stack内GestureDetector onTap无效
GestureDetector( GestureDetector(
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
@@ -289,9 +209,6 @@ class ReplyItem extends StatelessWidget {
// title // title
Container( Container(
margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4), margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4),
child: SelectableRegion(
focusNode: FocusNode(),
selectionControls: MaterialTextSelectionControls(),
child: Text.rich( child: Text.rich(
style: const TextStyle(height: 1.75), style: const TextStyle(height: 1.75),
maxLines: maxLines:
@@ -315,7 +232,6 @@ class ReplyItem extends StatelessWidget {
), ),
), ),
), ),
),
// 操作区域 // 操作区域
bottonAction(context, replyItem!.replyControl), bottonAction(context, replyItem!.replyControl),
// 一楼的评论 // 一楼的评论
@@ -445,6 +361,17 @@ class ReplyItemRow extends StatelessWidget {
InkWell( InkWell(
// 一楼点击评论展开评论详情 // 一楼点击评论展开评论详情
onTap: () => replyReply!(replyItem), onTap: () => replyReply!(replyItem),
onLongPress: () {
feedBack();
showModalBottomSheet(
context: context,
useRootNavigator: true,
isScrollControlled: true,
builder: (context) {
return MorePanel(item: replies![i]);
},
);
},
child: Container( child: Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.fromLTRB( padding: EdgeInsets.fromLTRB(
@@ -636,8 +563,8 @@ InlineSpan buildContent(
}, },
), ),
); );
} else if (RegExp(r'^\b(?:\d+[:])?[0-5]?[0-9][:][0-5]?[0-9]\b$')
} else if (RegExp(r'^\b(?:\d+[:])?[0-5]?[0-9][:][0-5]?[0-9]\b$').hasMatch(matchStr)) { .hasMatch(matchStr)) {
matchStr = matchStr.replaceAll('', ':'); matchStr = matchStr.replaceAll('', ':');
spanChilds.add( spanChilds.add(
TextSpan( TextSpan(
@@ -955,3 +882,100 @@ InlineSpan buildContent(
// spanChilds.add(TextSpan(text: matchMember)); // spanChilds.add(TextSpan(text: matchMember));
return TextSpan(children: spanChilds); return TextSpan(children: spanChilds);
} }
class MorePanel extends StatelessWidget {
final dynamic item;
const MorePanel({super.key, required this.item});
Future<dynamic> menuActionHandler(String type) async {
String message = item.content.message ?? item.content;
switch (type) {
case 'copyAll':
await Clipboard.setData(ClipboardData(text: message));
SmartDialog.showToast('已复制');
Get.back();
break;
case 'copyFreedom':
Get.back();
showDialog(
context: Get.context!,
builder: (context) {
return AlertDialog(
title: const Text('自由复制'),
content: SelectableText(message),
);
},
);
break;
// case 'block':
// SmartDialog.showToast('加入黑名单');
// break;
// case 'report':
// SmartDialog.showToast('举报');
// break;
// case 'delete':
// SmartDialog.showToast('删除');
// break;
default:
}
}
@override
Widget build(BuildContext context) {
Color errorColor = Theme.of(context).colorScheme.error;
return Container(
padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
InkWell(
onTap: () => Get.back(),
child: Container(
height: 35,
padding: const EdgeInsets.only(bottom: 2),
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.outline,
borderRadius: const BorderRadius.all(Radius.circular(3))),
),
),
),
),
ListTile(
onTap: () async => await menuActionHandler('copyAll'),
minLeadingWidth: 0,
leading: const Icon(Icons.copy_all_outlined, size: 19),
title: Text('复制全部', style: Theme.of(context).textTheme.titleSmall),
),
ListTile(
onTap: () async => await menuActionHandler('copyFreedom'),
minLeadingWidth: 0,
leading: const Icon(Icons.copy_outlined, size: 19),
title: Text('自由复制', style: Theme.of(context).textTheme.titleSmall),
),
// ListTile(
// onTap: () async => await menuActionHandler('block'),
// minLeadingWidth: 0,
// leading: Icon(Icons.block_outlined, color: errorColor),
// title: Text('加入黑名单', style: TextStyle(color: errorColor)),
// ),
// ListTile(
// onTap: () async => await menuActionHandler('report'),
// minLeadingWidth: 0,
// leading: Icon(Icons.report_outlined, color: errorColor),
// title: Text('举报', style: TextStyle(color: errorColor)),
// ),
// ListTile(
// onTap: () async => await menuActionHandler('del'),
// minLeadingWidth: 0,
// leading: Icon(Icons.delete_outline, color: errorColor),
// title: Text('删除', style: TextStyle(color: errorColor)),
// ),
],
),
);
}
}