Merge branch 'design'

This commit is contained in:
guozhigq
2024-06-29 14:30:08 +08:00
9 changed files with 458 additions and 70 deletions

View File

@ -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<MemberLikeDataModel>((e) => MemberLikeDataModel.fromJson(e))
.toList(),
};
} else {
return {

210
lib/models/member/like.dart Normal file
View File

@ -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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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<String, dynamic> 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"],
);
}

View File

@ -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<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs;
RxList<MemberLikeDataModel> recentLikeList = <MemberLikeDataModel>[].obs;
@override
void onInit() {
@ -190,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;
}
@ -208,6 +215,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');

View File

@ -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<MemberPage>
late Future _futureBuilderFuture;
late Future _memberSeasonsFuture;
late Future _memberCoinsFuture;
late Future _memberLikeFuture;
final ScrollController _extendNestCtr = ScrollController();
final StreamController<bool> appbarStream = StreamController<bool>();
late int mid;
@ -39,6 +41,7 @@ class _MemberPageState extends State<MemberPage>
_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<MemberPage>
trailing:
const Icon(Icons.arrow_forward_outlined, size: 19),
),
const Divider(height: 1, thickness: 0.1),
/// 视频
ListTile(
@ -170,45 +174,41 @@ class _MemberPageState extends State<MemberPage>
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的专栏')),
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();
}
},
),
),
@ -218,12 +218,7 @@ class _MemberPageState extends State<MemberPage>
/// 最近投币
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 +252,44 @@ class _MemberPageState extends State<MemberPage>
),
),
),
// 最近点赞
// 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();
}
},
),
),
),
],
),
),

View File

@ -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<MemberCoinsDataModel>? data;
const MemberCoinsPanel({super.key, this.data});
final List<MemberCoinsDataModel> 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]);
},
);
},

View File

@ -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<MemberLikeDataModel> 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]);
},
);
},
);
}
}

View File

@ -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]);
},
);
},
),
),
],
),

View File

@ -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)
],
),
],
),
),
],
),
),
);
}
}

View File

@ -43,7 +43,8 @@ class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
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(