diff --git a/lib/common/widgets/video_card/video_card_h.dart b/lib/common/widgets/video_card/video_card_h.dart index fa1249180..017f7079e 100644 --- a/lib/common/widgets/video_card/video_card_h.dart +++ b/lib/common/widgets/video_card/video_card_h.dart @@ -65,7 +65,7 @@ class VideoCardH extends StatelessWidget { title: videoItem.title, cover: videoItem.cover, ); - final colorScheme = ColorScheme.of(context); + final theme = Theme.of(context); return Material( type: MaterialType.transparency, child: Stack( @@ -175,9 +175,9 @@ class VideoCardH extends StatelessWidget { bottom: 0, right: 0, child: VideoProgressIndicator( - color: colorScheme.primary, + color: theme.colorScheme.primary, backgroundColor: - colorScheme.secondaryContainer, + theme.colorScheme.secondaryContainer, progress: progress == -1 ? 1 : progress / videoItem.duration, @@ -198,7 +198,7 @@ class VideoCardH extends StatelessWidget { ), ), const SizedBox(width: 10), - content(context), + content(theme), ], ), ), @@ -219,8 +219,7 @@ class VideoCardH extends StatelessWidget { ); } - Widget content(BuildContext context) { - final theme = Theme.of(context); + Widget content(ThemeData theme) { String pubdate = DateFormatUtils.dateFormat(videoItem.pubdate!); if (pubdate != '') pubdate += ' '; return Expanded( diff --git a/lib/grpc/space.dart b/lib/grpc/space.dart index e81ae17af..232b86bcc 100644 --- a/lib/grpc/space.dart +++ b/lib/grpc/space.dart @@ -1,4 +1,6 @@ import 'package:PiliPlus/grpc/bilibili/app/dynamic/v2.pb.dart'; +import 'package:PiliPlus/grpc/bilibili/app/interfaces/v1.pb.dart' + show SearchArchiveReply, SearchArchiveReq; import 'package:PiliPlus/grpc/bilibili/pagination.pb.dart'; import 'package:PiliPlus/grpc/grpc_req.dart'; import 'package:PiliPlus/grpc/url.dart'; @@ -24,4 +26,22 @@ abstract final class SpaceGrpc { OpusSpaceFlowResp.fromBuffer, ); } + + static Future> searchArchive({ + required String keyword, + required Int64 mid, + required int pn, + required Int64 ps, + }) { + return GrpcReq.request( + GrpcUrl.searchArchive, + SearchArchiveReq( + keyword: keyword, + mid: mid, + pn: Int64(pn), + ps: ps, + ), + SearchArchiveReply.fromBuffer, + ); + } } diff --git a/lib/grpc/url.dart b/lib/grpc/url.dart index 0769a5f2b..6ace7c1cc 100644 --- a/lib/grpc/url.dart +++ b/lib/grpc/url.dart @@ -55,4 +55,8 @@ abstract final class GrpcUrl { static const audioThumbUp = '$audio/ThumbUp'; static const audioTripleLike = '$audio/TripleLike'; static const audioCoinAdd = '$audio/CoinAdd'; + + // space + static const space = '/bilibili.app.interface.v1.Space'; + static const searchArchive = '$space/SearchArchive'; } diff --git a/lib/pages/member_search/child/controller.dart b/lib/pages/member_search/child/controller.dart index 52dbec9e1..69bfffe69 100644 --- a/lib/pages/member_search/child/controller.dart +++ b/lib/pages/member_search/child/controller.dart @@ -1,10 +1,13 @@ +import 'package:PiliPlus/grpc/bilibili/app/interfaces/v1.pb.dart' + show SearchArchiveReply; +import 'package:PiliPlus/grpc/space.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/member.dart'; import 'package:PiliPlus/models/common/member/search_type.dart'; import 'package:PiliPlus/models/dynamics/result.dart'; -import 'package:PiliPlus/models_new/member/search_archive/data.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/member_search/controller.dart'; +import 'package:fixnum/fixnum.dart' show Int64; class MemberSearchChildController extends CommonListController { MemberSearchChildController(this.controller, this.searchType); @@ -12,22 +15,18 @@ class MemberSearchChildController extends CommonListController { final MemberSearchController controller; final MemberSearchType searchType; - dynamic offset; + // archive + late final _ps = Int64(20); + late final _midInt64 = Int64(int.parse(controller.mid)); + + // dynamic + String? offset; @override void checkIsEnd(int length) { - switch (searchType) { - case MemberSearchType.archive: - if (controller.counts.first != -1 && - length >= controller.counts.first) { - isEnd = true; - } - break; - case MemberSearchType.dynamic: - if (controller.counts[1] != -1 && length >= controller.counts[1]) { - isEnd = true; - } - break; + final count = controller.counts[searchType.index]; + if (count != -1 && length >= count) { + isEnd = true; } } @@ -35,9 +34,9 @@ class MemberSearchChildController extends CommonListController { List? getDataList(response) { switch (searchType) { case MemberSearchType.archive: - SearchArchiveData data = response; - controller.counts[searchType.index] = data.page?.count ?? 0; - return data.list?.vlist; + SearchArchiveReply data = response; + controller.counts[searchType.index] = data.total.toInt(); + return data.archives; case MemberSearchType.dynamic: DynamicsDataModel data = response; offset = data.offset; @@ -58,9 +57,10 @@ class MemberSearchChildController extends CommonListController { @override Future customGetData() { return switch (searchType) { - MemberSearchType.archive => MemberHttp.searchArchive( - mid: controller.mid, + MemberSearchType.archive => SpaceGrpc.searchArchive( + mid: _midInt64, pn: page, + ps: _ps, keyword: controller.editingController.text, ), MemberSearchType.dynamic => MemberHttp.dynSearch( diff --git a/lib/pages/member_search/child/view.dart b/lib/pages/member_search/child/view.dart index aa9583572..ec4259884 100644 --- a/lib/pages/member_search/child/view.dart +++ b/lib/pages/member_search/child/view.dart @@ -1,10 +1,10 @@ import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; -import 'package:PiliPlus/common/widgets/video_card/video_card_h.dart'; import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/member/search_type.dart'; import 'package:PiliPlus/pages/dynamics/widgets/dynamic_panel.dart'; import 'package:PiliPlus/pages/member_search/child/controller.dart'; +import 'package:PiliPlus/pages/member_search/child/widgets/search_archive_grpc.dart'; import 'package:PiliPlus/utils/global_data.dart'; import 'package:PiliPlus/utils/grid.dart'; import 'package:PiliPlus/utils/waterfall.dart'; @@ -78,10 +78,7 @@ class _MemberSearchChildPageState extends State if (index == response.length - 1) { _controller.onLoadMore(); } - // TODO: dimension - return VideoCardH( - videoItem: response[index], - ); + return SearchArchiveGrpc(item: response[index]); }, itemCount: response.length, ), diff --git a/lib/pages/member_search/child/widgets/search_archive_grpc.dart b/lib/pages/member_search/child/widgets/search_archive_grpc.dart new file mode 100644 index 000000000..ff4098e52 --- /dev/null +++ b/lib/pages/member_search/child/widgets/search_archive_grpc.dart @@ -0,0 +1,228 @@ +import 'package:PiliPlus/common/style.dart'; +import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/flutter/layout_builder.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/grpc/bilibili/app/interfaces/v1.pb.dart' show Arc; +import 'package:PiliPlus/http/user.dart'; +import 'package:PiliPlus/models/common/badge_type.dart'; +import 'package:PiliPlus/models/common/stat_type.dart'; +import 'package:PiliPlus/utils/date_utils.dart'; +import 'package:PiliPlus/utils/duration_utils.dart'; +import 'package:PiliPlus/utils/em.dart'; +import 'package:PiliPlus/utils/extension/dimension_ext.dart'; +import 'package:PiliPlus/utils/id_utils.dart'; +import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:PiliPlus/utils/platform_utils.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:fixnum/fixnum.dart' show Int64; +import 'package:flutter/material.dart' hide LayoutBuilder; +import 'package:material_design_icons_flutter/material_design_icons_flutter.dart'; + +class SearchArchiveGrpc extends StatelessWidget { + const SearchArchiveGrpc({ + super.key, + required this.item, + }); + + final Arc item; + + @override + Widget build(BuildContext context) { + final arc = item.archive; + final bvid = IdUtils.av2bv(arc.aid.toInt()); + final regTitle = Em.regTitle(arc.title); + final titleStr = regTitle.map((e) => e.text).join(); + void onLongPress() => imageSaveDialog( + bvid: bvid, + title: titleStr, + cover: arc.pic, + ); + return Material( + type: MaterialType.transparency, + child: Stack( + clipBehavior: Clip.none, + children: [ + InkWell( + onLongPress: onLongPress, + onSecondaryTap: PlatformUtils.isMobile ? null : onLongPress, + onTap: () { + if (item.isPugv) { + PageUtils.viewPgcFromUri(item.uri, isPgc: false); + return; + } + if (arc.hasRedirectUrl()) { + PageUtils.viewPgcFromUri(arc.redirectUrl); + return; + } + PageUtils.toVideoPage( + bvid: bvid, + cid: arc.firstCid.toInt(), + cover: arc.pic, + title: titleStr, + isVertical: arc.dimension.isVertical, + ); + }, + child: Padding( + padding: const .symmetric( + horizontal: Style.safeSpace, + vertical: 5, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: Style.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + final double maxWidth = boxConstraints.maxWidth; + final double maxHeight = boxConstraints.maxHeight; + return Stack( + clipBehavior: Clip.none, + children: [ + NetworkImgLayer( + src: arc.pic, + width: maxWidth, + height: maxHeight, + ), + if (item.isPugv) + const PBadge( + text: '课堂', + top: 6.0, + right: 6.0, + ), + if (arc.duration > Int64.ZERO) + PBadge( + text: DurationUtils.formatDuration( + arc.duration.toInt(), + ), + right: 6.0, + bottom: 6.0, + type: PBadgeType.gray, + ), + ], + ); + }, + ), + ), + const SizedBox(width: 10), + content(context, regTitle), + ], + ), + ), + ), + Positioned( + bottom: 0, + right: 12, + width: 29, + height: 29, + child: PopupMenuButton( + padding: EdgeInsets.zero, + icon: Icon( + Icons.more_vert_outlined, + color: Theme.of(context).colorScheme.outline, + size: 17, + ), + position: PopupMenuPosition.under, + itemBuilder: (context) => [ + PopupMenuItem( + height: 45, + onTap: () => Utils.copyText(bvid), + child: Row( + spacing: 6, + children: [ + const Stack( + clipBehavior: Clip.none, + children: [ + Icon(MdiIcons.identifier, size: 16), + Icon(MdiIcons.circleOutline, size: 16), + ], + ), + Text(bvid, style: const TextStyle(fontSize: 13)), + ], + ), + ), + PopupMenuItem( + height: 45, + onTap: () => UserHttp.toViewLater(bvid: bvid), + child: const Row( + spacing: 6, + children: [ + Icon(MdiIcons.clockTimeEightOutline, size: 16), + Text('稍后再看', style: TextStyle(fontSize: 13)), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } + + Widget content( + BuildContext context, + List<({bool isEm, String text})> regTitle, + ) { + final arc = item.archive; + final theme = Theme.of(context); + String pubdate = DateFormatUtils.dateFormat(arc.pubdate.toInt()); + if (pubdate != '') pubdate += ' '; + return Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text.rich( + overflow: TextOverflow.ellipsis, + maxLines: 2, + TextSpan( + children: regTitle + .map( + (e) => TextSpan( + text: e.text, + style: TextStyle( + fontSize: theme.textTheme.bodyMedium!.fontSize, + height: 1.42, + letterSpacing: 0.3, + color: e.isEm + ? theme.colorScheme.primary + : theme.colorScheme.onSurface, + ), + ), + ) + .toList(), + ), + ), + ), + Text( + "$pubdate${arc.author.name}", + maxLines: 1, + style: TextStyle( + fontSize: 12, + height: 1, + color: theme.colorScheme.outline, + overflow: TextOverflow.clip, + ), + ), + const SizedBox(height: 3), + Row( + spacing: 8, + children: [ + StatWidget( + type: StatType.play, + value: arc.stat.view, + ), + StatWidget( + type: StatType.danmaku, + value: arc.stat.danmaku, + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/video/controller.dart b/lib/pages/video/controller.dart index 0f5c3a966..0abc9a9ef 100644 --- a/lib/pages/video/controller.dart +++ b/lib/pages/video/controller.dart @@ -198,10 +198,25 @@ class VideoDetailController extends GetxController @pragma('vm:notify-debugger-on-exception') void setVideoHeight() { try { - final isVertical = firstVideo.width != null && firstVideo.height != null - ? firstVideo.width! < firstVideo.height! - : false; - if (!scrollCtr.hasClients) { + var width = firstVideo.width; + var height = firstVideo.height; + if (width == null || height == null) { + if (isUgc && !isFileSource) { + final ugcIntroCtr = Get.find(tag: heroTag); + final data = ugcIntroCtr.videoDetail.value; + if (data.cid == cid.value) { + width = data.dimension!.width!; + height = data.dimension!.height!; + } else { + ugcIntroCtr.queryVideoIntro().whenComplete(setVideoHeight); + return; + } + } else { + return; + } + } + final isVertical = height > width; + if (_scrollCtr?.hasClients != true) { videoHeight = isVertical ? maxVideoHeight : minVideoHeight; this.isVertical.value = isVertical; return; @@ -298,11 +313,7 @@ class VideoDetailController extends GetxController defaultST = Duration.zero; } data = PlayUrlModel(timeLength: entry.totalTimeMilli); - if (isInit) { - Future.delayed(const Duration(milliseconds: 120), setVideoHeight); - } else { - setVideoHeight(); - } + setVideoHeight(); } @override diff --git a/lib/utils/extension/dimension_ext.dart b/lib/utils/extension/dimension_ext.dart new file mode 100644 index 000000000..151d953b6 --- /dev/null +++ b/lib/utils/extension/dimension_ext.dart @@ -0,0 +1,5 @@ +import 'package:PiliPlus/grpc/bilibili/app/archive/v1.pb.dart' show Dimension; + +extension DimensionExt on Dimension { + bool get isVertical => rotate == .ONE ? width > height : height > width; +}