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
void onInit() {
super.onInit();
queryVideoTags();
if (Get.arguments.isNotEmpty as bool) {
if (Get.arguments.containsKey('bangumiItem') as bool) {
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 {
var result = await UserHttp.videoTags(bvid: bvid);
if (result['status']) {

View File

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

View File

@@ -1,4 +1,8 @@
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_smart_dialog/flutter_smart_dialog.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 _favKey = GlobalKey<ActionItemState>();
late final _expandableCtr = ExpandableController(initialExpanded: false);
@override
void initState() {
@@ -167,6 +172,12 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true);
}
@override
void dispose() {
_expandableCtr.dispose();
super.dispose();
}
void _showFavBottomSheet() => showModalBottomSheet(
context: context,
useSafeArea: true,
@@ -222,7 +233,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
return;
}
feedBack();
widget.showIntroDetail();
// widget.showIntroDetail();
_expandableCtr.toggle();
}
// 用户主页
@@ -382,35 +394,54 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
const SizedBox(height: 8),
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(),
child: Row(children: [
Expanded(
onTap: showIntroDetail,
child: ExpandablePanel(
controller: _expandableCtr,
collapsed: GestureDetector(
onLongPress: () {
feedBack();
Utils.copyText(
'${widget.videoDetail?.title ?? videoItem['title'] ?? ''}');
},
child: Text(
widget.videoDetail?.title ?? videoItem['title'] ?? "",
// !loadingStatus
// ? "${widget.videoDetail?.title}"
// : videoItem['title'] ?? "",
'${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(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () => showIntroDetail(),
child: Padding(
padding: const EdgeInsets.only(top: 7, bottom: 6),
onTap: showIntroDetail,
child: Row(
children: <Widget>[
StatView(
@@ -463,7 +494,6 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
],
),
),
),
if (enableAi)
Positioned(
right: 10,
@@ -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(
() => videoIntroController.queryVideoIntroData.value["status"]
? const SizedBox()
@@ -748,4 +851,80 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
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,
),
),
SliverToBoxAdapter(
child: SizedBox(height: MediaQuery.paddingOf(context).bottom),
)
],
);
}

View File

@@ -480,6 +480,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:

View File

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