diff --git a/lib/common/widgets/video_card_h.dart b/lib/common/widgets/video_card_h.dart index a53080f1..9f9cfc75 100644 --- a/lib/common/widgets/video_card_h.dart +++ b/lib/common/widgets/video_card_h.dart @@ -173,6 +173,7 @@ class VideoContent extends StatelessWidget { ), ] else ...[ RichText( + overflow: TextOverflow.ellipsis, maxLines: 2, text: TextSpan( children: [ diff --git a/lib/pages/dynamics/view.dart b/lib/pages/dynamics/view.dart index ad733caa..d9eb07b7 100644 --- a/lib/pages/dynamics/view.dart +++ b/lib/pages/dynamics/view.dart @@ -252,7 +252,7 @@ class _DynamicsPageState extends State maxCrossAxisExtent: Grid.maxRowWidth * 2, //cacheExtent: 0.0, crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.cardSpace, /// follow max child trailing layout offset and layout with full cross axis extend /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] diff --git a/lib/pages/dynamics/widgets/article_panel.dart b/lib/pages/dynamics/widgets/article_panel.dart index 9f1d7c43..c48c619f 100644 --- a/lib/pages/dynamics/widgets/article_panel.dart +++ b/lib/pages/dynamics/widgets/article_panel.dart @@ -1,15 +1,14 @@ import 'package:flutter/material.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../../common/constants.dart'; import 'pic_panel.dart'; Widget articlePanel(item, context, {floor = 1}) { TextStyle authorStyle = TextStyle(color: Theme.of(context).colorScheme.primary); return Padding( - padding: floor == 2 - ? EdgeInsets.zero - : const EdgeInsets.only(left: 12, right: 12), + padding: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages/dynamics/widgets/forward_panel.dart b/lib/pages/dynamics/widgets/forward_panel.dart index af7afeff..a13582c9 100644 --- a/lib/pages/dynamics/widgets/forward_panel.dart +++ b/lib/pages/dynamics/widgets/forward_panel.dart @@ -89,7 +89,11 @@ Widget forWard(item, context, ctr, source, {floor = 1}) { return videoSeasonWidget(item, context, 'archive', floor: floor); // 文章 case 'DYNAMIC_TYPE_ARTICLE': - return articlePanel(item, context, floor: floor); + return Container( + padding: + const EdgeInsets.only(left: 10, top: 12, right: 10, bottom: 10), + color: Theme.of(context).dividerColor.withOpacity(0.08), + child: articlePanel(item, context, floor: floor)); // 转发 case 'DYNAMIC_TYPE_FORWARD': return InkWell( diff --git a/lib/pages/dynamics/widgets/live_rcmd_panel.dart b/lib/pages/dynamics/widgets/live_rcmd_panel.dart index 4f255b04..2315fabe 100644 --- a/lib/pages/dynamics/widgets/live_rcmd_panel.dart +++ b/lib/pages/dynamics/widgets/live_rcmd_panel.dart @@ -44,9 +44,8 @@ Widget liveRcmdPanel(item, context, {floor = 1}) { const SizedBox(height: 4), if (item.modules.moduleDynamic.topic != null) ...[ Padding( - padding: floor == 2 - ? EdgeInsets.zero - : const EdgeInsets.only(left: 12, right: 12), + padding: + const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), child: GestureDetector( child: Text( '#${item.modules.moduleDynamic.topic.name}', @@ -60,90 +59,91 @@ Widget liveRcmdPanel(item, context, {floor = 1}) { Text.rich(richNode(item, context)), const SizedBox(height: 6), ], - GestureDetector( - onTap: () { - _dynamicsController.pushDetail(item, floor); - }, - child: LayoutBuilder(builder: (context, box) { - double width = box.maxWidth; - return Stack( - children: [ - Hero( - tag: liveRcmd.roomId.toString(), - child: NetworkImgLayer( - type: floor == 1 ? 'emote' : null, - width: width, - height: width / StyleString.aspectRatio, - src: item.modules.moduleDynamic.major.liveRcmd.cover, - ), - ), - PBadge( - text: watchedShow['text_large'], - top: 6, - right: 56, - bottom: null, - left: null, - type: 'gray', - ), - PBadge( - text: liveStatus == 1 ? '直播中' : '直播结束', - top: 6, - right: 6, - bottom: null, - left: null, - ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: Container( - height: 80, - padding: const EdgeInsets.fromLTRB(12, 0, 10, 10), - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - gradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black45, + Padding( + padding: + const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + child: GestureDetector( + onTap: () { + _dynamicsController.pushDetail(item, floor); + }, + child: LayoutBuilder(builder: (context, box) { + double width = box.maxWidth; + return Stack( + children: [ + Hero( + tag: liveRcmd.roomId.toString(), + child: NetworkImgLayer( + type: floor == 1 ? 'emote' : null, + width: width, + height: width / StyleString.aspectRatio, + src: item.modules.moduleDynamic.major.liveRcmd.cover, + ), + ), + PBadge( + text: watchedShow['text_large'], + top: 6, + right: 56, + bottom: null, + left: null, + type: 'gray', + ), + PBadge( + text: liveStatus == 1 ? '直播中' : '直播结束', + top: 6, + right: 6, + bottom: null, + left: null, + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + height: 80, + padding: const EdgeInsets.fromLTRB(12, 0, 10, 10), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black45, + ], + ), + borderRadius: floor == 1 + ? null + : const BorderRadius.all(Radius.circular(6))), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DefaultTextStyle.merge( + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelMedium! + .fontSize, + color: Colors.white), + child: Row( + children: [ + Text(item.modules.moduleDynamic.major.liveRcmd + .areaName ?? + ''), + ], + ), + ), ], ), - borderRadius: floor == 1 - ? null - : const BorderRadius.all(Radius.circular(6))), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - DefaultTextStyle.merge( - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize, - color: Colors.white), - child: Row( - children: [ - Text(item.modules.moduleDynamic.major.liveRcmd - .areaName ?? - ''), - ], - ), - ), - ], + ), ), - ), - ), - ], - ); - }), - ), + ], + ); + }), + )), const SizedBox(height: 6), Padding( - padding: floor == 1 - ? const EdgeInsets.only(left: 12, right: 12) - : EdgeInsets.zero, + padding: const EdgeInsets.symmetric(horizontal: StyleString.safeSpace), child: Text( item.modules.moduleDynamic.major.liveRcmd.title, maxLines: 1, diff --git a/lib/pages/dynamics/widgets/video_panel.dart b/lib/pages/dynamics/widgets/video_panel.dart index 42d59936..0de15e57 100644 --- a/lib/pages/dynamics/widgets/video_panel.dart +++ b/lib/pages/dynamics/widgets/video_panel.dart @@ -76,80 +76,84 @@ Widget videoSeasonWidget(item, context, type, {floor = 1}) { Text.rich(richNode(item, context)), const SizedBox(height: 6), ], - LayoutBuilder(builder: (context, box) { - double width = box.maxWidth; - return Stack( - children: [ - Hero( - tag: content.bvid, - child: NetworkImgLayer( - type: floor == 1 ? 'emote' : null, - width: width, - height: width / StyleString.aspectRatio, - src: content.cover, - semanticsLabel: content.title, - ), - ), - if (content.badge != null && type == 'pgc') - PBadge( - text: content.badge['text'], - top: 8.0, - right: 10.0, - bottom: null, - left: null, - ), - Positioned( - left: 0, - right: 0, - bottom: 0, - child: Container( - height: 80, - padding: const EdgeInsets.fromLTRB(12, 0, 10, 10), - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration( - gradient: const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black54, + Padding( + padding: EdgeInsets.symmetric(horizontal: StyleString.safeSpace), + child: LayoutBuilder(builder: (context, box) { + double width = box.maxWidth; + return Stack( + children: [ + Hero( + tag: content.bvid, + child: NetworkImgLayer( + type: floor == 1 ? 'emote' : null, + width: width, + height: width / StyleString.aspectRatio, + src: content.cover, + semanticsLabel: content.title, + ), + ), + if (content.badge != null && type == 'pgc') + PBadge( + text: content.badge['text'], + top: 8.0, + right: 10.0, + bottom: null, + left: null, + ), + Positioned( + left: 0, + right: 0, + bottom: 0, + child: Container( + height: 80, + padding: const EdgeInsets.fromLTRB(12, 0, 10, 10), + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + gradient: const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black54, + ], + ), + borderRadius: floor == 1 + ? null + : const BorderRadius.all(Radius.circular(6))), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DefaultTextStyle.merge( + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelMedium! + .fontSize, + color: Colors.white), + child: Row( + children: [ + Text(content.durationText ?? ''), + if (content.durationText != null) + const SizedBox(width: 10), + Text(content.stat.play + '次围观'), + const SizedBox(width: 10), + Text(content.stat.danmu + '条弹幕') + ], + ), + ), + Image.asset( + 'assets/images/play.png', + width: 60, + height: 60, + ), ], ), - borderRadius: floor == 1 - ? null - : const BorderRadius.all(Radius.circular(6))), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - DefaultTextStyle.merge( - style: TextStyle( - fontSize: - Theme.of(context).textTheme.labelMedium!.fontSize, - color: Colors.white), - child: Row( - children: [ - Text(content.durationText ?? ''), - if (content.durationText != null) - const SizedBox(width: 10), - Text(content.stat.play + '次围观'), - const SizedBox(width: 10), - Text(content.stat.danmu + '条弹幕') - ], - ), - ), - Image.asset( - 'assets/images/play.png', - width: 60, - height: 60, - ), - ], + ), ), - ), - ), - ], - ); - }), + ], + ); + })), const SizedBox(height: 6), Padding( padding: floor == 1 diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 0b308cc9..177e58e6 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -5,6 +5,9 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/pages/fav/index.dart'; import 'package:PiliPalaX/pages/fav/widgets/item.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; + class FavPage extends StatefulWidget { const FavPage({super.key}); @@ -60,17 +63,28 @@ class _FavPageState extends State { if (snapshot.connectionState == ConnectionState.done) { Map data = snapshot.data as Map; if (data['status']) { - return Obx( - () => ListView.builder( - controller: scrollController, - itemCount: _favController.favFolderData.value.list!.length, - itemBuilder: (context, index) { - return FavItem( - favFolderItem: - _favController.favFolderData.value.list![index]); - }, - ), - ); + return Obx(() => + CustomScrollView(controller: scrollController, slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth(context, + Grid.maxRowWidth * 2, StyleString.safeSpace) / + 1.9 / + StyleString.aspectRatio), + delegate: SliverChildBuilderDelegate( + childCount: + _favController.favFolderData.value.list!.length, + (BuildContext context, int index) { + return FavItem( + favFolderItem: _favController + .favFolderData.value.list![index]); + }, + ), + ) + ])); } else { return CustomScrollView( physics: const NeverScrollableScrollPhysics(), diff --git a/lib/pages/history/view.dart b/lib/pages/history/view.dart index 0986ed25..9a6748e5 100644 --- a/lib/pages/history/view.dart +++ b/lib/pages/history/view.dart @@ -6,6 +6,8 @@ import 'package:PiliPalaX/common/widgets/http_error.dart'; import 'package:PiliPalaX/common/widgets/no_data.dart'; import 'package:PiliPalaX/pages/history/index.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; import 'widgets/item.dart'; class HistoryPage extends StatefulWidget { @@ -189,7 +191,18 @@ class _HistoryPageState extends State { if (data['status']) { return Obx( () => _historyController.historyList.isNotEmpty - ? SliverList( + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth( + context, + Grid.maxRowWidth * 2, + StyleString.safeSpace) / + 2.1 / + StyleString.aspectRatio), delegate: SliverChildBuilderDelegate( (context, index) { return HistoryItem( diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index 3bcfef9e..ff98c9c8 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -146,7 +146,7 @@ class HistoryItem extends StatelessWidget { children: [ Padding( padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), + StyleString.safeSpace, 0, StyleString.safeSpace, 0), child: LayoutBuilder( builder: (context, boxConstraints) { double width = diff --git a/lib/pages/hot/view.dart b/lib/pages/hot/view.dart index 65b4e721..05becf44 100644 --- a/lib/pages/hot/view.dart +++ b/lib/pages/hot/view.dart @@ -93,7 +93,7 @@ class _HotPageState extends State with AutomaticKeepAliveClientMixin { () => SliverGrid( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( // 行间距 - mainAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.cardSpace, // 列间距 crossAxisSpacing: StyleString.safeSpace, // 最大宽度 diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index 28a872b3..2eebb19d 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -6,6 +6,9 @@ import 'package:PiliPalaX/common/widgets/no_data.dart'; import 'package:PiliPalaX/common/widgets/video_card_h.dart'; import 'package:PiliPalaX/pages/later/index.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; + class LaterPage extends StatefulWidget { const LaterPage({super.key}); @@ -77,7 +80,18 @@ class _LaterPageState extends State { return Obx( () => _laterController.laterList.isNotEmpty && !_laterController.isLoading.value - ? SliverList( + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth( + context, + Grid.maxRowWidth * 2, + StyleString.safeSpace) / + 1.9 / + StyleString.aspectRatio), delegate: SliverChildBuilderDelegate((context, index) { var videoItem = _laterController.laterList[index]; diff --git a/lib/pages/member_archive/view.dart b/lib/pages/member_archive/view.dart index 591cb3ec..2068e61c 100644 --- a/lib/pages/member_archive/view.dart +++ b/lib/pages/member_archive/view.dart @@ -3,7 +3,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/widgets/video_card_h.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../common/constants.dart'; import '../../common/widgets/http_error.dart'; +import '../../utils/grid.dart'; import 'controller.dart'; class MemberArchivePage extends StatefulWidget { @@ -72,7 +74,18 @@ class _MemberArchivePageState extends State { if (data['status']) { return Obx( () => list.isNotEmpty - ? SliverList( + ? SliverGrid( + gridDelegate: + SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth( + context, + Grid.maxRowWidth * 2, + StyleString.safeSpace) / + 1.9 / + StyleString.aspectRatio), delegate: SliverChildBuilderDelegate( (BuildContext context, index) { return VideoCardH( diff --git a/lib/pages/member_dynamics/view.dart b/lib/pages/member_dynamics/view.dart index e054a08d..8bd4513a 100644 --- a/lib/pages/member_dynamics/view.dart +++ b/lib/pages/member_dynamics/view.dart @@ -4,8 +4,11 @@ import 'package:get/get.dart'; import 'package:PiliPalaX/pages/member_dynamics/index.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../common/constants.dart'; import '../../common/widgets/http_error.dart'; +import '../../utils/grid.dart'; import '../dynamics/widgets/dynamic_panel.dart'; +import 'package:waterfall_flow/waterfall_flow.dart'; class MemberDynamicsPage extends StatefulWidget { const MemberDynamicsPage({super.key}); @@ -70,14 +73,28 @@ class _MemberDynamicsPageState extends State { if (data['status']) { return Obx( () => list.isNotEmpty - ? SliverList( - delegate: SliverChildBuilderDelegate( - (context, index) { - return DynamicPanel(item: list[index]); - }, - childCount: list.length, - ), - ) + ? SliverWaterfallFlow.extent( + maxCrossAxisExtent: Grid.maxRowWidth * 2, + //cacheExtent: 0.0, + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + + /// follow max child trailing layout offset and layout with full cross axis extend + /// last child as loadmore item/no more item in [GridView] and [WaterfallFlow] + /// with full cross axis extend + // LastChildLayoutType.fullCrossAxisExtend, + + /// as foot at trailing and layout with full cross axis extend + /// show no more item at trailing when children are not full of viewport + /// if children is full of viewport, it's the same as fullCrossAxisExtend + // LastChildLayoutType.foot, + lastChildLayoutTypeBuilder: (index) => + index == list.length + ? LastChildLayoutType.foot + : LastChildLayoutType.none, + children: [ + for (var i in list) DynamicPanel(item: i), + ]) : const SliverToBoxAdapter(), ); } else { diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index f6739cd5..8389919c 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -4,104 +4,118 @@ import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../../utils/grid.dart'; + Widget searchArticlePanel(BuildContext context, ctr, list) { TextStyle textStyle = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); - return ListView.builder( - controller: ctr!.scrollController, - itemCount: list.length, - itemBuilder: (context, index) { - return InkWell( - onTap: () { - Get.toNamed('/htmlRender', parameters: { - 'url': 'www.bilibili.com/read/cv${list[index].id}', - 'title': list[index].subTitle, - 'id': 'cv${list[index].id}', - 'dynamicType': 'read' - }); - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder(builder: (context, boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (list[index].imageUrls != null && - list[index].imageUrls.isNotEmpty) - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder(builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return NetworkImgLayer( - width: maxWidth, - height: maxHeight, - src: list[index].imageUrls.first, - ); - }), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - maxLines: 2, - text: TextSpan( + return CustomScrollView(controller: ctr.scrollController, slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth( + context, Grid.maxRowWidth * 2, StyleString.safeSpace) / + 1.9 / + StyleString.aspectRatio), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return InkWell( + onTap: () { + Get.toNamed('/htmlRender', parameters: { + 'url': 'www.bilibili.com/read/cv${list[index].id}', + 'title': list[index].subTitle, + 'id': 'cv${list[index].id}', + 'dynamicType': 'read' + }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 5, StyleString.safeSpace, 5), + child: LayoutBuilder(builder: (context, boxConstraints) { + final double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.textScalerOf(context).scale(1.0)) / + 2; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (list[index].imageUrls != null && + list[index].imageUrls.isNotEmpty) + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return NetworkImgLayer( + width: maxWidth, + height: maxHeight, + src: list[index].imageUrls.first, + ); + }), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (var i in list[index].title) ...[ - TextSpan( - text: i['text'], - style: TextStyle( - fontWeight: FontWeight.w500, - letterSpacing: 0.3, - color: i['type'] == 'em' - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .onSurface, - ), + RichText( + maxLines: 2, + text: TextSpan( + children: [ + for (var i in list[index].title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontWeight: FontWeight.w500, + letterSpacing: 0.3, + color: i['type'] == 'em' + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface, + ), + ), + ] + ], ), - ] + ), + const Spacer(), + Text( + Utils.dateFormat(list[index].pubTime, + formatType: 'detail'), + style: textStyle), + Row( + children: [ + Text('${list[index].view}浏览', + style: textStyle), + Text(' • ', style: textStyle), + Text('${list[index].reply}评论', + style: textStyle), + ], + ), ], ), ), - const Spacer(), - Text( - Utils.dateFormat(list[index].pubTime, - formatType: 'detail'), - style: textStyle), - Row( - children: [ - Text('${list[index].view}浏览', style: textStyle), - Text(' • ', style: textStyle), - Text('${list[index].reply}评论', style: textStyle), - ], - ), - ], - ), + ), + ], ), - ), - ], + ); + }), ), ); - }), - ), - ); - }, - ); + }, + )) + ]); } diff --git a/lib/pages/search_panel/widgets/live_panel.dart b/lib/pages/search_panel/widgets/live_panel.dart index ff9f11cc..c7011506 100644 --- a/lib/pages/search_panel/widgets/live_panel.dart +++ b/lib/pages/search_panel/widgets/live_panel.dart @@ -4,6 +4,8 @@ import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../../utils/grid.dart'; + Widget searchLivePanel(BuildContext context, ctr, list) { return Padding( padding: const EdgeInsets.only( @@ -11,13 +13,15 @@ Widget searchLivePanel(BuildContext context, ctr, list) { child: GridView.builder( primary: false, controller: ctr!.scrollController, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: StyleString.cardSpace + 2, - mainAxisSpacing: StyleString.cardSpace + 3, - mainAxisExtent: - MediaQuery.sizeOf(context).width / 2 / StyleString.aspectRatio + - MediaQuery.textScalerOf(context).scale(66.0)), + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: Grid.maxRowWidth, + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + mainAxisExtent: Grid.calculateActualWidth( + context, Grid.maxRowWidth, StyleString.safeSpace) / + StyleString.aspectRatio + + MediaQuery.textScalerOf(context).scale(80), + ), itemCount: list.length, itemBuilder: (context, index) { return LiveItem(liveItem: list![index]); diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart index 6c0dbbbe..44a4c097 100644 --- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart +++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart @@ -9,138 +9,155 @@ import 'package:PiliPalaX/models/bangumi/info.dart'; import 'package:PiliPalaX/models/common/search_type.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../../utils/grid.dart'; + Widget searchMbangumiPanel(BuildContext context, ctr, list) { TextStyle style = - TextStyle(fontSize: Theme.of(context).textTheme.labelMedium!.fontSize); - return ListView.builder( - controller: ctr!.scrollController, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - itemCount: list!.length, - itemBuilder: (context, index) { - var i = list![index]; - return InkWell( - onTap: () { - /// TODO 番剧详情页面 - // Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}', arguments: { - // 'videoItem': i, - // 'heroTag': Utils.makeHeroTag(i.id), - // 'videoType': SearchType.media_bangumi - // }); - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 7, StyleString.safeSpace, 7), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Stack( - children: [ - NetworkImgLayer( - width: 111, - height: 148, - src: i.cover, - ), - PBadge( - text: i.mediaType == 1 ? '番剧' : '国创', - top: 6.0, - right: 4.0, - bottom: null, - left: null, - ) - ], - ), - const SizedBox(width: 10), - Expanded( - child: Column( + TextStyle(fontSize: Theme.of(context).textTheme.labelMedium!.fontSize); + return Expanded( + child: CustomScrollView( + controller: ctr.scrollController, + slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: 157,), + delegate: + SliverChildBuilderDelegate((BuildContext context, int index) { + var i = list![index]; + return InkWell( + onTap: () { + /// TODO 番剧详情页面 + // Get.toNamed('/video?bvid=${i.bvid}&cid=${i.cid}', arguments: { + // 'videoItem': i, + // 'heroTag': Utils.makeHeroTag(i.id), + // 'videoType': SearchType.media_bangumi + // }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 7, StyleString.safeSpace, 2), + child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(height: 4), - RichText( - maxLines: 1, - overflow: TextOverflow.ellipsis, - text: TextSpan( - style: TextStyle( - color: Theme.of(context).colorScheme.onSurface), + Stack( + children: [ + NetworkImgLayer( + width: 111, + height: 148, + src: i.cover, + ), + PBadge( + text: i.mediaType == 1 ? '番剧' : '国创', + top: 6.0, + right: 4.0, + bottom: null, + left: null, + ) + ], + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - for (var i in i.title) ...[ - TextSpan( - text: i['text'], + const SizedBox(height: 4), + RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan( style: TextStyle( - fontSize: MediaQuery.textScalerOf(context) - .scale(Theme.of(context) - .textTheme - .titleSmall! - .fontSize!), - fontWeight: FontWeight.bold, - color: i['type'] == 'em' - ? Theme.of(context).colorScheme.primary - : Theme.of(context).colorScheme.onSurface, - ), + color: + Theme.of(context).colorScheme.onSurface), + children: [ + for (var i in i.title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontSize: MediaQuery.textScalerOf(context) + .scale(Theme.of(context) + .textTheme + .titleSmall! + .fontSize!), + fontWeight: FontWeight.bold, + color: i['type'] == 'em' + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface, + ), + ), + ], + ], ), - ], + ), + const SizedBox(height: 12), + Text('评分:${i.mediaScore['score'].toString()}', + style: style), + Row( + children: [ + Text(i.areas, style: style), + const SizedBox(width: 3), + const Text('·'), + const SizedBox(width: 3), + Text(Utils.dateFormat(i.pubtime).toString(), + style: style), + ], + ), + Row( + children: [ + Text(i.styles, style: style), + const SizedBox(width: 3), + const Text('·'), + const SizedBox(width: 3), + Text(i.indexShow, style: style), + ], + ), + const SizedBox(height: 18), + SizedBox( + height: 32, + child: ElevatedButton( + onPressed: () async { + SmartDialog.showLoading(msg: '获取中...'); + var res = await SearchHttp.bangumiInfo( + seasonId: i.seasonId); + SmartDialog.dismiss().then((value) { + if (res['status']) { + EpisodeItem episode = + res['data'].episodes.first; + String bvid = episode.bvid!; + int cid = episode.cid!; + String pic = episode.cover!; + String heroTag = Utils.makeHeroTag(cid); + Get.toNamed( + '/video?bvid=$bvid&cid=$cid&seasonId=${i.seasonId}', + arguments: { + 'pic': pic, + 'heroTag': heroTag, + 'videoType': SearchType.media_bangumi, + 'bangumiItem': res['data'], + }, + ); + } + }); + }, + child: const Text('观看'), + ), + ), ], ), ), - const SizedBox(height: 12), - Text('评分:${i.mediaScore['score'].toString()}', - style: style), - Row( - children: [ - Text(i.areas, style: style), - const SizedBox(width: 3), - const Text('·'), - const SizedBox(width: 3), - Text(Utils.dateFormat(i.pubtime).toString(), - style: style), - ], - ), - Row( - children: [ - Text(i.styles, style: style), - const SizedBox(width: 3), - const Text('·'), - const SizedBox(width: 3), - Text(i.indexShow, style: style), - ], - ), - const SizedBox(height: 18), - SizedBox( - height: 32, - child: ElevatedButton( - onPressed: () async { - SmartDialog.showLoading(msg: '获取中...'); - var res = await SearchHttp.bangumiInfo( - seasonId: i.seasonId); - SmartDialog.dismiss().then((value) { - if (res['status']) { - EpisodeItem episode = res['data'].episodes.first; - String bvid = episode.bvid!; - int cid = episode.cid!; - String pic = episode.cover!; - String heroTag = Utils.makeHeroTag(cid); - Get.toNamed( - '/video?bvid=$bvid&cid=$cid&seasonId=${i.seasonId}', - arguments: { - 'pic': pic, - 'heroTag': heroTag, - 'videoType': SearchType.media_bangumi, - 'bangumiItem': res['data'], - }, - ); - } - }); - }, - child: const Text('观看'), - ), - ), ], ), ), - ], - ), + ); + }, childCount: list.length), ), - ); - }, + ], + ), ); } diff --git a/lib/pages/search_panel/widgets/user_panel.dart b/lib/pages/search_panel/widgets/user_panel.dart index 3596f7b7..04f7a9b9 100644 --- a/lib/pages/search_panel/widgets/user_panel.dart +++ b/lib/pages/search_panel/widgets/user_panel.dart @@ -3,74 +3,79 @@ import 'package:get/get.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; import 'package:PiliPalaX/utils/utils.dart'; +import '../../../common/constants.dart'; +import '../../../utils/grid.dart'; + Widget searchUserPanel(BuildContext context, ctr, list) { TextStyle style = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); - return ListView.builder( - controller: ctr!.scrollController, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - itemCount: list!.length, - itemBuilder: (context, index) { - var i = list![index]; - String heroTag = Utils.makeHeroTag(i!.mid); - return InkWell( - onTap: () => Get.toNamed('/member?mid=${i.mid}', - arguments: {'heroTag': heroTag, 'face': i.upic}), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), - child: Row( - children: [ - Hero( - tag: heroTag, - child: NetworkImgLayer( - width: 42, - height: 42, - src: i.upic, - type: 'avatar', - ), - ), - const SizedBox(width: 10), - Column( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, + return CustomScrollView(controller: ctr.scrollController, slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: 52), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + var i = list![index]; + String heroTag = Utils.makeHeroTag(i!.mid); + return InkWell( + onTap: () => Get.toNamed('/member?mid=${i.mid}', + arguments: {'heroTag': heroTag, 'face': i.upic}), + child: Row( children: [ - Row( - children: [ - Text( - i!.uname, - style: const TextStyle( - fontSize: 14, - ), - ), - const SizedBox(width: 6), - Image.asset( - 'assets/images/lv/lv${i!.level}.png', - height: 11, - semanticLabel: '等级${i.level}', - ), - ], - ), - Row( - children: [ - Text('粉丝:${i.fans} ', style: style), - Text(' 视频:${i.videos}', style: style) - ], - ), - if (i.officialVerify['desc'] != '') - Text( - i.officialVerify['desc'], - style: style, + const SizedBox(width: 15), + Hero( + tag: heroTag, + child: NetworkImgLayer( + width: 42, + height: 42, + src: i.upic, + type: 'avatar', ), + ), + const SizedBox(width: 10), + Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Row( + children: [ + Text( + i!.uname, + style: const TextStyle( + fontSize: 14, + ), + ), + const SizedBox(width: 6), + Image.asset( + 'assets/images/lv/lv${i!.level}.png', + height: 11, + semanticLabel: '等级${i.level}', + ), + ], + ), + Row( + children: [ + Text('粉丝:${i.fans} ', style: style), + Text(' 视频:${i.videos}', style: style) + ], + ), + if (i.officialVerify['desc'] != '') + Text( + i.officialVerify['desc'], + style: style, + ), + ], + ) ], - ) - ], - ), - ), - ); - }, - ); + ), + ); + }, + )) + ]); } diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index 9cb115e4..7a3bb7ed 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -5,53 +5,30 @@ import 'package:PiliPalaX/common/widgets/video_card_h.dart'; import 'package:PiliPalaX/models/common/search_type.dart'; import 'package:PiliPalaX/pages/search_panel/index.dart'; +import '../../../common/constants.dart'; +import '../../../utils/grid.dart'; + class SearchVideoPanel extends StatelessWidget { SearchVideoPanel({ - this.ctr, - this.list, + required this.ctr, + required this.list, Key? key, }) : super(key: key); - final SearchPanelController? ctr; - final List? list; + final SearchPanelController ctr; + final List list; final VideoPanelController controller = Get.put(VideoPanelController()); @override Widget build(BuildContext context) { - return Stack( - alignment: Alignment.topCenter, + return Column( children: [ - Padding( - padding: const EdgeInsets.only(top: 36), - child: ListView.builder( - controller: ctr!.scrollController, - addAutomaticKeepAlives: false, - addRepaintBoundaries: false, - itemCount: list!.length, - itemBuilder: (context, index) { - var i = list![index]; - return Padding( - padding: index == 0 - ? const EdgeInsets.only(top: 2) - : EdgeInsets.zero, - child: VideoCardH(videoItem: i, showPubdate: true), - ); - }, - ), - ), // 分类筛选 Container( - width: double.infinity, - height: 36, + width: context.width, + height: 34, padding: const EdgeInsets.only(left: 8, top: 0, right: 12), - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide( - // color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - // ), - // ), - // ), child: Row( children: [ Expanded( @@ -67,11 +44,12 @@ class SearchVideoPanel extends StatelessWidget { type: i['type'], selectedType: controller.selectedType.value, callFn: (bool selected) async { + print('selected: $selected'); controller.selectedType.value = i['type']; - ctr!.order.value = + ctr.order.value = i['type'].toString().split('.').last; SmartDialog.showLoading(msg: 'loading'); - await ctr!.onRefresh(); + await ctr.onRefresh(); SmartDialog.dismiss(); }, ), @@ -101,7 +79,29 @@ class SearchVideoPanel extends StatelessWidget { ), ], ), - ), // 放置在ListView.builder()上方的组件 + ), + Expanded( + child: CustomScrollView( + controller: ctr.scrollController, + slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth(context, + Grid.maxRowWidth * 2, StyleString.safeSpace) / + 1.9 / + StyleString.aspectRatio), + delegate: SliverChildBuilderDelegate( + (BuildContext context, int index) { + return VideoCardH(videoItem: list[index], showPubdate: true); + }, + childCount: list.length, + ), + ), + ], + )), ], ); } @@ -126,7 +126,7 @@ class CustomFilterChip extends StatelessWidget { return SizedBox( height: 34, child: FilterChip( - padding: const EdgeInsets.only(left: 11, right: 11), + padding: const EdgeInsets.only(left: 8, right: 8), labelPadding: EdgeInsets.zero, label: Text( label!, diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index 44fa0db7..e76272f9 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -2,6 +2,8 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:PiliPalaX/common/widgets/http_error.dart'; +import '../../common/constants.dart'; +import '../../utils/grid.dart'; import 'controller.dart'; import 'widgets/item.dart'; @@ -51,17 +53,28 @@ class _SubPageState extends State { if (snapshot.connectionState == ConnectionState.done) { Map? data = snapshot.data; if (data != null && data['status']) { - return Obx( - () => ListView.builder( - controller: scrollController, - itemCount: _subController.subFolderData.value.list!.length, - itemBuilder: (context, index) { - return SubItem( - subFolderItem: - _subController.subFolderData.value.list![index]); - }, - ), - ); + return Obx(() => + CustomScrollView(controller: scrollController, slivers: [ + SliverGrid( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + mainAxisExtent: Grid.calculateActualWidth(context, + Grid.maxRowWidth * 2, StyleString.safeSpace) / + 1.9 / + StyleString.aspectRatio), + delegate: SliverChildBuilderDelegate( + childCount: + _subController.subFolderData.value.list!.length, + (BuildContext context, int index) { + return SubItem( + subFolderItem: _subController + .subFolderData.value.list![index]); + }, + ), + ) + ])); } else { return CustomScrollView( physics: const NeverScrollableScrollPhysics(),