Signed-off-by: dom <githubaccount56556@proton.me>
This commit is contained in:
dom
2026-05-14 12:13:17 +08:00
parent ba85c014cf
commit 7482f52557
78 changed files with 429 additions and 849 deletions

View File

@@ -1,180 +0,0 @@
import 'package:PiliPlus/common/widgets/gesture/tap_gesture_recognizer.dart';
import 'package:PiliPlus/common/widgets/selectable_text.dart';
import 'package:PiliPlus/models_new/video/video_ai_conclusion/model_result.dart';
import 'package:PiliPlus/pages/common/slide/common_slide_page.dart';
import 'package:PiliPlus/pages/video/controller.dart';
import 'package:PiliPlus/utils/duration_utils.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class AiConclusionPanel extends CommonSlidePage {
final AiConclusionResult item;
const AiConclusionPanel({
super.key,
required this.item,
});
@override
State<AiConclusionPanel> createState() => _AiDetailState();
}
class _AiDetailState extends State<AiConclusionPanel>
with SingleTickerProviderStateMixin, CommonSlideMixin {
@override
Widget buildPage(ThemeData theme) {
return Material(
color: theme.colorScheme.surface,
child: Column(
children: [
GestureDetector(
onTap: Get.back,
child: SizedBox(
height: 35,
child: Center(
child: Container(
width: 32,
height: 3,
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: const BorderRadius.all(Radius.circular(3)),
),
),
),
),
),
Expanded(
child: enableSlide ? slideList(theme) : buildList(theme),
),
],
),
);
}
late Key _key;
late bool _isNested;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final controller = PrimaryScrollController.of(context);
_isNested = controller is ExtendedNestedScrollController;
_key = ValueKey(controller.hashCode);
}
@override
Widget buildList(ThemeData theme) {
final child = _buildContent(theme, widget.item);
if (_isNested) {
return ExtendedVisibilityDetector(
uniqueKey: const Key('ai-conclusion'),
child: child,
);
}
return child;
}
Widget _buildContent(ThemeData theme, AiConclusionResult res) {
return CustomScrollView(
key: _key,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
if (res.summary?.isNotEmpty == true) ...[
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 14),
child: selectableText(
res.summary!,
style: const TextStyle(
fontSize: 15,
height: 1.5,
),
),
),
),
if (res.outline?.isNotEmpty == true)
SliverToBoxAdapter(
child: Divider(
height: 20,
color: theme.dividerColor.withValues(alpha: 0.1),
thickness: 6,
),
),
],
if (res.outline?.isNotEmpty == true)
SliverPadding(
padding: EdgeInsets.only(
left: 14,
right: 14,
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
sliver: SliverList.builder(
itemCount: res.outline!.length,
itemBuilder: (context, index) {
final item = res.outline![index];
return SelectionArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (index != 0) const SizedBox(height: 10),
Text(
item.title!,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
height: 1.5,
),
),
const SizedBox(height: 6),
...?item.partOutline?.map(
(item) => Wrap(
children: [
Text.rich(
TextSpan(
style: TextStyle(
fontSize: 14,
color: theme.colorScheme.onSurface,
height: 1.5,
),
children: [
TextSpan(
text: DurationUtils.formatDuration(
item.timestamp,
),
style: TextStyle(
color: theme.colorScheme.primary,
),
recognizer:
(NoDeadlineTapGestureRecognizer()
..onTap = () {
try {
Get.find<VideoDetailController>(
tag: Get.arguments['heroTag'],
).plPlayerController.seekTo(
Duration(
seconds: item.timestamp!,
),
isSeek: false,
);
} catch (_) {}
}),
),
const TextSpan(text: ' '),
TextSpan(text: item.content!),
],
),
),
],
),
),
],
),
);
},
),
),
],
);
}
}

View File

