feat: 视频详情页,分类页。个人中心

This commit is contained in:
chengsiwen committed 2026-07-07 16:04:15 +08:00
1 parent b5bce6edcf
commit a3426831ed
26 files changed
+2878 -1077

No files matched your search

+18 -26
View File
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:novyronst/common/api/video_request.dart';
import 'package:novyronst/common/model/ny_drama_model.dart';
import 'package:novyronst/pages/play/widgets/player_view.dart';
import 'package:novyronst/pages/widgets/ny_loading_widget.dart';
class PlayPage extends StatefulWidget {
const PlayPage({super.key, this.isActive = true});
@@ -69,33 +70,26 @@ class _RootPageState extends State<PlayPage> {
if (index < 0 || index >= _videoList.length) return;
final item = _videoList[index];
final videoId = item.shortPlayVideoId ?? item.id;
if (videoId == null) return;
final _info = item.videoInfo ?? {};
final videoId = _info['short_play_video_id'];
if (videoId == null) {
return;
}
final shortPlayId = _info['short_play_id'];
if (shortPlayId == null) {
return;
}
final shortPlayId = item.shortPlayId ?? item.shortId;
final oldIsCollect = item.isCollect ?? false;
final oldCollectTotal = item.collectTotal;
final newIsCollect = !oldIsCollect;
final newCollectTotal = oldCollectTotal + (newIsCollect ? 1 : -1);
final success = item.isCollect == false
? await VideoRequest.collect(shortPlayId: shortPlayId ?? 0, videoId: videoId)
: await VideoRequest.cancelCollect(shortPlayId: shortPlayId ?? 0);
if (!mounted) return;
setState(() {
_videoList[index] = item.copyWith(
isCollect: newIsCollect,
collectTotal: newCollectTotal < 0 ? 0 : newCollectTotal,
);
});
final success = newIsCollect
? await VideoRequest.collect(shortPlayId: shortPlayId, videoId: videoId)
: await VideoRequest.cancelCollect(shortPlayId: shortPlayId);
if (!mounted || success) return;
setState(() {
_videoList[index] = _videoList[index].copyWith(
isCollect: oldIsCollect,
collectTotal: oldCollectTotal,
);
_videoList[index] = item.copyWith(isCollect: !item.isCollect!);
// item.isCollect = !item.isCollect!;
});
}
@@ -106,9 +100,7 @@ class _RootPageState extends State<PlayPage> {
Widget _buildBody() {
if (_isLoading && _videoList.isEmpty) {
return const Center(
child: CircularProgressIndicator(color: Colors.white),
);
return const NyLoadingWidget();
}
if (_videoList.isEmpty) {
+53 -5
View File
@@ -13,6 +13,8 @@ class PlayerView extends StatefulWidget {
this.onCollectTap,
this.isLoadingMore = false,
this.isActive = true,
this.bottomMetaSpacing = 76,
this.progressHeaderBuilder,
});
final List<NyDramaModel> videos;
@@ -21,6 +23,9 @@ class PlayerView extends StatefulWidget {
final ValueChanged<int>? onCollectTap;
final bool isLoadingMore;
final bool isActive;
final double bottomMetaSpacing;
final Widget Function(BuildContext context, NyDramaModel drama, int index)?
progressHeaderBuilder;
@override
State<PlayerView> createState() => _PlayerViewState();
@@ -48,6 +53,13 @@ class _PlayerViewState extends State<PlayerView> {
super.didUpdateWidget(oldWidget);
if (widget.videos.isEmpty) return;
if (oldWidget.initialIndex != widget.initialIndex) {
_currentIndex = widget.initialIndex.clamp(0, widget.videos.length - 1);
if (_pageController.hasClients) {
_jumpToPageAfterBuild(_currentIndex);
}
}
if (_currentIndex >= widget.videos.length) {
_currentIndex = widget.videos.length - 1;
}
@@ -187,12 +199,24 @@ class _PlayerViewState extends State<PlayerView> {
}
void _handlePageChanged(int index) {
if (index == _currentIndex) return;
_currentIndex = index;
widget.onPageChanged?.call(index);
_cachePlaybackWindow(index);
widget.isActive ? _playActiveVideo() : _pauseAllVideos();
}
void _jumpToPageAfterBuild(int index) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!mounted || widget.videos.isEmpty || !_pageController.hasClients) {
return;
}
final nextIndex = index.clamp(0, widget.videos.length - 1);
_pageController.jumpToPage(nextIndex);
});
}
void _togglePlayback() {
final controller = _controllers[_currentIndex];
if (!widget.isActive ||
@@ -217,12 +241,15 @@ class _PlayerViewState extends State<PlayerView> {
itemBuilder: (context, index) {
return _PlayerPage(
drama: widget.videos[index],
index: index,
controller: _controllers[index],
isPageActive: widget.isActive,
isLoadingMore:
widget.isLoadingMore && index == widget.videos.length - 1,
bottomMetaSpacing: widget.bottomMetaSpacing,
onTap: _togglePlayback,
onCollectTap: () => widget.onCollectTap?.call(index),
progressHeaderBuilder: widget.progressHeaderBuilder,
);
},
);
@@ -232,23 +259,33 @@ class _PlayerViewState extends State<PlayerView> {
class _PlayerPage extends StatelessWidget {
const _PlayerPage({
required this.drama,
required this.index,
required this.controller,
required this.isPageActive,
required this.isLoadingMore,
required this.bottomMetaSpacing,
required this.onTap,
required this.onCollectTap,
required this.progressHeaderBuilder,
});
final NyDramaModel drama;
final int index;
final VideoPlayerController? controller;
final bool isPageActive;
final bool isLoadingMore;
final double bottomMetaSpacing;
final VoidCallback onTap;
final VoidCallback onCollectTap;
final Widget Function(BuildContext context, NyDramaModel drama, int index)?
progressHeaderBuilder;
@override
Widget build(BuildContext context) {
final bottomPadding = MediaQuery.paddingOf(context).bottom + 86;
final metaBottom = resolvePlayerMetaBottom(
safeAreaBottom: MediaQuery.paddingOf(context).bottom,
bottomSpacing: bottomMetaSpacing,
);
return GestureDetector(
behavior: HitTestBehavior.opaque,
@@ -263,14 +300,14 @@ class _PlayerPage extends StatelessWidget {
Positioned(
left: 16,
right: 16,
bottom: bottomPadding - 10,
child: _buildMeta(),
bottom: metaBottom,
child: _buildMeta(context),
),
if (isLoadingMore)
Positioned(
left: 0,
right: 0,
bottom: bottomPadding - 30,
bottom: metaBottom - 20,
child: const Center(
child: SizedBox(
width: 18,
@@ -324,7 +361,7 @@ class _PlayerPage extends StatelessWidget {
);
}
Widget _buildMeta() {
Widget _buildMeta(BuildContext context) {
final isCollect = drama.isCollect ?? false;
return Column(
mainAxisSize: MainAxisSize.min,
@@ -370,6 +407,10 @@ class _PlayerPage extends StatelessWidget {
],
),
const SizedBox(height: 16),
if (progressHeaderBuilder != null) ...[
progressHeaderBuilder!(context, drama, index),
const SizedBox(height: 8),
],
_buildProgress(),
],
);
@@ -475,3 +516,10 @@ bool shouldPlayActiveVideo(
value.isInitialized &&
!value.isPlaying;
}
double resolvePlayerMetaBottom({
required double safeAreaBottom,
required double bottomSpacing,
}) {
return safeAreaBottom + bottomSpacing;
}