diff --git a/lib/http/api.dart b/lib/http/api.dart index 40b4fd5d..5b2cdf58 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -619,4 +619,7 @@ class Api { /// 获取空降区间 static const String getSkipSegments = '${HttpString.sponsorBlockBaseUrl}/api/skipSegments'; + + /// 视频标签 + static const String videoTag = '/x/tag/archive/tags'; } diff --git a/lib/http/video.dart b/lib/http/video.dart index b5d47fa3..6237abcb 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:dio/dio.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/utils/id_utils.dart'; +import 'package:pilipala/models/video/tags.dart'; import '../common/constants.dart'; import '../models/common/reply_type.dart'; import '../models/home/rcmd/result.dart'; @@ -607,4 +608,19 @@ class VideoHttp { }; } } + + // 获取视频标签 + static Future getVideoTag({required String bvid}) async { + var res = await Request().get(Api.videoTag, data: {'bvid': bvid}); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'].map((e) { + return VideoTagItem.fromJson(e); + }).toList() + }; + } else { + return {'status': false, 'data': [], 'msg': res.data['message']}; + } + } } diff --git a/lib/models/video/tags.dart b/lib/models/video/tags.dart new file mode 100644 index 00000000..f01f85ba --- /dev/null +++ b/lib/models/video/tags.dart @@ -0,0 +1,17 @@ +class VideoTagItem { + String? tagName; + int? tagId; + int? tagType; + + VideoTagItem({ + this.tagName, + this.tagId, + this.tagType, + }); + + VideoTagItem.fromJson(Map json) { + tagName = json['tag_name']; + tagId = json['tag_id']; + tagType = json['type']; + } +} diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index cfd65b2f..5ddede96 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -10,6 +10,7 @@ import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/user/fav_folder.dart'; import 'package:pilipala/models/video/ai.dart'; +import 'package:pilipala/models/video/tags.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/controller.dart'; import 'package:pilipala/pages/video/detail/reply/index.dart'; @@ -52,6 +53,7 @@ class VideoIntroController extends GetxController { RxInt lastPlayCid = 0.obs; var userInfo; + RxList videoTags = [].obs; // 同时观看 bool isShowOnlineTotal = false; @@ -82,6 +84,7 @@ class VideoIntroController extends GetxController { } enableRelatedVideo = setting.get(SettingBoxKey.enableRelatedVideo, defaultValue: true); + queryVideoTag(); } // 获取视频简介&分p @@ -678,4 +681,12 @@ class VideoIntroController extends GetxController { }, ); } + + // 获取视频标签 + void queryVideoTag() async { + var result = await VideoHttp.getVideoTag(bvid: bvid); + if (result['status']) { + videoTags.value = result['data']; + } + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index f6b6ed65..451173dd 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -387,7 +387,10 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ExpandablePanel( controller: _expandableCtr, collapsed: const SizedBox(height: 0), - expanded: IntroDetail(videoDetail: widget.videoDetail!), + expanded: IntroDetail( + videoDetail: widget.videoDetail!, + videoTags: videoIntroController.videoTags, + ), theme: const ExpandableThemeData( animationDuration: Duration(milliseconds: 300), scrollAnimationDuration: Duration(milliseconds: 300), diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index c74558a0..7387f29f 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -4,6 +4,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/http/constants.dart'; +import 'package:pilipala/models/video/tags.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/utils.dart'; @@ -11,13 +12,15 @@ class IntroDetail extends StatelessWidget { const IntroDetail({ super.key, this.videoDetail, + this.videoTags, }); + final dynamic videoDetail; + final RxList? videoTags; @override Widget build(BuildContext context) { TextStyle textStyle = TextStyle( - fontSize: 14, color: Theme.of(context).colorScheme.primary, ); return SizedBox( @@ -59,19 +62,26 @@ class IntroDetail extends StatelessWidget { ) ], ), - const SizedBox(height: 4), - SelectableRegion( - focusNode: FocusNode(), - selectionControls: MaterialTextSelectionControls(), - child: Text.rich( - style: const TextStyle(height: 1.4), - TextSpan( - children: [ - buildContent(context, videoDetail!), - ], + if (videoDetail!.descV2.isNotEmpty) ...[ + const SizedBox(height: 4), + SelectableRegion( + focusNode: FocusNode(), + selectionControls: MaterialTextSelectionControls(), + child: Text.rich( + style: TextStyle( + height: 1.4, color: Theme.of(context).colorScheme.outline), + TextSpan( + children: [ + buildContent(context, videoDetail!), + ], + ), ), ), - ), + ], + const SizedBox(height: 8), + Obx(() => null != videoTags && videoTags!.isNotEmpty + ? _buildTags(context, videoTags) + : const SizedBox.shrink()), ], ), ); @@ -152,4 +162,36 @@ class IntroDetail extends StatelessWidget { }); return TextSpan(children: spanChilds); } + + Widget _buildTags(BuildContext context, List? videoTags) { + final ColorScheme colorScheme = Theme.of(context).colorScheme; + return Wrap( + spacing: 6, + runSpacing: 6, + direction: Axis.horizontal, + textDirection: TextDirection.ltr, + children: videoTags!.map((tag) { + return InkWell( + onTap: () { + Get.toNamed('/searchResult', parameters: {'keyword': tag.tagName!}); + }, + borderRadius: BorderRadius.circular(6), + child: Container( + decoration: BoxDecoration( + color: colorScheme.surfaceVariant.withOpacity(0.5), + borderRadius: BorderRadius.circular(6), + ), + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 10), + child: Text( + tag.tagName!, + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurfaceVariant, + ), + ), + ), + ); + }).toList(), + ); + } }