opt: intro panel

Closes #14

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-11-27 12:20:59 +08:00
parent d2a3d7bcd1
commit 5092650246
6 changed files with 268 additions and 72 deletions

View File

@@ -63,7 +63,6 @@ class BangumiIntroController extends CommonController {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
queryVideoTags();
if (Get.arguments.isNotEmpty as bool) { if (Get.arguments.isNotEmpty as bool) {
if (Get.arguments.containsKey('bangumiItem') as bool) { if (Get.arguments.containsKey('bangumiItem') as bool) {
preRender = true; preRender = true;
@@ -105,6 +104,12 @@ class BangumiIntroController extends CommonController {
} }
} }
@override
Future queryData([bool isRefresh = true]) async {
await queryVideoTags();
return super.queryData(isRefresh);
}
Future queryVideoTags() async { Future queryVideoTags() async {
var result = await UserHttp.videoTags(bvid: bvid); var result = await UserHttp.videoTags(bvid: bvid);
if (result['status']) { if (result['status']) {

View File

@@ -124,7 +124,7 @@ class VideoIntroController extends GetxController
// 获取视频简介&分p // 获取视频简介&分p
void queryVideoIntro() async { void queryVideoIntro() async {
queryVideoTags(); await queryVideoTags();
var result = await VideoHttp.videoIntro(bvid: bvid); var result = await VideoHttp.videoIntro(bvid: bvid);
if (result['status']) { if (result['status']) {
videoDetail.value = result['data']!; videoDetail.value = result['data']!;

View File

@@ -1,4 +1,8 @@
import 'package:PiliPalaX/common/widgets/self_sized_horizontal_list.dart'; import 'package:PiliPalaX/common/widgets/self_sized_horizontal_list.dart';
import 'package:PiliPalaX/pages/search/widgets/search_text.dart';
import 'package:PiliPalaX/utils/extension.dart';
import 'package:expandable/expandable.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@@ -154,6 +158,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
late final _coinKey = GlobalKey<ActionItemState>(); late final _coinKey = GlobalKey<ActionItemState>();
late final _favKey = GlobalKey<ActionItemState>(); late final _favKey = GlobalKey<ActionItemState>();
late final _expandableCtr = ExpandableController(initialExpanded: false);
@override @override
void initState() { void initState() {
@@ -167,6 +172,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true); enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
} }
@override
void dispose() {
_expandableCtr.dispose();
super.dispose();
}
void _showFavBottomSheet() => showModalBottomSheet( void _showFavBottomSheet() => showModalBottomSheet(
context: context, context: context,
useSafeArea: true, useSafeArea: true,
@@ -222,7 +233,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
return; return;
} }
feedBack(); feedBack();
widget.showIntroDetail(); // widget.showIntroDetail();
_expandableCtr.toggle();
} }
// 用户主页 // 用户主页
@@ -382,86 +394,104 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
const SizedBox(height: 8), const SizedBox(height: 8),
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(), onTap: showIntroDetail,
child: Row(children: [ child: ExpandablePanel(
Expanded( controller: _expandableCtr,
child: Text( collapsed: GestureDetector(
widget.videoDetail?.title ?? videoItem['title'] ?? "", onLongPress: () {
// !loadingStatus feedBack();
// ? "${widget.videoDetail?.title}" Utils.copyText(
// : videoItem['title'] ?? "", '${widget.videoDetail?.title ?? videoItem['title'] ?? ''}');
style: const TextStyle( },
fontSize: 16, child: Text(
fontWeight: FontWeight.w500, '${widget.videoDetail?.title ?? videoItem['title'] ?? ''}',
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
)),
Icon(
Icons.arrow_forward_ios,
size: 16,
color: t.colorScheme.outline,
), ),
]), expanded: GestureDetector(
onLongPress: () {
feedBack();
Utils.copyText(
'${widget.videoDetail?.title ?? videoItem['title'] ?? ''}');
},
child: Text(
widget.videoDetail?.title ?? videoItem['title'] ?? '',
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
),
theme: const ExpandableThemeData(
animationDuration: Duration(milliseconds: 300),
scrollAnimationDuration: Duration(milliseconds: 300),
crossFadePoint: 0,
fadeCurve: Curves.ease,
sizeCurve: Curves.linear,
),
),
), ),
const SizedBox(height: 8),
Stack( Stack(
children: [ children: [
GestureDetector( GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(), onTap: showIntroDetail,
child: Padding( child: Row(
padding: const EdgeInsets.only(top: 7, bottom: 6), children: <Widget>[
child: Row( StatView(
children: <Widget>[ theme: 'gray',
StatView( view: !loadingStatus
theme: 'gray', ? widget.videoDetail?.stat?.view ?? '-'
view: !loadingStatus : videoItem['stat']?.view ?? '-',
? widget.videoDetail?.stat?.view ?? '-' size: 'medium',
: videoItem['stat']?.view ?? '-', ),
size: 'medium', const SizedBox(width: 10),
StatDanMu(
theme: 'gray',
danmu: !loadingStatus
? widget.videoDetail?.stat?.danmu ?? '-'
: videoItem['stat']?.danmu ?? '-',
size: 'medium',
),
const SizedBox(width: 10),
Text(
Utils.dateFormat(
!loadingStatus
? widget.videoDetail?.pubdate
: videoItem['pubdate'],
formatType: 'detail'),
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
), ),
),
if (MineController.anonymity) ...<Widget>[
const SizedBox(width: 10), const SizedBox(width: 10),
StatDanMu( Icon(
theme: 'gray', MdiIcons.incognito,
danmu: !loadingStatus size: 15,
? widget.videoDetail?.stat?.danmu ?? '-' color: t.colorScheme.outline,
: videoItem['stat']?.danmu ?? '-', semanticLabel: '无痕',
size: 'medium',
), ),
const SizedBox(width: 10), ],
Text( const SizedBox(width: 10),
Utils.dateFormat( if (videoIntroController.isShowOnlineTotal)
!loadingStatus Obx(
? widget.videoDetail?.pubdate () => Text(
: videoItem['pubdate'], '${videoIntroController.total.value}人在看',
formatType: 'detail'), style: TextStyle(
style: TextStyle( fontSize: 12,
fontSize: 12, color: t.colorScheme.outline,
color: t.colorScheme.outline,
),
),
if (MineController.anonymity) ...<Widget>[
const SizedBox(width: 10),
Icon(
MdiIcons.incognito,
size: 15,
color: t.colorScheme.outline,
semanticLabel: '无痕',
),
],
const SizedBox(width: 10),
if (videoIntroController.isShowOnlineTotal)
Obx(
() => Text(
'${videoIntroController.total.value}人在看',
style: TextStyle(
fontSize: 12,
color: t.colorScheme.outline,
),
), ),
), ),
], ),
), ],
), ),
), ),
if (enableAi) if (enableAi)
@@ -488,7 +518,80 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
) )
], ],
), ),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: showIntroDetail,
child: ExpandablePanel(
controller: _expandableCtr,
collapsed: const SizedBox.shrink(),
expanded: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 8),
GestureDetector(
onTap: () {
Utils.copyText(
'${videoIntroController.videoDetail.value.bvid}');
},
child: Text(
'${videoIntroController.videoDetail.value.bvid}',
style: TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.primary,
),
),
),
if (videoIntroController
.videoDetail.value.descV2.isNullOrEmpty.not) ...[
const SizedBox(height: 8),
SelectableText.rich(
style: const TextStyle(
height: 1.4,
// fontSize: 13,
),
TextSpan(
children: [
buildContent(context,
videoIntroController.videoDetail.value),
],
),
),
],
if (videoIntroController.videoTags is List &&
videoIntroController.videoTags.isNotEmpty) ...[
const SizedBox(height: 8),
Wrap(
spacing: 8,
runSpacing: 8,
children: (videoIntroController.videoTags as List)
.map(
(item) => SearchText(
fontSize: 13,
searchText: item['tag_name'],
onSelect: (_) => Get.toNamed('/searchResult',
parameters: {
'keyword': item['tag_name']
}),
onLongSelect: (_) =>
Utils.copyText(item['tag_name']),
),
)
.toList(),
),
],
],
),
theme: const ExpandableThemeData(
animationDuration: Duration(milliseconds: 300),
scrollAnimationDuration: Duration(milliseconds: 300),
crossFadePoint: 0,
fadeCurve: Curves.ease,
sizeCurve: Curves.linear,
),
),
),
const SizedBox(height: 8),
Obx( Obx(
() => videoIntroController.queryVideoIntroData.value["status"] () => videoIntroController.queryVideoIntroData.value["status"]
? const SizedBox() ? const SizedBox()
@@ -748,4 +851,80 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
text: '转发'), text: '转发'),
]); ]);
} }
InlineSpan buildContent(BuildContext context, VideoDetailData content) {
final List descV2 = content.descV2!;
// type
// 1 普通文本
// 2 @用户
final List<TextSpan> spanChildren = List.generate(descV2.length, (index) {
final currentDesc = descV2[index];
switch (currentDesc.type) {
case 1:
final List<InlineSpan> spanChildren = <InlineSpan>[];
final RegExp urlRegExp = RegExp(r'https?://\S+\b');
final Iterable<Match> matches =
urlRegExp.allMatches(currentDesc.rawText);
int previousEndIndex = 0;
for (final Match match in matches) {
if (match.start > previousEndIndex) {
spanChildren.add(TextSpan(
text: currentDesc.rawText
.substring(previousEndIndex, match.start)));
}
spanChildren.add(
TextSpan(
text: match.group(0),
style: TextStyle(
color: Theme.of(context).colorScheme.primary), // 设置颜色为蓝色
recognizer: TapGestureRecognizer()
..onTap = () {
// 处理点击事件
try {
Get.toNamed(
'/webviewnew',
parameters: {
'url': match.group(0)!,
'type': 'url',
'pageTitle': match.group(0)!,
},
);
} catch (err) {
SmartDialog.showToast(err.toString());
}
},
),
);
previousEndIndex = match.end;
}
if (previousEndIndex < currentDesc.rawText.length) {
spanChildren.add(TextSpan(
text: currentDesc.rawText.substring(previousEndIndex)));
}
final TextSpan result = TextSpan(children: spanChildren);
return result;
case 2:
final Color colorSchemePrimary =
Theme.of(context).colorScheme.primary;
final String heroTag = Utils.makeHeroTag(currentDesc.bizId);
return TextSpan(
text: '@${currentDesc.rawText}',
style: TextStyle(color: colorSchemePrimary),
recognizer: TapGestureRecognizer()
..onTap = () {
Get.toNamed(
'/member?mid=${currentDesc.bizId}',
arguments: {'face': '', 'heroTag': heroTag},
);
},
);
default:
return const TextSpan();
}
});
return TextSpan(children: spanChildren);
}
} }

View File

@@ -1273,6 +1273,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
showIntroDetail: showIntroDetail, showIntroDetail: showIntroDetail,
), ),
), ),
SliverToBoxAdapter(
child: SizedBox(height: MediaQuery.paddingOf(context).bottom),
)
], ],
); );
} }

View File

@@ -480,6 +480,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.3" version: "5.0.3"
expandable:
dependency: "direct main"
description:
name: expandable
sha256: "9604d612d4d1146dafa96c6d8eec9c2ff0994658d6d09fed720ab788c7f5afc2"
url: "https://pub.dev"
source: hosted
version: "5.0.1"
extended_image: extended_image:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@@ -168,6 +168,7 @@ dependencies:
image_cropper: ^8.0.2 image_cropper: ^8.0.2
#解压直播消息 #解压直播消息
brotli: ^0.6.0 brotli: ^0.6.0
expandable: ^5.0.1
dependency_overrides: dependency_overrides:
screen_brightness: ^2.0.0+2 screen_brightness: ^2.0.0+2