@@ -4,7 +4,6 @@ import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/stat_type.dart';
import 'package:PiliPlus/models/common/video/video_quality.dart';
import 'package:PiliPlus/models_new/pgc/pgc_info_model/episode.dart' as pgc;
@@ -364,11 +363,11 @@ class _DownloadPanelState extends State<DownloadPanel> {
return Padding(
padding: const EdgeInsets.only(bottom: 2),
child: SizedBox(
height: 98,
height: 110,
child: Builder(
builder: (context) {
return Material(
type: MaterialType.transparency,
type: .transparency,
child: InkWell(
onTap: () {
if (_onDownload(
@@ -389,12 +388,12 @@ class _DownloadPanelState extends State<DownloadPanel> {
children: [
if (cover?.isNotEmpty == true)
Stack(
clipBehavior: Clip.none,
clipBehavior: .none,
children: [
NetworkImgLayer(
src: cover,
width: 140.8,
height: 88,
width: 160,
height: 110,
cacheWidth: cacheWidth,
),
if (duration != null && duration > 0)
@@ -402,14 +401,14 @@ class _DownloadPanelState extends State<DownloadPanel> {
text: DurationUtils.formatDuration(duration),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
type: .gray,
),
if (isCharging == true)
const PBadge(
text: '充电专属',
top: 6,
right: 6,
type: PBadgeType.error,
type: .error,
)
else if (episode.badge != null)
PBadge(
@@ -417,9 +416,9 @@ class _DownloadPanelState extends State<DownloadPanel> {
top: 6,
right: 6,
type: switch (episode.badge) {
'预告' => PBadgeType.gray,
'限免' => PBadgeType.free,
_ => PBadgeType.primary,
'预告' => .gray,
'限免' => .free,
_ => .primary,
},
),
],

View File

@@ -88,7 +88,7 @@ class LocalIntroController extends CommonIntroController {
}
final index = (-1).obs;
double get _offset => index * 100 + 7 - 35;
double get _offset => index * 112 + 7 - 35;
final list = RxList<BiliDownloadEntryInfo>();
@override

View File

@@ -3,7 +3,6 @@ import 'dart:io';
import 'package:PiliPlus/common/style.dart';
import 'package:PiliPlus/common/widgets/badge.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/video/video_quality.dart';
import 'package:PiliPlus/models_new/download/bili_download_entry_info.dart';
import 'package:PiliPlus/pages/video/introduction/local/controller.dart';
@@ -41,7 +40,7 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
final item = _controller.list[index];
return _buildItem(theme, currIndex == index, index, item);
},
itemExtent: 100,
itemExtent: 112,
);
});
}
@@ -56,11 +55,11 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
final cover = File(path.join(entry.entryDirPath, PathUtils.coverName));
final cacheWidth = entry.pageData?.cacheWidth ?? false;
return Padding(
padding: const EdgeInsets.only(bottom: 2),
padding: const .only(bottom: 2),
child: SizedBox(
height: 98,
height: 110,
child: Material(
type: MaterialType.transparency,
type: .transparency,
child: InkWell(
onTap: () {
if (isCurr) {
@@ -69,7 +68,7 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
_controller.playIndex(index, entry: entry);
},
child: Padding(
padding: const EdgeInsets.symmetric(
padding: const .symmetric(
horizontal: Style.safeSpace,
vertical: 5,
),
@@ -77,24 +76,24 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
spacing: 10,
children: [
Stack(
clipBehavior: Clip.none,
clipBehavior: .none,
children: [
cover.existsSync()
? ClipRRect(
borderRadius: Style.mdRadius,
child: Image.file(
cover,
width: 140.8,
height: 88,
width: 160,
height: 110,
fit: BoxFit.cover,
cacheWidth: cacheWidth ? 140.8 : null,
cacheHeight: cacheWidth ? null : 88,
cacheWidth: cacheWidth ? 160 : null,
cacheHeight: cacheWidth ? null : 110,
),
)
: NetworkImgLayer(
src: entry.cover,
width: 140.8,
height: 88,
width: 160,
height: 110,
),
PBadge(
text: DurationUtils.formatDuration(
@@ -102,14 +101,14 @@ class _LocalIntroPanelState extends State<LocalIntroPanel>
),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
type: .gray,
),
if (entry.videoQuality case final videoQuality?)
PBadge(
text: VideoQuality.fromCode(videoQuality).shortDesc,
right: 6.0,
top: 6.0,
type: PBadgeType.gray,
type: .gray,
),
],
),

View File

@@ -5,7 +5,6 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/image/image_save.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/stat/stat.dart';
import 'package:PiliPlus/models/common/badge_type.dart';
import 'package:PiliPlus/models/common/stat_type.dart';
import 'package:PiliPlus/models_new/media_list/media_list.dart';
import 'package:PiliPlus/models_new/video/video_detail/episode.dart';
@@ -131,7 +130,7 @@ class _MediaListPanelState extends State<MediaListPanel>
),
sliver: Obx(
() => SliverFixedExtentList.builder(
itemExtent: 100,
itemExtent: 112,
itemCount: widget.mediaList.length,
itemBuilder: (context, index) {
if (index == widget.mediaList.length - 1 &&
@@ -164,11 +163,11 @@ class _MediaListPanelState extends State<MediaListPanel>
bvid: item.bvid,
);
return Padding(
padding: const EdgeInsets.only(bottom: 2),
padding: const .only(bottom: 2),
child: SizedBox(
height: 98,
height: 110,
child: Material(
type: MaterialType.transparency,
type: .transparency,
child: InkWell(
onTap: () {
if (item.type != 2) {
@@ -181,23 +180,20 @@ class _MediaListPanelState extends State<MediaListPanel>
onLongPress: onLongPress,
onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress,
child: Stack(
clipBehavior: Clip.none,
clipBehavior: .none,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 5,
),
padding: const .symmetric(horizontal: 12, vertical: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
Stack(
clipBehavior: Clip.none,
clipBehavior: .none,
children: [
NetworkImgLayer(
src: item.cover,
width: 140.8,
height: 88,
width: 160,
height: 110,
),
if (item.badge?.isNotEmpty == true)
PBadge(
@@ -205,8 +201,8 @@ class _MediaListPanelState extends State<MediaListPanel>
right: 6.0,
top: 6.0,
type: switch (item.badge) {
'充电专属' => PBadgeType.error,
_ => PBadgeType.primary,
'充电专属' => .error,
_ => .primary,
},
),
PBadge(
@@ -215,7 +211,7 @@ class _MediaListPanelState extends State<MediaListPanel>
),
right: 6.0,
bottom: 6.0,
type: PBadgeType.gray,
type: .gray,
),
],
),

View File

@@ -177,7 +177,7 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
Loading() => SliverFixedExtentList.builder(
itemCount: 10,
itemBuilder: (_, _) => const VideoCardHSkeleton(),
itemExtent: 100,
itemExtent: 112,
),
Success(:final response) =>
response != null && response.isNotEmpty
@@ -191,7 +191,7 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
}
final videoItem = response[index];
return Padding(
padding: const EdgeInsets.only(bottom: 2),
padding: const .only(bottom: 2),
child: VideoCardHMemberVideo(
videoItem: videoItem,
bvid: _bvid,
@@ -209,7 +209,7 @@ class _HorizontalMemberPageState extends State<HorizontalMemberPage> {
);
},
itemCount: response.length,
itemExtent: 100,
itemExtent: 112,
),
],
)

View File

@@ -199,13 +199,14 @@ class _PayCoinsPageState extends State<PayCoinsPage>
}
Widget _buildCoinWidget(int index, double factor) {
final filter = _getPayFilter(index);
final coinSize = 35 + (factor * 15);
return Center(
child: SizedBox(
height: 70 + (factor * 30),
width: 70 + (factor * 30),
child: SizedBox.square(
dimension: 70 + (factor * 30),
child: ColorFiltered(
colorFilter: ColorFilter.mode(
_getPayFilter(index),
filter,
BlendMode.srcATop,
),
child: Stack(
@@ -221,8 +222,8 @@ class _PayCoinsPageState extends State<PayCoinsPage>
child: FadeTransition(
opacity: _coinFadeAnim,
child: Image.asset(
height: 35 + (factor * 15),
width: 35 + (factor * 15),
height: coinSize,
width: coinSize,
index == 0 ? Assets.coinsOne : Assets.coinsTwo,
),
),

View File

@@ -408,7 +408,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
return Obx(
() {
final isFullScreen = this.isFullScreen;
return scaffold(
return scaffold_(
appBar: removeAppBar(isFullScreen)
? null
: PreferredSize(
@@ -693,7 +693,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Widget get childWhenDisabledLandscape => Obx(
() {
final isFullScreen = this.isFullScreen;
return scaffold(
return scaffold_(
appBar: removeAppBar(isFullScreen)
? null
: AppBar(backgroundColor: Colors.black, toolbarHeight: 0),
@@ -859,7 +859,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Widget get childWhenDisabledAlmostSquare => Obx(() {
final isFullScreen = this.isFullScreen;
return scaffold(
return scaffold_(
appBar: removeAppBar(isFullScreen)
? null
: AppBar(backgroundColor: Colors.black, toolbarHeight: 0),
@@ -1236,6 +1236,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
left: 0,
right: 0,
top: 0,
height: height,
child: GestureDetector(
onTap: handlePlay,
behavior: .opaque,
@@ -1344,7 +1345,7 @@ class _VideoDetailPageVState extends State<VideoDetailPageV>
Positioned(
left: 16,
bottom: isFullScreen ? max(75, maxHeight * 0.25) : 75,
width: MediaQuery.textScalerOf(context).scale(120),
width: 120,
child: AnimatedList(
padding: EdgeInsets.zero,
key: videoDetailController.listKey,

View File

@@ -94,7 +94,7 @@ class _ViewPointsPageState extends State<ViewPointsPage>
final child = ListView.builder(
key: _key,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
padding: .only(
top: 7,
bottom: MediaQuery.viewPaddingOf(context).bottom + 100,
),
@@ -125,7 +125,7 @@ class _ViewPointsPageState extends State<ViewPointsPage>
Widget _buildItem(ThemeData theme, ViewPointSegment segment, bool isCurr) {
final theme = Theme.of(context);
return Material(
type: MaterialType.transparency,
type: .transparency,
child: InkWell(
onTap: segment.from != null
? () {
@@ -137,29 +137,26 @@ class _ViewPointsPageState extends State<ViewPointsPage>
}
: null,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: Style.safeSpace,
vertical: 5,
),
padding: const .symmetric(horizontal: Style.safeSpace, vertical: 5),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: .start,
children: [
NetworkImgLayer(
src: segment.url,
width: 140.8,
height: 88,
width: 160,
height: 110,
),
const SizedBox(width: 10),
Expanded(
child: Column(
spacing: 10,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: .min,
crossAxisAlignment: .start,
children: [
Text(
segment.title ?? '',
maxLines: 2,
overflow: TextOverflow.ellipsis,
overflow: .ellipsis,
style: isCurr
? TextStyle(
fontWeight: FontWeight.bold,