From a63a9a28d7df50d38a359373fc9dcd2a680a4e3f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 29 Jun 2024 13:53:43 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=9C=80=E8=BF=91=E7=82=B9?= =?UTF-8?q?=E8=B5=9E=E7=9A=84=E8=A7=86=E9=A2=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/member.dart | 5 +- lib/models/member/like.dart | 210 ++++++++++++++++++++++++ lib/pages/member/controller.dart | 10 ++ lib/pages/member/view.dart | 62 +++++-- lib/pages/member/widgets/conis.dart | 8 +- lib/pages/member/widgets/like.dart | 31 ++++ lib/pages/member_like/widgets/item.dart | 96 +++++++++++ 7 files changed, 400 insertions(+), 22 deletions(-) create mode 100644 lib/models/member/like.dart create mode 100644 lib/pages/member/widgets/like.dart create mode 100644 lib/pages/member_like/widgets/item.dart diff --git a/lib/http/member.dart b/lib/http/member.dart index 1af0f9a4..20a2c728 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -1,5 +1,6 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/models/member/like.dart'; import '../common/constants.dart'; import '../models/dynamics/result.dart'; import '../models/follow/result.dart'; @@ -328,7 +329,9 @@ class MemberHttp { if (res.data['code'] == 0) { return { 'status': true, - 'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists']) + 'data': res.data['data']['list'] + .map((e) => MemberLikeDataModel.fromJson(e)) + .toList(), }; } else { return { diff --git a/lib/models/member/like.dart b/lib/models/member/like.dart new file mode 100644 index 00000000..df71e2ec --- /dev/null +++ b/lib/models/member/like.dart @@ -0,0 +1,210 @@ +class MemberLikeDataModel { + MemberLikeDataModel({ + this.aid, + this.videos, + this.tid, + this.tname, + this.pic, + this.title, + this.pubdate, + this.ctime, + this.desc, + this.state, + this.duration, + this.redirectUrl, + this.rights, + this.owner, + this.stat, + this.dimension, + this.cover43, + this.bvid, + this.interVideo, + this.resourceType, + this.subtitle, + this.enableVt, + }); + + final int? aid; + final int? videos; + final int? tid; + final String? tname; + final String? pic; + final String? title; + final int? pubdate; + final int? ctime; + final String? desc; + final int? state; + final int? duration; + final String? redirectUrl; + final Rights? rights; + final Owner? owner; + final Stat? stat; + final Dimension? dimension; + final String? cover43; + final String? bvid; + final bool? interVideo; + final String? resourceType; + final String? subtitle; + final int? enableVt; + + factory MemberLikeDataModel.fromJson(Map json) => + MemberLikeDataModel( + aid: json["aid"], + videos: json["videos"], + tid: json["tid"], + tname: json["tname"], + pic: json["pic"], + title: json["title"], + pubdate: json["pubdate"], + ctime: json["ctime"], + desc: json["desc"], + state: json["state"], + duration: json["duration"], + redirectUrl: json["redirect_url"], + rights: Rights.fromJson(json["rights"]), + owner: Owner.fromJson(json["owner"]), + stat: Stat.fromJson(json["stat"]), + dimension: Dimension.fromJson(json["dimension"]), + cover43: json["cover43"], + bvid: json["bvid"], + interVideo: json["inter_video"], + resourceType: json["resource_type"], + subtitle: json["subtitle"], + enableVt: json["enable_vt"], + ); +} + +class Dimension { + Dimension({ + required this.width, + required this.height, + required this.rotate, + }); + + final int width; + final int height; + final int rotate; + + factory Dimension.fromJson(Map json) => Dimension( + width: json["width"], + height: json["height"], + rotate: json["rotate"], + ); +} + +class Owner { + Owner({ + required this.mid, + required this.name, + required this.face, + }); + + final int mid; + final String name; + final String face; + + factory Owner.fromJson(Map json) => Owner( + mid: json["mid"], + name: json["name"], + face: json["face"], + ); +} + +class Rights { + Rights({ + required this.bp, + required this.elec, + required this.download, + required this.movie, + required this.pay, + required this.hd5, + required this.noReprint, + required this.autoplay, + required this.ugcPay, + required this.isCooperation, + required this.ugcPayPreview, + required this.noBackground, + required this.arcPay, + required this.payFreeWatch, + }); + + final int bp; + final int elec; + final int download; + final int movie; + final int pay; + final int hd5; + final int noReprint; + final int autoplay; + final int ugcPay; + final int isCooperation; + final int ugcPayPreview; + final int noBackground; + final int arcPay; + final int payFreeWatch; + + factory Rights.fromJson(Map json) => Rights( + bp: json["bp"], + elec: json["elec"], + download: json["download"], + movie: json["movie"], + pay: json["pay"], + hd5: json["hd5"], + noReprint: json["no_reprint"], + autoplay: json["autoplay"], + ugcPay: json["ugc_pay"], + isCooperation: json["is_cooperation"], + ugcPayPreview: json["ugc_pay_preview"], + noBackground: json["no_background"], + arcPay: json["arc_pay"], + payFreeWatch: json["pay_free_watch"], + ); +} + +class Stat { + Stat({ + required this.aid, + required this.view, + required this.danmaku, + required this.reply, + required this.favorite, + required this.coin, + required this.share, + required this.nowRank, + required this.hisRank, + required this.like, + required this.dislike, + required this.vt, + required this.vv, + }); + + final int aid; + final int view; + final int danmaku; + final int reply; + final int favorite; + final int coin; + final int share; + final int nowRank; + final int hisRank; + final int like; + final int dislike; + final int vt; + final int vv; + + factory Stat.fromJson(Map json) => Stat( + aid: json["aid"], + view: json["view"], + danmaku: json["danmaku"], + reply: json["reply"], + favorite: json["favorite"], + coin: json["coin"], + share: json["share"], + nowRank: json["now_rank"], + hisRank: json["his_rank"], + like: json["like"], + dislike: json["dislike"], + vt: json["vt"], + vv: json["vv"], + ); +} diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 0aa7166f..7db046d4 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -8,6 +8,7 @@ import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/member/archive.dart'; import 'package:pilipala/models/member/coin.dart'; import 'package:pilipala/models/member/info.dart'; +import 'package:pilipala/models/member/like.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:share_plus/share_plus.dart'; @@ -25,6 +26,7 @@ class MemberController extends GetxController { RxInt attribute = (-1).obs; RxString attributeText = '关注'.obs; RxList recentCoinsList = [].obs; + RxList recentLikeList = [].obs; @override void onInit() { @@ -208,6 +210,14 @@ class MemberController extends GetxController { return res; } + // 请求点赞视频 + Future getRecentLikeVideo() async { + if (userInfo == null) return; + var res = await MemberHttp.getRecentLikeVideo(mid: mid); + recentLikeList.value = res['data']; + return res; + } + // 跳转查看动态 void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid'); diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index bb0d92be..9c0da652 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -9,6 +9,7 @@ import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/utils/utils.dart'; import 'widgets/conis.dart'; +import 'widgets/like.dart'; import 'widgets/profile.dart'; import 'widgets/seasons.dart'; @@ -26,6 +27,7 @@ class _MemberPageState extends State late Future _futureBuilderFuture; late Future _memberSeasonsFuture; late Future _memberCoinsFuture; + late Future _memberLikeFuture; final ScrollController _extendNestCtr = ScrollController(); final StreamController appbarStream = StreamController(); late int mid; @@ -39,6 +41,7 @@ class _MemberPageState extends State _futureBuilderFuture = _memberController.getInfo(); _memberSeasonsFuture = _memberController.getMemberSeasons(); _memberCoinsFuture = _memberController.getRecentCoinVideo(); + _memberLikeFuture = _memberController.getRecentLikeVideo(); _extendNestCtr.addListener( () { final double offset = _extendNestCtr.position.pixels; @@ -162,6 +165,7 @@ class _MemberPageState extends State trailing: const Icon(Icons.arrow_forward_outlined, size: 19), ), + const Divider(height: 1, thickness: 0.1), /// 视频 ListTile( @@ -170,12 +174,10 @@ class _MemberPageState extends State trailing: const Icon(Icons.arrow_forward_outlined, size: 19), ), + const Divider(height: 1, thickness: 0.1), /// 专栏 - ListTile( - onTap: () {}, - title: const Text('Ta的专栏'), - ), + const ListTile(title: Text('Ta的专栏')), MediaQuery.removePadding( removeTop: true, removeBottom: true, @@ -218,12 +220,7 @@ class _MemberPageState extends State /// 最近投币 Obx( () => _memberController.recentCoinsList.isNotEmpty - ? ListTile( - onTap: () {}, - title: const Text('最近投币的视频'), - // trailing: const Icon(Icons.arrow_forward_outlined, - // size: 19), - ) + ? const ListTile(title: Text('最近投币的视频')) : const SizedBox(), ), MediaQuery.removePadding( @@ -257,13 +254,44 @@ class _MemberPageState extends State ), ), ), - // 最近点赞 - // ListTile( - // onTap: () {}, - // title: const Text('最近点赞的视频'), - // trailing: - // const Icon(Icons.arrow_forward_outlined, size: 19), - // ), + + /// 最近点赞 + Obx( + () => _memberController.recentLikeList.isNotEmpty + ? const ListTile(title: Text('最近点赞的视频')) + : const SizedBox(), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: FutureBuilder( + future: _memberLikeFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + return MemberLikePanel(data: data['data']); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), + ), + ), ], ), ), diff --git a/lib/pages/member/widgets/conis.dart b/lib/pages/member/widgets/conis.dart index 57a8a583..bdff2120 100644 --- a/lib/pages/member/widgets/conis.dart +++ b/lib/pages/member/widgets/conis.dart @@ -4,8 +4,8 @@ import 'package:pilipala/models/member/coin.dart'; import 'package:pilipala/pages/member_coin/widgets/item.dart'; class MemberCoinsPanel extends StatelessWidget { - final List? data; - const MemberCoinsPanel({super.key, this.data}); + final List data; + const MemberCoinsPanel({super.key, required this.data}); @override Widget build(BuildContext context) { @@ -20,9 +20,9 @@ class MemberCoinsPanel extends StatelessWidget { ), physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, - itemCount: data!.length, + itemCount: data.length, itemBuilder: (context, i) { - return MemberCoinsItem(coinItem: data![i]); + return MemberCoinsItem(coinItem: data[i]); }, ); }, diff --git a/lib/pages/member/widgets/like.dart b/lib/pages/member/widgets/like.dart new file mode 100644 index 00000000..6342c274 --- /dev/null +++ b/lib/pages/member/widgets/like.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/models/member/like.dart'; +import 'package:pilipala/pages/member_like/widgets/item.dart'; + +class MemberLikePanel extends StatelessWidget { + final List data; + const MemberLikePanel({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, boxConstraints) { + return GridView.builder( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Use a fixed count for GridView + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + childAspectRatio: 0.94, + ), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: data.length, + itemBuilder: (context, i) { + return MemberLikeItem(likeItem: data[i]); + }, + ); + }, + ); + } +} diff --git a/lib/pages/member_like/widgets/item.dart b/lib/pages/member_like/widgets/item.dart new file mode 100644 index 00000000..57798bb7 --- /dev/null +++ b/lib/pages/member_like/widgets/item.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/badge.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/common/widgets/stat/view.dart'; +import 'package:pilipala/http/search.dart'; +import 'package:pilipala/models/member/like.dart'; +import 'package:pilipala/utils/utils.dart'; + +class MemberLikeItem extends StatelessWidget { + final MemberLikeDataModel likeItem; + + const MemberLikeItem({ + Key? key, + required this.likeItem, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + String heroTag = Utils.makeHeroTag(likeItem.aid); + return Card( + elevation: 0, + clipBehavior: Clip.hardEdge, + margin: EdgeInsets.zero, + child: InkWell( + onTap: () async { + int cid = + await SearchHttp.ab2c(aid: likeItem.aid, bvid: likeItem.bvid); + Get.toNamed('/video?bvid=${likeItem.bvid}&cid=$cid', + arguments: {'videoItem': likeItem, 'heroTag': heroTag}); + }, + child: Column( + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder(builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return Stack( + children: [ + NetworkImgLayer( + src: likeItem.pic, + width: maxWidth, + height: maxHeight, + ), + if (likeItem.duration != null) + PBadge( + bottom: 6, + right: 6, + type: 'gray', + text: Utils.timeFormat(likeItem.duration), + ) + ], + ); + }), + ), + Padding( + padding: const EdgeInsets.fromLTRB(5, 6, 0, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + likeItem.title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 4), + Row( + children: [ + StatView( + view: likeItem.stat!.view, + theme: 'gray', + ), + const Spacer(), + Text( + Utils.CustomStamp_str( + timestamp: likeItem.pubdate, date: 'MM-DD'), + style: TextStyle( + fontSize: 11, + color: Theme.of(context).colorScheme.outline, + ), + ), + const SizedBox(width: 6) + ], + ), + ], + ), + ), + ], + ), + ), + ); + } +} From ea7ae15384660071505332282154a6d8f2037ed9 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 29 Jun 2024 14:29:43 +0800 Subject: [PATCH 2/2] =?UTF-8?q?mod:=20=E5=90=88=E9=9B=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/controller.dart | 7 +++- lib/pages/member/view.dart | 52 +++++++++++++-------------- lib/pages/member/widgets/seasons.dart | 44 +++++++++++++---------- lib/pages/member_seasons/view.dart | 3 +- 4 files changed, 58 insertions(+), 48 deletions(-) diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 7db046d4..cc928a8d 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -192,12 +192,17 @@ class MemberController extends GetxController { Share.share('${memberInfo.value.name} - https://space.bilibili.com/$mid'); } - // 请求专栏 + // 请求合集 Future getMemberSeasons() async { if (userInfo == null) return; var res = await MemberHttp.getMemberSeasons(mid, 1, 10); if (!res['status']) { SmartDialog.showToast("用户专栏请求异常:${res['msg']}"); + } else { + // 只取前四个专栏 + res['data'].seasonsList.map((e) { + e.archives = e.archives!.sublist(0, 4); + }).toList(); } return res; } diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 9c0da652..f62ffacc 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -178,39 +178,37 @@ class _MemberPageState extends State /// 专栏 const ListTile(title: Text('Ta的专栏')), + const Divider(height: 1, thickness: 0.1), + + /// 合集 + const ListTile(title: Text('Ta的合集')), MediaQuery.removePadding( removeTop: true, removeBottom: true, context: context, - child: Padding( - padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - ), - child: FutureBuilder( - future: _memberSeasonsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - if (data['data'].seasonsList.isEmpty) { - return commenWidget('用户没有设置专栏'); - } else { - return MemberSeasonsPanel(data: data['data']); - } - } else { - // 请求错误 - return const SizedBox(); - } - } else { + child: FutureBuilder( + future: _memberSeasonsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == + ConnectionState.done) { + if (snapshot.data == null) { return const SizedBox(); } - }, - ), + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + if (data['data'].seasonsList.isEmpty) { + return commenWidget('用户没有设置合集'); + } else { + return MemberSeasonsPanel(data: data['data']); + } + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, ), ), diff --git a/lib/pages/member/widgets/seasons.dart b/lib/pages/member/widgets/seasons.dart index 125c978f..1367d6bd 100644 --- a/lib/pages/member/widgets/seasons.dart +++ b/lib/pages/member/widgets/seasons.dart @@ -25,7 +25,7 @@ class MemberSeasonsPanel extends StatelessWidget { children: [ ListTile( onTap: () => Get.toNamed( - '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'), + '/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}&seasonName=${item.meta!.name}'), title: Text( item.meta!.name!, maxLines: 1, @@ -44,24 +44,30 @@ class MemberSeasonsPanel extends StatelessWidget { ), ), const SizedBox(height: 10), - LayoutBuilder( - builder: (context, boxConstraints) { - return GridView.builder( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, // Use a fixed count for GridView - crossAxisSpacing: StyleString.safeSpace, - mainAxisSpacing: StyleString.safeSpace, - childAspectRatio: 0.94, - ), - physics: const NeverScrollableScrollPhysics(), - shrinkWrap: true, - itemCount: item.archives!.length, - itemBuilder: (context, i) { - return MemberSeasonsItem(seasonItem: item.archives![i]); - }, - ); - }, + Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: LayoutBuilder( + builder: (context, boxConstraints) { + return GridView.builder( + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, // Use a fixed count for GridView + crossAxisSpacing: StyleString.safeSpace, + mainAxisSpacing: StyleString.safeSpace, + childAspectRatio: 0.94, + ), + physics: const NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: item.archives!.length, + itemBuilder: (context, i) { + return MemberSeasonsItem(seasonItem: item.archives![i]); + }, + ); + }, + ), ), ], ), diff --git a/lib/pages/member_seasons/view.dart b/lib/pages/member_seasons/view.dart index 06944f10..556e2ec5 100644 --- a/lib/pages/member_seasons/view.dart +++ b/lib/pages/member_seasons/view.dart @@ -43,7 +43,8 @@ class _MemberSeasonsPageState extends State { appBar: AppBar( titleSpacing: 0, centerTitle: false, - title: Text('他的专栏', style: Theme.of(context).textTheme.titleMedium), + title: Text(Get.parameters['seasonName']!, + style: Theme.of(context).textTheme.titleMedium), ), body: Padding( padding: const EdgeInsets.only(