mirror of
https://github.com/HChaZZY/PiliPlus.git
synced 2025-12-06 09:13:48 +08:00
opt: intro panel
Closes #14 Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
@@ -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']) {
|
||||||
|
|||||||
@@ -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']!;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1273,6 +1273,9 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
showIntroDetail: showIntroDetail,
|
showIntroDetail: showIntroDetail,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: SizedBox(height: MediaQuery.paddingOf(context).bottom),
|
||||||
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user