Merge branch 'design'
This commit is contained in:
@ -1,37 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Widget pBadge(
|
||||
// text,
|
||||
// context,
|
||||
// double? top,
|
||||
// double? right,
|
||||
// double? bottom,
|
||||
// double? left, {
|
||||
// type = 'primary',
|
||||
// }) {
|
||||
// Color bgColor = Theme.of(context).colorScheme.primary;
|
||||
// Color color = Theme.of(context).colorScheme.onPrimary;
|
||||
// if (type == 'gray') {
|
||||
// bgColor = Colors.black54.withOpacity(0.4);
|
||||
// color = Colors.white;
|
||||
// }
|
||||
// return Positioned(
|
||||
// top: top,
|
||||
// left: left,
|
||||
// right: right,
|
||||
// bottom: bottom,
|
||||
// child: Container(
|
||||
// padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 6),
|
||||
// decoration:
|
||||
// BoxDecoration(borderRadius: BorderRadius.circular(4), color: bgColor),
|
||||
// child: Text(
|
||||
// text,
|
||||
// style: TextStyle(fontSize: 11, color: color),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
class PBadge extends StatelessWidget {
|
||||
final String? text;
|
||||
final double? top;
|
||||
|
47
lib/common/widgets/content_container.dart
Normal file
47
lib/common/widgets/content_container.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class ContentContainer extends StatelessWidget {
|
||||
final Widget? contentWidget;
|
||||
final Widget? bottomWidget;
|
||||
final bool isScrollable;
|
||||
final Clip? childClipBehavior;
|
||||
|
||||
const ContentContainer(
|
||||
{Key? key,
|
||||
this.contentWidget,
|
||||
this.bottomWidget,
|
||||
this.isScrollable = true,
|
||||
this.childClipBehavior})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
return SingleChildScrollView(
|
||||
clipBehavior: childClipBehavior ?? Clip.hardEdge,
|
||||
physics: isScrollable ? null : NeverScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints: constraints.copyWith(
|
||||
minHeight: constraints.maxHeight,
|
||||
maxHeight: double.infinity,
|
||||
),
|
||||
child: IntrinsicHeight(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
if (contentWidget != null)
|
||||
Expanded(
|
||||
child: contentWidget!,
|
||||
)
|
||||
else
|
||||
Spacer(),
|
||||
if (bottomWidget != null) bottomWidget!,
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -17,6 +17,10 @@ class VideoCardH extends StatelessWidget {
|
||||
final Function()? longPress;
|
||||
final Function()? longPressEnd;
|
||||
final String source;
|
||||
final bool showOwner;
|
||||
final bool showView;
|
||||
final bool showDanmaku;
|
||||
final bool showPubdate;
|
||||
|
||||
const VideoCardH({
|
||||
Key? key,
|
||||
@ -24,6 +28,10 @@ class VideoCardH extends StatelessWidget {
|
||||
this.longPress,
|
||||
this.longPressEnd,
|
||||
this.source = 'normal',
|
||||
this.showOwner = true,
|
||||
this.showView = true,
|
||||
this.showDanmaku = true,
|
||||
this.showPubdate = false,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -103,7 +111,14 @@ class VideoCardH extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
VideoContent(videoItem: videoItem, source: source)
|
||||
VideoContent(
|
||||
videoItem: videoItem,
|
||||
source: source,
|
||||
showOwner: showOwner,
|
||||
showView: showView,
|
||||
showDanmaku: showDanmaku,
|
||||
showPubdate: showPubdate,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -119,8 +134,20 @@ class VideoContent extends StatelessWidget {
|
||||
// ignore: prefer_typing_uninitialized_variables
|
||||
final videoItem;
|
||||
final String source;
|
||||
const VideoContent(
|
||||
{super.key, required this.videoItem, this.source = 'normal'});
|
||||
final bool showOwner;
|
||||
final bool showView;
|
||||
final bool showDanmaku;
|
||||
final bool showPubdate;
|
||||
|
||||
const VideoContent({
|
||||
super.key,
|
||||
required this.videoItem,
|
||||
this.source = 'normal',
|
||||
this.showOwner = true,
|
||||
this.showView = true,
|
||||
this.showDanmaku = true,
|
||||
this.showPubdate = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -179,34 +206,40 @@ class VideoContent extends StatelessWidget {
|
||||
// ),
|
||||
// ),
|
||||
// const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
videoItem.owner.name,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
if (showPubdate)
|
||||
Text(
|
||||
Utils.dateFormat(videoItem.pubdate!),
|
||||
style: TextStyle(
|
||||
fontSize: 11, color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
if (showOwner)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
videoItem.owner.name,
|
||||
style: TextStyle(
|
||||
fontSize:
|
||||
Theme.of(context).textTheme.labelMedium!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmaku,
|
||||
),
|
||||
// Text(
|
||||
// Utils.dateFormat(videoItem.pubdate!),
|
||||
// style: TextStyle(
|
||||
// fontSize: 11,
|
||||
// color: Theme.of(context).colorScheme.outline),
|
||||
// )
|
||||
if (showView) ...[
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: videoItem.stat.view,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
],
|
||||
if (showDanmaku)
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: videoItem.stat.danmaku,
|
||||
),
|
||||
|
||||
const Spacer(),
|
||||
// SizedBox(
|
||||
// width: 20,
|
||||
|
@ -215,7 +215,7 @@ class Api {
|
||||
// 粉丝
|
||||
// vmid 用户id pn 页码 ps 每页个数,最大50 order: desc
|
||||
// order_type 排序规则 最近访问传空,最常访问传 attention
|
||||
static const String fans = 'https://api.bilibili.com/x/relation/fans';
|
||||
static const String fans = '/x/relation/fans';
|
||||
|
||||
// 直播
|
||||
// ?page=1&page_size=30&platform=web
|
||||
@ -372,4 +372,36 @@ class Api {
|
||||
/// local_id
|
||||
static const getWebKey =
|
||||
'https://passport.bilibili.com/x/passport-login/web/key';
|
||||
|
||||
/// 置顶视频
|
||||
static const getTopVideoApi = '/x/space/top/arc';
|
||||
|
||||
/// 主页 - 最近投币的视频
|
||||
/// vmid
|
||||
/// gaia_source = main_web
|
||||
/// web_location
|
||||
/// w_rid
|
||||
/// wts
|
||||
static const getRecentCoinVideoApi = '/x/space/coin/video';
|
||||
|
||||
/// 最近点赞的视频
|
||||
static const getRecentLikeVideoApi = '/x/space/like/video';
|
||||
|
||||
/// 最近追番
|
||||
static const getRecentBangumiApi = '/x/space/bangumi/follow/list';
|
||||
|
||||
/// 用户专栏
|
||||
static const getMemberSeasonsApi = '/x/polymer/web-space/home/seasons_series';
|
||||
|
||||
/// 获赞数 播放数
|
||||
static const getMemberViewApi = '/x/space/upstat';
|
||||
|
||||
/// 查询某个专栏
|
||||
/// mid
|
||||
/// season_id
|
||||
/// sort_reverse
|
||||
/// page_num
|
||||
/// page_size
|
||||
static const getSeasonDetailApi =
|
||||
'/x/polymer/web-space/seasons_archives_list';
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
import 'dart:ffi';
|
||||
|
||||
import 'package:pilipala/http/index.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/models/follow/result.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/seasons.dart';
|
||||
import 'package:pilipala/models/member/tags.dart';
|
||||
import 'package:pilipala/utils/wbi_sign.dart';
|
||||
|
||||
@ -215,4 +219,144 @@ class MemberHttp {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取up置顶
|
||||
static Future getTopVideo(String? vmid) async {
|
||||
var res = await Request().get(Api.getTopVideoApi);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']
|
||||
.map<MemberTagItemModel>((e) => MemberTagItemModel.fromJson(e))
|
||||
.toList()
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 获取uo专栏
|
||||
static Future getMemberSeasons(int? mid, int? pn, int? ps) async {
|
||||
var res = await Request().get(Api.getMemberSeasonsApi, data: {
|
||||
'mid': mid,
|
||||
'page_num': pn,
|
||||
'page_size': ps,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 最近投币
|
||||
static Future getRecentCoinVideo({required int mid}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
'mid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.getRecentCoinVideoApi,
|
||||
data: {
|
||||
'vmid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': res.data['data']
|
||||
.map<MemberCoinsDataModel>((e) => MemberCoinsDataModel.fromJson(e))
|
||||
.toList(),
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 最近点赞
|
||||
static Future getRecentLikeVideo({required int mid}) async {
|
||||
Map params = await WbiSign().makSign({
|
||||
'mid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
});
|
||||
var res = await Request().get(
|
||||
Api.getRecentLikeVideoApi,
|
||||
data: {
|
||||
'vmid': mid,
|
||||
'gaia_source': 'main_web',
|
||||
'web_location': 333.999,
|
||||
'w_rid': params['w_rid'],
|
||||
'wts': params['wts'],
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberSeasonsDataModel.fromJson(res.data['data']['items_lists'])
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 查看某个专栏
|
||||
static Future getSeasonDetail({
|
||||
required int mid,
|
||||
required int seasonId,
|
||||
bool sortReverse = false,
|
||||
required int pn,
|
||||
required int ps,
|
||||
}) async {
|
||||
var res = await Request().get(
|
||||
Api.getSeasonDetailApi,
|
||||
data: {
|
||||
'mid': mid,
|
||||
'season_id': seasonId,
|
||||
'sort_reverse': sortReverse,
|
||||
'page_num': pn,
|
||||
'page_size': ps,
|
||||
},
|
||||
);
|
||||
if (res.data['code'] == 0) {
|
||||
try {
|
||||
return {
|
||||
'status': true,
|
||||
'data': MemberSeasonsList.fromJson(res.data['data'])
|
||||
};
|
||||
} catch (err) {
|
||||
print(err);
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
'status': false,
|
||||
'data': [],
|
||||
'msg': res.data['message'],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
89
lib/models/member/coin.dart
Normal file
89
lib/models/member/coin.dart
Normal file
@ -0,0 +1,89 @@
|
||||
class MemberCoinsDataModel {
|
||||
MemberCoinsDataModel({
|
||||
this.aid,
|
||||
this.bvid,
|
||||
this.cid,
|
||||
this.coins,
|
||||
this.copyright,
|
||||
this.ctime,
|
||||
this.desc,
|
||||
this.duration,
|
||||
this.owner,
|
||||
this.pic,
|
||||
this.pubLocation,
|
||||
this.pubdate,
|
||||
this.resourceType,
|
||||
this.state,
|
||||
this.subtitle,
|
||||
this.time,
|
||||
this.title,
|
||||
this.tname,
|
||||
this.videos,
|
||||
this.view,
|
||||
this.danmaku,
|
||||
});
|
||||
|
||||
int? aid;
|
||||
String? bvid;
|
||||
int? cid;
|
||||
int? coins;
|
||||
int? copyright;
|
||||
int? ctime;
|
||||
String? desc;
|
||||
int? duration;
|
||||
Owner? owner;
|
||||
String? pic;
|
||||
String? pubLocation;
|
||||
int? pubdate;
|
||||
String? resourceType;
|
||||
int? state;
|
||||
String? subtitle;
|
||||
int? time;
|
||||
String? title;
|
||||
String? tname;
|
||||
int? videos;
|
||||
int? view;
|
||||
int? danmaku;
|
||||
|
||||
MemberCoinsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
aid = json['aid'];
|
||||
bvid = json['bvid'];
|
||||
cid = json['cid'];
|
||||
coins = json['coins'];
|
||||
copyright = json['copyright'];
|
||||
ctime = json['ctime'];
|
||||
desc = json['desc'];
|
||||
duration = json['duration'];
|
||||
owner = Owner.fromJson(json['owner']);
|
||||
pic = json['pic'];
|
||||
pubLocation = json['pub_location'];
|
||||
pubdate = json['pubdate'];
|
||||
resourceType = json['resource_type'];
|
||||
state = json['state'];
|
||||
subtitle = json['subtitle'];
|
||||
time = json['time'];
|
||||
title = json['title'];
|
||||
tname = json['tname'];
|
||||
videos = json['videos'];
|
||||
view = json['stat']['view'];
|
||||
danmaku = json['stat']['danmaku'];
|
||||
}
|
||||
}
|
||||
|
||||
class Owner {
|
||||
Owner({
|
||||
this.mid,
|
||||
this.name,
|
||||
this.face,
|
||||
});
|
||||
|
||||
int? mid;
|
||||
String? name;
|
||||
String? face;
|
||||
|
||||
Owner.fromJson(Map<String, dynamic> json) {
|
||||
mid = json['mid'];
|
||||
name = json['name'];
|
||||
face = json['face'];
|
||||
}
|
||||
}
|
108
lib/models/member/seasons.dart
Normal file
108
lib/models/member/seasons.dart
Normal file
@ -0,0 +1,108 @@
|
||||
class MemberSeasonsDataModel {
|
||||
MemberSeasonsDataModel({
|
||||
this.page,
|
||||
this.seasonsList,
|
||||
});
|
||||
|
||||
Map? page;
|
||||
List<MemberSeasonsList>? seasonsList;
|
||||
|
||||
MemberSeasonsDataModel.fromJson(Map<String, dynamic> json) {
|
||||
page = json['page'];
|
||||
seasonsList = json['seasons_list'] != null
|
||||
? json['seasons_list']
|
||||
.map<MemberSeasonsList>((e) => MemberSeasonsList.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
}
|
||||
}
|
||||
|
||||
class MemberSeasonsList {
|
||||
MemberSeasonsList({
|
||||
this.archives,
|
||||
this.meta,
|
||||
this.recentAids,
|
||||
this.page,
|
||||
});
|
||||
|
||||
List<MemberArchiveItem>? archives;
|
||||
MamberMeta? meta;
|
||||
List? recentAids;
|
||||
Map? page;
|
||||
|
||||
MemberSeasonsList.fromJson(Map<String, dynamic> json) {
|
||||
archives = json['archives'] != null
|
||||
? json['archives']
|
||||
.map<MemberArchiveItem>((e) => MemberArchiveItem.fromJson(e))
|
||||
.toList()
|
||||
: [];
|
||||
meta = MamberMeta.fromJson(json['meta']);
|
||||
page = json['page'];
|
||||
}
|
||||
}
|
||||
|
||||
class MemberArchiveItem {
|
||||
MemberArchiveItem({
|
||||
this.aid,
|
||||
this.bvid,
|
||||
this.ctime,
|
||||
this.duration,
|
||||
this.pic,
|
||||
this.cover,
|
||||
this.pubdate,
|
||||
this.view,
|
||||
this.title,
|
||||
});
|
||||
|
||||
int? aid;
|
||||
String? bvid;
|
||||
int? ctime;
|
||||
int? duration;
|
||||
String? pic;
|
||||
String? cover;
|
||||
int? pubdate;
|
||||
int? view;
|
||||
String? title;
|
||||
|
||||
MemberArchiveItem.fromJson(Map<String, dynamic> json) {
|
||||
aid = json['aid'];
|
||||
bvid = json['bvid'];
|
||||
ctime = json['ctime'];
|
||||
duration = json['duration'];
|
||||
pic = json['pic'];
|
||||
cover = json['pic'];
|
||||
pubdate = json['pubdate'];
|
||||
view = json['stat']['view'];
|
||||
title = json['title'];
|
||||
}
|
||||
}
|
||||
|
||||
class MamberMeta {
|
||||
MamberMeta({
|
||||
this.cover,
|
||||
this.description,
|
||||
this.mid,
|
||||
this.name,
|
||||
this.ptime,
|
||||
this.seasonId,
|
||||
this.total,
|
||||
});
|
||||
|
||||
String? cover;
|
||||
String? description;
|
||||
int? mid;
|
||||
String? name;
|
||||
int? ptime;
|
||||
int? seasonId;
|
||||
int? total;
|
||||
|
||||
MamberMeta.fromJson(Map<String, dynamic> json) {
|
||||
cover = json['cover'];
|
||||
description = json['description'];
|
||||
mid = json['mid'];
|
||||
name = json['name'];
|
||||
ptime = json['ptime'];
|
||||
seasonId = json['season_id'];
|
||||
total = json['total'];
|
||||
}
|
||||
}
|
@ -95,8 +95,12 @@ class _PlDanmakuState extends State<PlDanmaku> {
|
||||
// 根据position判断是否有已缓存弹幕。没有则请求对应段
|
||||
int segIndex = (currentPosition / (6 * 60 * 1000)).ceil();
|
||||
segIndex = segIndex < 1 ? 1 : segIndex;
|
||||
if (ctr.dmSegList[segIndex - 1].elems.isEmpty &&
|
||||
!ctr.hasrequestSeg.contains(segIndex - 1)) {
|
||||
print('🌹🌹: ${segIndex}');
|
||||
print('🌹🌹: ${ctr.dmSegList.length}');
|
||||
print('🌹🌹: ${ctr.hasrequestSeg.contains(segIndex - 1)}');
|
||||
if (segIndex - 1 >= ctr.dmSegList.length ||
|
||||
(ctr.dmSegList[segIndex - 1].elems.isEmpty &&
|
||||
!ctr.hasrequestSeg.contains(segIndex - 1))) {
|
||||
ctr.hasrequestSeg.add(segIndex - 1);
|
||||
ctr.currentSegIndex = segIndex;
|
||||
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||
|
@ -1,4 +0,0 @@
|
||||
library archive_panel;
|
||||
|
||||
export './controller.dart';
|
||||
export 'index.dart';
|
@ -1,240 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loading_more_list/loading_more_list.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
import 'package:pilipala/pages/member/archive/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart';
|
||||
|
||||
class ArchivePanel extends StatefulWidget {
|
||||
final int? mid;
|
||||
const ArchivePanel({super.key, this.mid});
|
||||
|
||||
@override
|
||||
State<ArchivePanel> createState() => _ArchivePanelState();
|
||||
}
|
||||
|
||||
class _ArchivePanelState extends State<ArchivePanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
DateTime lastRefreshTime = DateTime.now();
|
||||
late final LoadMoreListSource source;
|
||||
late final ArchiveController _archiveController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
print('🐶🐶: ${widget.mid}');
|
||||
_archiveController = Get.put(ArchiveController(widget.mid),
|
||||
tag: Utils.makeHeroTag(widget.mid));
|
||||
source = LoadMoreListSource(_archiveController);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return PullToRefreshNotification(
|
||||
onRefresh: () async {
|
||||
await Future.delayed(const Duration(seconds: 1));
|
||||
return true;
|
||||
},
|
||||
maxDragOffset: 50,
|
||||
child: GlowNotificationWidget(
|
||||
Column(
|
||||
children: <Widget>[
|
||||
// 下拉刷新指示器
|
||||
// PullToRefreshContainer(
|
||||
// (PullToRefreshScrollNotificationInfo? info) {
|
||||
// return PullToRefreshHeader(info, lastRefreshTime);
|
||||
// },
|
||||
// ),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(left: 14, top: 8, bottom: 8, right: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('排序方式'),
|
||||
SizedBox(
|
||||
height: 35,
|
||||
width: 85,
|
||||
child: TextButton(
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
onPressed: () {
|
||||
// _archiveController.order = 'click';
|
||||
// _archiveController.pn = 1;
|
||||
_archiveController.toggleSort();
|
||||
source.refresh(true);
|
||||
// LoadMoreListSource().loadData();
|
||||
},
|
||||
child: Obx(
|
||||
() => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
transitionBuilder:
|
||||
(Widget child, Animation<double> animation) {
|
||||
return ScaleTransition(
|
||||
scale: animation, child: child);
|
||||
},
|
||||
child: Text(
|
||||
_archiveController.currentOrder['label']!,
|
||||
key: ValueKey<String>(
|
||||
_archiveController.currentOrder['label']!),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: LoadingMoreList<VListItemModel>(
|
||||
ListConfig<VListItemModel>(
|
||||
sourceList: source,
|
||||
itemBuilder:
|
||||
(BuildContext c, VListItemModel item, int index) {
|
||||
if (index == 0) {
|
||||
return Column(
|
||||
children: [
|
||||
const SizedBox(height: 6),
|
||||
VideoCardH(videoItem: item)
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return VideoCardH(videoItem: item);
|
||||
}
|
||||
},
|
||||
indicatorBuilder: _buildIndicator,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
showGlowLeading: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
|
||||
TextStyle style =
|
||||
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||
Widget? widget;
|
||||
switch (status) {
|
||||
case IndicatorStatus.none:
|
||||
widget = Container(height: 0.0);
|
||||
break;
|
||||
case IndicatorStatus.loadingMoreBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.fullScreenBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
case IndicatorStatus.error:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget);
|
||||
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
|
||||
break;
|
||||
case IndicatorStatus.fullScreenError:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
break;
|
||||
case IndicatorStatus.noMoreLoad:
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.empty:
|
||||
widget = Text('用户没有投稿', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget _setbackground(bool full, Widget widget, {double height = 100}) {
|
||||
widget = Padding(
|
||||
padding: height == double.infinity
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: height,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
alignment: Alignment.center,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget getIndicator(BuildContext context) {
|
||||
final TargetPlatform platform = Theme.of(context).platform;
|
||||
return platform == TargetPlatform.iOS
|
||||
? const CupertinoActivityIndicator(
|
||||
animating: true,
|
||||
radius: 16.0,
|
||||
)
|
||||
: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreListSource extends LoadingMoreBase<VListItemModel> {
|
||||
late ArchiveController ctr;
|
||||
LoadMoreListSource(this.ctr);
|
||||
bool forceRefresh = false;
|
||||
|
||||
@override
|
||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||
bool isSuccess = false;
|
||||
var res = await ctr.getMemberArchive();
|
||||
if (res['status']) {
|
||||
if (ctr.pn == 2) {
|
||||
clear();
|
||||
}
|
||||
addAll(res['data'].list.vlist);
|
||||
}
|
||||
if (length < res['data'].page['count']) {
|
||||
isSuccess = true;
|
||||
} else {
|
||||
isSuccess = false;
|
||||
}
|
||||
return isSuccess;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> refresh([bool clearBeforeRequest = false]) async {
|
||||
// _hasMore = true;
|
||||
// pageindex = 1;
|
||||
// //force to refresh list when you don't want clear list before request
|
||||
// //for the case, if your list already has 20 items.
|
||||
forceRefresh = !clearBeforeRequest;
|
||||
var result = await super.refresh(clearBeforeRequest);
|
||||
|
||||
forceRefresh = false;
|
||||
return result;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import 'package:pilipala/http/member.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
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/utils/storage.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@ -20,9 +21,10 @@ class MemberController extends GetxController {
|
||||
late int ownerMid;
|
||||
// 投稿列表
|
||||
RxList<VListItemModel>? archiveList = [VListItemModel()].obs;
|
||||
var userInfo;
|
||||
dynamic userInfo;
|
||||
RxInt attribute = (-1).obs;
|
||||
RxString attributeText = '关注'.obs;
|
||||
RxList<MemberCoinsDataModel> recentCoinsList = <MemberCoinsDataModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@ -55,14 +57,6 @@ class MemberController extends GetxController {
|
||||
return res;
|
||||
}
|
||||
|
||||
// Future getMemberCardInfo() async {
|
||||
// var res = await MemberHttp.memberCardInfo(mid: mid);
|
||||
// if (res['status']) {
|
||||
// print(userStat);
|
||||
// }
|
||||
// return res;
|
||||
// }
|
||||
|
||||
// 关注/取关up
|
||||
Future actionRelationMod() async {
|
||||
if (userInfo == null) {
|
||||
@ -173,4 +167,35 @@ class MemberController extends GetxController {
|
||||
void shareUser() {
|
||||
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']}");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 请求投币视频
|
||||
Future getRecentCoinVideo() async {
|
||||
if (userInfo == null) return;
|
||||
var res = await MemberHttp.getRecentCoinVideo(mid: mid);
|
||||
recentCoinsList.value = res['data'];
|
||||
return res;
|
||||
}
|
||||
|
||||
// 跳转查看动态
|
||||
void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid');
|
||||
|
||||
// 跳转查看投稿
|
||||
void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid');
|
||||
|
||||
// 跳转查看专栏
|
||||
void pushSeasonsPage() {}
|
||||
// 跳转查看最近投币
|
||||
void pushRecentCoinsPage() async {
|
||||
if (recentCoinsList.isNotEmpty) {}
|
||||
}
|
||||
}
|
||||
|
@ -1,31 +0,0 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
|
||||
class MemberDynamicPanelController extends GetxController {
|
||||
MemberDynamicPanelController(this.mid);
|
||||
int? mid;
|
||||
String offset = '';
|
||||
int count = 0;
|
||||
bool hasMore = true;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid ??= int.parse(Get.parameters['mid']!);
|
||||
}
|
||||
|
||||
Future getMemberDynamic() async {
|
||||
if (!hasMore) {
|
||||
return {'status': false};
|
||||
}
|
||||
var res = await MemberHttp.memberDynamic(
|
||||
offset: offset,
|
||||
mid: mid,
|
||||
);
|
||||
if (res['status']) {
|
||||
offset = res['data'].offset;
|
||||
hasMore = res['data'].hasMore;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
@ -1,152 +0,0 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:loading_more_list/loading_more_list.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
import 'package:pilipala/pages/dynamics/widgets/dynamic_panel.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'controller.dart';
|
||||
|
||||
class MemberDynamicPanel extends StatefulWidget {
|
||||
final int? mid;
|
||||
const MemberDynamicPanel({super.key, this.mid});
|
||||
|
||||
@override
|
||||
State<MemberDynamicPanel> createState() => _MemberDynamicPanelState();
|
||||
}
|
||||
|
||||
class _MemberDynamicPanelState extends State<MemberDynamicPanel>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
DateTime lastRefreshTime = DateTime.now();
|
||||
late final LoadMoreListSource source;
|
||||
late final MemberDynamicPanelController _dynamicController;
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_dynamicController = Get.put(MemberDynamicPanelController(widget.mid),
|
||||
tag: Utils.makeHeroTag(widget.mid));
|
||||
source = LoadMoreListSource(_dynamicController);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return LoadingMoreList<DynamicItemModel>(
|
||||
ListConfig<DynamicItemModel>(
|
||||
sourceList: source,
|
||||
itemBuilder: (BuildContext c, DynamicItemModel item, int index) {
|
||||
return DynamicPanel(item: item);
|
||||
},
|
||||
indicatorBuilder: _buildIndicator,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
|
||||
TextStyle style =
|
||||
TextStyle(fontSize: 13, color: Theme.of(context).colorScheme.outline);
|
||||
Widget? widget;
|
||||
switch (status) {
|
||||
case IndicatorStatus.none:
|
||||
widget = Container(height: 0.0);
|
||||
break;
|
||||
case IndicatorStatus.loadingMoreBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.fullScreenBusying:
|
||||
widget = Text('加载中...', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
case IndicatorStatus.error:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget);
|
||||
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
|
||||
break;
|
||||
case IndicatorStatus.fullScreenError:
|
||||
|
||||
/// TODO 异常逻辑
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
widget = GestureDetector(
|
||||
onTap: () {},
|
||||
child: widget,
|
||||
);
|
||||
break;
|
||||
case IndicatorStatus.noMoreLoad:
|
||||
widget = Text('没有更多了', style: style);
|
||||
widget = _setbackground(false, widget, height: 60.0);
|
||||
break;
|
||||
case IndicatorStatus.empty:
|
||||
widget = Text('用户没有投稿', style: style);
|
||||
widget = _setbackground(true, widget);
|
||||
break;
|
||||
}
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget _setbackground(bool full, Widget widget, {double height = 100}) {
|
||||
widget = Padding(
|
||||
padding: height == double.infinity
|
||||
? EdgeInsets.zero
|
||||
: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
height: height,
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
alignment: Alignment.center,
|
||||
child: widget,
|
||||
),
|
||||
);
|
||||
return widget;
|
||||
}
|
||||
|
||||
Widget getIndicator(BuildContext context) {
|
||||
final TargetPlatform platform = Theme.of(context).platform;
|
||||
return platform == TargetPlatform.iOS
|
||||
? const CupertinoActivityIndicator(
|
||||
animating: true,
|
||||
radius: 16.0,
|
||||
)
|
||||
: CircularProgressIndicator(
|
||||
strokeWidth: 2.0,
|
||||
valueColor:
|
||||
AlwaysStoppedAnimation<Color>(Theme.of(context).primaryColor),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreListSource extends LoadingMoreBase<DynamicItemModel> {
|
||||
late MemberDynamicPanelController ctr;
|
||||
LoadMoreListSource(this.ctr);
|
||||
|
||||
@override
|
||||
Future<bool> loadData([bool isloadMoreAction = false]) async {
|
||||
bool isSuccess = false;
|
||||
var res = await ctr.getMemberDynamic();
|
||||
if (res['status']) {
|
||||
addAll(res['data'].items);
|
||||
}
|
||||
try {
|
||||
if (res['data'].hasMore) {
|
||||
isSuccess = true;
|
||||
} else {
|
||||
isSuccess = false;
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
return isSuccess;
|
||||
}
|
||||
}
|
@ -1,16 +1,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/pages/member/archive/view.dart';
|
||||
import 'package:pilipala/pages/member/dynamic/index.dart';
|
||||
import 'package:pilipala/pages/member/index.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
import 'widgets/conis.dart';
|
||||
import 'widgets/profile.dart';
|
||||
import 'widgets/seasons.dart';
|
||||
|
||||
class MemberPage extends StatefulWidget {
|
||||
const MemberPage({super.key});
|
||||
@ -23,9 +23,10 @@ class _MemberPageState extends State<MemberPage>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late String heroTag;
|
||||
late MemberController _memberController;
|
||||
Future? _futureBuilderFuture;
|
||||
late Future _futureBuilderFuture;
|
||||
late Future _memberSeasonsFuture;
|
||||
late Future _memberCoinsFuture;
|
||||
final ScrollController _extendNestCtr = ScrollController();
|
||||
late TabController _tabController;
|
||||
final StreamController<bool> appbarStream = StreamController<bool>();
|
||||
late int mid;
|
||||
|
||||
@ -35,12 +36,13 @@ class _MemberPageState extends State<MemberPage>
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
|
||||
_memberController = Get.put(MemberController(), tag: heroTag);
|
||||
_tabController = TabController(length: 3, vsync: this, initialIndex: 2);
|
||||
_futureBuilderFuture = _memberController.getInfo();
|
||||
_memberSeasonsFuture = _memberController.getMemberSeasons();
|
||||
_memberCoinsFuture = _memberController.getRecentCoinVideo();
|
||||
_extendNestCtr.addListener(
|
||||
() {
|
||||
double offset = _extendNestCtr.position.pixels;
|
||||
if (offset > 230) {
|
||||
if (offset > 100) {
|
||||
appbarStream.add(true);
|
||||
} else {
|
||||
appbarStream.add(false);
|
||||
@ -59,183 +61,222 @@ class _MemberPageState extends State<MemberPage>
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
primary: true,
|
||||
body: ExtendedNestedScrollView(
|
||||
controller: _extendNestCtr,
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
return <Widget>[
|
||||
SliverAppBar(
|
||||
pinned: false,
|
||||
primary: true,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 1,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
expandedHeight: 290,
|
||||
titleSpacing: 0,
|
||||
title: StreamBuilder(
|
||||
stream: appbarStream.stream,
|
||||
initialData: false,
|
||||
builder: (context, AsyncSnapshot snapshot) {
|
||||
return AnimatedOpacity(
|
||||
opacity: snapshot.data ? 1 : 0,
|
||||
curve: Curves.easeOut,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Obx(
|
||||
() => NetworkImgLayer(
|
||||
width: 35,
|
||||
height: 35,
|
||||
type: 'avatar',
|
||||
src: _memberController.face.value,
|
||||
),
|
||||
body: Column(
|
||||
children: [
|
||||
AppBar(
|
||||
title: StreamBuilder(
|
||||
stream: appbarStream.stream,
|
||||
initialData: false,
|
||||
builder: (context, AsyncSnapshot snapshot) {
|
||||
return AnimatedOpacity(
|
||||
opacity: snapshot.data ? 1 : 0,
|
||||
curve: Curves.easeOut,
|
||||
duration: const Duration(milliseconds: 500),
|
||||
child: Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Obx(
|
||||
() => NetworkImgLayer(
|
||||
width: 35,
|
||||
height: 35,
|
||||
type: 'avatar',
|
||||
src: _memberController.face.value,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Obx(
|
||||
() => Text(
|
||||
_memberController.memberInfo.value.name ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
fontSize: 14),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Obx(
|
||||
() => Text(
|
||||
_memberController.memberInfo.value.name ?? '',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground,
|
||||
fontSize: 14),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
if (_memberController.ownerMid !=
|
||||
_memberController.mid) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () => _memberController.blockUser(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.block, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_memberController.attribute.value != 128
|
||||
? '加入黑名单'
|
||||
: '移除黑名单'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSearch?mid=${Get.parameters['mid']}&uname=${_memberController.memberInfo.value.name!}'),
|
||||
icon: const Icon(Icons.search_outlined),
|
||||
),
|
||||
PopupMenuButton(
|
||||
icon: const Icon(Icons.more_vert),
|
||||
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||
if (_memberController.ownerMid != _memberController.mid) ...[
|
||||
PopupMenuItem(
|
||||
onTap: () => _memberController.shareUser(),
|
||||
onTap: () => _memberController.blockUser(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.share_outlined, size: 19),
|
||||
const Icon(Icons.block, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_memberController.ownerMid !=
|
||||
_memberController.mid
|
||||
? '分享UP主'
|
||||
: '分享我的主页'),
|
||||
Text(_memberController.attribute.value != 128
|
||||
? '加入黑名单'
|
||||
: '移除黑名单'),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
PopupMenuItem(
|
||||
onTap: () => _memberController.shareUser(),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.share_outlined, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(_memberController.ownerMid != _memberController.mid
|
||||
? '分享UP主'
|
||||
: '分享我的主页'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: _extendNestCtr,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(
|
||||
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
flexibleSpace: FlexibleSpaceBar(
|
||||
background: Stack(
|
||||
child: Column(
|
||||
children: [
|
||||
profileWidget(),
|
||||
|
||||
/// 动态链接
|
||||
ListTile(
|
||||
onTap: _memberController.pushDynamicsPage,
|
||||
title: const Text('Ta的动态'),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
),
|
||||
|
||||
/// 视频
|
||||
ListTile(
|
||||
onTap: _memberController.pushArchivesPage,
|
||||
title: const Text('Ta的投稿'),
|
||||
trailing:
|
||||
const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
),
|
||||
|
||||
/// 专栏
|
||||
ListTile(
|
||||
onTap: () {},
|
||||
title: const 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 {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
/// 收藏
|
||||
|
||||
/// 追番
|
||||
/// 最近投币
|
||||
Obx(
|
||||
() => _memberController.face.value != ''
|
||||
? Positioned.fill(
|
||||
bottom: 10,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
image: DecorationImage(
|
||||
fit: BoxFit.fitWidth,
|
||||
image: NetworkImage(
|
||||
_memberController.face.value),
|
||||
alignment: Alignment.topCenter,
|
||||
isAntiAlias: true,
|
||||
),
|
||||
),
|
||||
foregroundDecoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
colors: [
|
||||
Theme.of(context)
|
||||
.colorScheme
|
||||
.background
|
||||
.withOpacity(0.44),
|
||||
Theme.of(context).colorScheme.background,
|
||||
],
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
stops: const [0.0, 0.46],
|
||||
),
|
||||
),
|
||||
),
|
||||
() => _memberController.recentCoinsList.isNotEmpty
|
||||
? ListTile(
|
||||
onTap: () {},
|
||||
title: const Text('最近投币的视频'),
|
||||
// trailing: const Icon(Icons.arrow_forward_outlined,
|
||||
// size: 19),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
Positioned(
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
height: 20,
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
MediaQuery.removePadding(
|
||||
removeTop: true,
|
||||
removeBottom: true,
|
||||
context: context,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
),
|
||||
child: FutureBuilder(
|
||||
future: _memberCoinsFuture,
|
||||
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 MemberCoinsPanel(data: data['data']);
|
||||
} else {
|
||||
// 请求错误
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
profileWidget(),
|
||||
// 最近点赞
|
||||
// ListTile(
|
||||
// onTap: () {},
|
||||
// title: const Text('最近点赞的视频'),
|
||||
// trailing:
|
||||
// const Icon(Icons.arrow_forward_outlined, size: 19),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
pinnedHeaderSliverHeightBuilder: () {
|
||||
return MediaQuery.of(context).padding.top + kToolbarHeight;
|
||||
},
|
||||
onlyOneScrollInBody: true,
|
||||
body: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
height: 50,
|
||||
child: TabBar(controller: _tabController, tabs: const [
|
||||
Tab(text: '主页'),
|
||||
Tab(text: '动态'),
|
||||
Tab(text: '投稿'),
|
||||
]),
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _tabController,
|
||||
children: [
|
||||
const Text('主页'),
|
||||
MemberDynamicPanel(mid: mid),
|
||||
ArchivePanel(mid: mid),
|
||||
],
|
||||
))
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget profileWidget() {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 18, right: 18),
|
||||
padding: const EdgeInsets.only(left: 18, right: 18, bottom: 20),
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
@ -250,7 +291,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
profile(_memberController),
|
||||
const SizedBox(height: 14),
|
||||
const SizedBox(height: 20),
|
||||
Row(
|
||||
children: [
|
||||
Flexible(
|
||||
@ -260,7 +301,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge!
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.bold),
|
||||
)),
|
||||
const SizedBox(width: 2),
|
||||
@ -332,29 +373,11 @@ class _MemberPageState extends State<MemberPage>
|
||||
softWrap: true,
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 4),
|
||||
const SizedBox(height: 6),
|
||||
if (_memberController.memberInfo.value.sign != '')
|
||||
SelectableText(
|
||||
_memberController.memberInfo.value.sign!,
|
||||
maxLines: _memberController
|
||||
.memberInfo.value.official!['title'] !=
|
||||
''
|
||||
? 1
|
||||
: 2,
|
||||
style: const TextStyle(
|
||||
overflow: TextOverflow.ellipsis),
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return AlertDialog(
|
||||
content: SelectableText(_memberController
|
||||
.memberInfo.value.sign!),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
@ -371,4 +394,22 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget commenWidget(msg) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 20,
|
||||
bottom: 30,
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
msg,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.labelMedium!
|
||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
31
lib/pages/member/widgets/conis.dart
Normal file
31
lib/pages/member/widgets/conis.dart
Normal file
@ -0,0 +1,31 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
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});
|
||||
|
||||
@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 MemberCoinsItem(coinItem: data![i]);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ Widget profile(ctr, {loadingStatus = false}) {
|
||||
return Builder(
|
||||
builder: ((context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(top: 3 * MediaQuery.of(context).padding.top),
|
||||
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20),
|
||||
child: Row(
|
||||
children: [
|
||||
Hero(
|
||||
@ -78,7 +78,8 @@ Widget profile(ctr, {loadingStatus = false}) {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10, right: 10),
|
||||
padding:
|
||||
const EdgeInsets.only(top: 10, left: 10, right: 10),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
|
85
lib/pages/member/widgets/seasons.dart
Normal file
85
lib/pages/member/widgets/seasons.dart
Normal file
@ -0,0 +1,85 @@
|
||||
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/models/member/seasons.dart';
|
||||
import 'package:pilipala/pages/member_seasons/widgets/item.dart';
|
||||
|
||||
class MemberSeasonsPanel extends StatelessWidget {
|
||||
final MemberSeasonsDataModel? data;
|
||||
const MemberSeasonsPanel({super.key, this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListView.builder(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
itemCount: data!.seasonsList!.length,
|
||||
itemBuilder: (context, index) {
|
||||
MemberSeasonsList item = data!.seasonsList![index];
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12, right: 4),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12, left: 4),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
item.meta!.name!,
|
||||
maxLines: 1,
|
||||
style: Theme.of(context).textTheme.titleSmall!,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
PBadge(
|
||||
stack: 'relative',
|
||||
size: 'small',
|
||||
text: item.meta!.total.toString(),
|
||||
),
|
||||
const Spacer(),
|
||||
SizedBox(
|
||||
width: 35,
|
||||
height: 35,
|
||||
child: IconButton(
|
||||
onPressed: () => Get.toNamed(
|
||||
'/memberSeasons?mid=${item.meta!.mid}&seasonId=${item.meta!.seasonId}'),
|
||||
style: ButtonStyle(
|
||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
||||
),
|
||||
icon: const Icon(
|
||||
Icons.arrow_forward,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
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]);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,9 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
import 'package:pilipala/models/member/archive.dart';
|
||||
|
||||
class ArchiveController extends GetxController {
|
||||
ArchiveController(this.mid);
|
||||
int? mid;
|
||||
class MemberArchiveController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
late int mid;
|
||||
int pn = 1;
|
||||
int count = 0;
|
||||
RxMap<String, String> currentOrder = <String, String>{}.obs;
|
||||
@ -12,20 +14,27 @@ class ArchiveController extends GetxController {
|
||||
{'type': 'click', 'label': '最多播放'},
|
||||
{'type': 'stow', 'label': '最多收藏'},
|
||||
];
|
||||
RxList<VListItemModel> archivesList = <VListItemModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid ??= int.parse(Get.parameters['mid']!);
|
||||
print('🐶🐶: $mid');
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
currentOrder.value = orderList.first;
|
||||
}
|
||||
|
||||
// 获取用户投稿
|
||||
Future getMemberArchive() async {
|
||||
Future getMemberArchive(type) async {
|
||||
if (type == 'onRefresh') {
|
||||
pn = 1;
|
||||
}
|
||||
var res = await MemberHttp.memberArchive(
|
||||
mid: mid, pn: pn, order: currentOrder['type']!);
|
||||
mid: mid,
|
||||
pn: pn,
|
||||
order: currentOrder['type']!,
|
||||
);
|
||||
if (res['status']) {
|
||||
archivesList.addAll(res['data'].list.vlist);
|
||||
count = res['data'].page['count'];
|
||||
pn += 1;
|
||||
}
|
||||
@ -34,11 +43,16 @@ class ArchiveController extends GetxController {
|
||||
|
||||
toggleSort() async {
|
||||
pn = 1;
|
||||
int index = orderList.indexOf(currentOrder.value);
|
||||
int index = orderList.indexOf(currentOrder);
|
||||
if (index == orderList.length - 1) {
|
||||
currentOrder.value = orderList.first;
|
||||
} else {
|
||||
currentOrder.value = orderList[index + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
getMemberArchive('onLoad');
|
||||
}
|
||||
}
|
4
lib/pages/member_archive/index.dart
Normal file
4
lib/pages/member_archive/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library member_archive;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
120
lib/pages/member_archive/view.dart
Normal file
120
lib/pages/member_archive/view.dart
Normal file
@ -0,0 +1,120 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/widgets/video_card_h.dart';
|
||||
import 'controller.dart';
|
||||
|
||||
class MemberArchivePage extends StatefulWidget {
|
||||
const MemberArchivePage({super.key});
|
||||
|
||||
@override
|
||||
State<MemberArchivePage> createState() => _MemberArchivePageState();
|
||||
}
|
||||
|
||||
class _MemberArchivePageState extends State<MemberArchivePage> {
|
||||
final MemberArchiveController _memberArchivesController =
|
||||
Get.put(MemberArchiveController());
|
||||
late Future _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture =
|
||||
_memberArchivesController.getMemberArchive('onRefresh');
|
||||
scrollController = _memberArchivesController.scrollController;
|
||||
scrollController.addListener(
|
||||
() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle(
|
||||
'member_archives', const Duration(milliseconds: 500), () {
|
||||
_memberArchivesController.onLoad();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('他的投稿'),
|
||||
// actions: [
|
||||
// Obx(
|
||||
// () => PopupMenuButton<String>(
|
||||
// padding: EdgeInsets.zero,
|
||||
// tooltip: '投稿排序',
|
||||
// icon: Icon(
|
||||
// Icons.more_vert_outlined,
|
||||
// color: Theme.of(context).colorScheme.outline,
|
||||
// ),
|
||||
// position: PopupMenuPosition.under,
|
||||
// onSelected: (String type) {},
|
||||
// itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
|
||||
// for (var i in _memberArchivesController.orderList) ...[
|
||||
// PopupMenuItem<String>(
|
||||
// onTap: () {},
|
||||
// value: _memberArchivesController.currentOrder['label'],
|
||||
// child: Row(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// children: [
|
||||
// Text(i['label']!),
|
||||
// if (_memberArchivesController.currentOrder['label'] ==
|
||||
// i['label']) ...[
|
||||
// const SizedBox(width: 10),
|
||||
// const Icon(Icons.done, size: 20),
|
||||
// ],
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ]
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ],
|
||||
),
|
||||
body: CustomScrollView(
|
||||
controller: _memberArchivesController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data != null) {
|
||||
Map data = snapshot.data as Map;
|
||||
List list = _memberArchivesController.archivesList;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => list.isNotEmpty
|
||||
? SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return VideoCardH(
|
||||
videoItem: list[index],
|
||||
showOwner: false,
|
||||
showPubdate: true,
|
||||
);
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
)
|
||||
: const SliverToBoxAdapter(),
|
||||
);
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
3
lib/pages/member_coin/controller.dart
Normal file
3
lib/pages/member_coin/controller.dart
Normal file
@ -0,0 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberCoinController extends GetxController {}
|
@ -1,4 +1,4 @@
|
||||
library dynamic_panel;
|
||||
library member_coin;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
15
lib/pages/member_coin/view.dart
Normal file
15
lib/pages/member_coin/view.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MemberCoinPage extends StatefulWidget {
|
||||
const MemberCoinPage({super.key});
|
||||
|
||||
@override
|
||||
State<MemberCoinPage> createState() => _MemberCoinPageState();
|
||||
}
|
||||
|
||||
class _MemberCoinPageState extends State<MemberCoinPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold();
|
||||
}
|
||||
}
|
95
lib/pages/member_coin/widgets/item.dart
Normal file
95
lib/pages/member_coin/widgets/item.dart
Normal file
@ -0,0 +1,95 @@
|
||||
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/coin.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class MemberCoinsItem extends StatelessWidget {
|
||||
final MemberCoinsDataModel coinItem;
|
||||
|
||||
const MemberCoinsItem({
|
||||
Key? key,
|
||||
required this.coinItem,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(coinItem.aid);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
int cid =
|
||||
await SearchHttp.ab2c(aid: coinItem.aid, bvid: coinItem.bvid);
|
||||
Get.toNamed('/video?bvid=${coinItem.bvid}&cid=$cid',
|
||||
arguments: {'videoItem': coinItem, '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: coinItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
if (coinItem.duration != null)
|
||||
PBadge(
|
||||
bottom: 6,
|
||||
right: 6,
|
||||
type: 'gray',
|
||||
text: Utils.timeFormat(coinItem.duration),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
coinItem.title!,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
view: coinItem.view,
|
||||
theme: 'gray',
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
Utils.CustomStamp_str(
|
||||
timestamp: coinItem.pubdate, date: 'MM-DD'),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
41
lib/pages/member_dynamics/controller.dart
Normal file
41
lib/pages/member_dynamics/controller.dart
Normal file
@ -0,0 +1,41 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
import 'package:pilipala/models/dynamics/result.dart';
|
||||
|
||||
class MemberDynamicsController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
late int mid;
|
||||
String offset = '';
|
||||
int count = 0;
|
||||
bool hasMore = true;
|
||||
RxList<DynamicItemModel> dynamicsList = <DynamicItemModel>[].obs;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
}
|
||||
|
||||
Future getMemberDynamic(type) async {
|
||||
if (type == 'onRefresh') {
|
||||
offset = '';
|
||||
dynamicsList.clear();
|
||||
}
|
||||
var res = await MemberHttp.memberDynamic(
|
||||
offset: offset,
|
||||
mid: mid,
|
||||
);
|
||||
if (res['status']) {
|
||||
dynamicsList.addAll(res['data'].items);
|
||||
offset = res['data'].offset;
|
||||
hasMore = res['data'].hasMore;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
getMemberDynamic('onLoad');
|
||||
}
|
||||
}
|
4
lib/pages/member_dynamics/index.dart
Normal file
4
lib/pages/member_dynamics/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library member_dynamics;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
86
lib/pages/member_dynamics/view.dart
Normal file
86
lib/pages/member_dynamics/view.dart
Normal file
@ -0,0 +1,86 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/pages/member_dynamics/index.dart';
|
||||
|
||||
import '../dynamics/widgets/dynamic_panel.dart';
|
||||
|
||||
class MemberDynamicsPage extends StatefulWidget {
|
||||
const MemberDynamicsPage({super.key});
|
||||
|
||||
@override
|
||||
State<MemberDynamicsPage> createState() => _MemberDynamicsPageState();
|
||||
}
|
||||
|
||||
class _MemberDynamicsPageState extends State<MemberDynamicsPage> {
|
||||
final MemberDynamicsController _memberDynamicController =
|
||||
Get.put(MemberDynamicsController());
|
||||
late Future _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture =
|
||||
_memberDynamicController.getMemberDynamic('onRefresh');
|
||||
scrollController = _memberDynamicController.scrollController;
|
||||
scrollController.addListener(
|
||||
() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle(
|
||||
'member_dynamics', const Duration(milliseconds: 500), () {
|
||||
_memberDynamicController.onLoad();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_memberDynamicController.scrollController.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('他的动态'),
|
||||
),
|
||||
body: CustomScrollView(
|
||||
controller: _memberDynamicController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data as Map;
|
||||
List list = _memberDynamicController.dynamicsList;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => list.isNotEmpty
|
||||
? SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return DynamicPanel(item: list[index]);
|
||||
},
|
||||
childCount: list.length,
|
||||
),
|
||||
)
|
||||
: const SliverToBoxAdapter(),
|
||||
);
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
} else {
|
||||
return const SliverToBoxAdapter();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
3
lib/pages/member_like/controller.dart
Normal file
3
lib/pages/member_like/controller.dart
Normal file
@ -0,0 +1,3 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class MemberLikeController extends GetxController {}
|
4
lib/pages/member_like/index.dart
Normal file
4
lib/pages/member_like/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library member_like;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
15
lib/pages/member_like/view.dart
Normal file
15
lib/pages/member_like/view.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MemberLikePage extends StatefulWidget {
|
||||
const MemberLikePage({super.key});
|
||||
|
||||
@override
|
||||
State<MemberLikePage> createState() => _MemberLikePageState();
|
||||
}
|
||||
|
||||
class _MemberLikePageState extends State<MemberLikePage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold();
|
||||
}
|
||||
}
|
47
lib/pages/member_seasons/controller.dart
Normal file
47
lib/pages/member_seasons/controller.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/member.dart';
|
||||
import 'package:pilipala/models/member/seasons.dart';
|
||||
|
||||
class MemberSeasonsController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
late int mid;
|
||||
late int seasonId;
|
||||
int pn = 1;
|
||||
int ps = 30;
|
||||
int count = 0;
|
||||
RxList<MemberArchiveItem> seasonsList = <MemberArchiveItem>[].obs;
|
||||
late Map page;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
mid = int.parse(Get.parameters['mid']!);
|
||||
seasonId = int.parse(Get.parameters['seasonId']!);
|
||||
}
|
||||
|
||||
// 获取专栏详情
|
||||
Future getSeasonDetail(type) async {
|
||||
if (type == 'onRefresh') {
|
||||
pn = 1;
|
||||
}
|
||||
var res = await MemberHttp.getSeasonDetail(
|
||||
mid: mid,
|
||||
seasonId: seasonId,
|
||||
pn: pn,
|
||||
ps: ps,
|
||||
sortReverse: false,
|
||||
);
|
||||
if (res['status']) {
|
||||
seasonsList.addAll(res['data'].archives);
|
||||
page = res['data'].page;
|
||||
pn += 1;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// 上拉加载
|
||||
Future onLoad() async {
|
||||
getSeasonDetail('onLoad');
|
||||
}
|
||||
}
|
4
lib/pages/member_seasons/index.dart
Normal file
4
lib/pages/member_seasons/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library member_seasons;
|
||||
|
||||
export 'controller.dart';
|
||||
export 'view.dart';
|
103
lib/pages/member_seasons/view.dart
Normal file
103
lib/pages/member_seasons/view.dart
Normal file
@ -0,0 +1,103 @@
|
||||
import 'package:easy_debounce/easy_throttle.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'controller.dart';
|
||||
import 'widgets/item.dart';
|
||||
|
||||
class MemberSeasonsPage extends StatefulWidget {
|
||||
const MemberSeasonsPage({super.key});
|
||||
|
||||
@override
|
||||
State<MemberSeasonsPage> createState() => _MemberSeasonsPageState();
|
||||
}
|
||||
|
||||
class _MemberSeasonsPageState extends State<MemberSeasonsPage> {
|
||||
final MemberSeasonsController _memberSeasonsController =
|
||||
Get.put(MemberSeasonsController());
|
||||
late Future _futureBuilderFuture;
|
||||
late ScrollController scrollController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_futureBuilderFuture =
|
||||
_memberSeasonsController.getSeasonDetail('onRefresh');
|
||||
scrollController = _memberSeasonsController.scrollController;
|
||||
scrollController.addListener(
|
||||
() {
|
||||
if (scrollController.position.pixels >=
|
||||
scrollController.position.maxScrollExtent - 200) {
|
||||
EasyThrottle.throttle(
|
||||
'member_archives', const Duration(milliseconds: 500), () {
|
||||
_memberSeasonsController.onLoad();
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('他的专栏'),
|
||||
),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace,
|
||||
right: StyleString.safeSpace,
|
||||
),
|
||||
child: SingleChildScrollView(
|
||||
controller: _memberSeasonsController.scrollController,
|
||||
child: FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
if (snapshot.data != null) {
|
||||
Map data = snapshot.data as Map;
|
||||
List list = _memberSeasonsController.seasonsList;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => list.isNotEmpty
|
||||
? LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
return GridView.builder(
|
||||
gridDelegate:
|
||||
const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 2,
|
||||
crossAxisSpacing: StyleString.safeSpace,
|
||||
mainAxisSpacing: StyleString.safeSpace,
|
||||
childAspectRatio: 0.94,
|
||||
),
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
itemCount: _memberSeasonsController
|
||||
.seasonsList.length,
|
||||
itemBuilder: (context, i) {
|
||||
return MemberSeasonsItem(
|
||||
seasonItem: _memberSeasonsController
|
||||
.seasonsList[i],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: const SizedBox(),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
} else {
|
||||
return const SizedBox();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
98
lib/pages/member_seasons/widgets/item.dart
Normal file
98
lib/pages/member_seasons/widgets/item.dart
Normal file
@ -0,0 +1,98 @@
|
||||
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/utils/utils.dart';
|
||||
|
||||
class MemberSeasonsItem extends StatelessWidget {
|
||||
final dynamic seasonItem;
|
||||
|
||||
const MemberSeasonsItem({
|
||||
Key? key,
|
||||
required this.seasonItem,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
String heroTag = Utils.makeHeroTag(seasonItem.aid);
|
||||
return Card(
|
||||
elevation: 0,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: EdgeInsets.zero,
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
int cid =
|
||||
await SearchHttp.ab2c(aid: seasonItem.aid, bvid: seasonItem.bvid);
|
||||
Get.toNamed('/video?bvid=${seasonItem.bvid}&cid=$cid',
|
||||
arguments: {'videoItem': seasonItem, '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: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
src: seasonItem.pic,
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
if (seasonItem.duration != null)
|
||||
PBadge(
|
||||
bottom: 6,
|
||||
right: 6,
|
||||
type: 'gray',
|
||||
text: Utils.timeFormat(seasonItem.duration),
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(5, 6, 0, 0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
seasonItem.title,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
StatView(
|
||||
view: seasonItem.view,
|
||||
theme: 'gray',
|
||||
),
|
||||
const Spacer(),
|
||||
Text(
|
||||
Utils.CustomStamp_str(
|
||||
timestamp: seasonItem.pubdate, date: 'MM-DD'),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -76,6 +76,7 @@ class VideoIntroController extends GetxController {
|
||||
if (Get.arguments.containsKey('videoItem')) {
|
||||
preRender = true;
|
||||
var args = Get.arguments['videoItem'];
|
||||
var keys = Get.arguments.keys.toList();
|
||||
videoItem!['pic'] = args.pic;
|
||||
if (args.title is String) {
|
||||
videoItem!['title'] = args.title;
|
||||
@ -86,11 +87,9 @@ class VideoIntroController extends GetxController {
|
||||
}
|
||||
videoItem!['title'] = str;
|
||||
}
|
||||
if (args.stat != null) {
|
||||
videoItem!['stat'] = args.stat;
|
||||
}
|
||||
videoItem!['pubdate'] = args.pubdate;
|
||||
videoItem!['owner'] = args.owner;
|
||||
videoItem!['stat'] = keys.contains('stat') && args.stat;
|
||||
videoItem!['pubdate'] = keys.contains('pubdate') && args.pubdate;
|
||||
videoItem!['owner'] = keys.contains('owner') && args.owner;
|
||||
}
|
||||
}
|
||||
userLogin = userInfo != null;
|
||||
|
@ -247,7 +247,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
padding: const EdgeInsets.only(
|
||||
left: StyleString.safeSpace, right: StyleString.safeSpace, top: 10),
|
||||
sliver: SliverToBoxAdapter(
|
||||
child: !loadingStatus || videoItem.isNotEmpty
|
||||
child: !loadingStatus
|
||||
? Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@ -277,7 +277,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
children: [
|
||||
StatView(
|
||||
theme: 'gray',
|
||||
view: !widget.loadingStatus
|
||||
view: !loadingStatus
|
||||
? widget.videoDetail!.stat!.view
|
||||
: videoItem['stat'].view,
|
||||
size: 'medium',
|
||||
@ -285,7 +285,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
const SizedBox(width: 10),
|
||||
StatDanMu(
|
||||
theme: 'gray',
|
||||
danmu: !widget.loadingStatus
|
||||
danmu: !loadingStatus
|
||||
? widget.videoDetail!.stat!.danmaku
|
||||
: videoItem['stat'].danmaku,
|
||||
size: 'medium',
|
||||
@ -293,7 +293,7 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
Utils.dateFormat(
|
||||
!widget.loadingStatus
|
||||
!loadingStatus
|
||||
? widget.videoDetail!.pubdate
|
||||
: videoItem['pubdate'],
|
||||
formatType: 'detail'),
|
||||
|
@ -21,7 +21,12 @@ import 'package:pilipala/pages/later/index.dart';
|
||||
import 'package:pilipala/pages/liveRoom/view.dart';
|
||||
import 'package:pilipala/pages/login/index.dart';
|
||||
import 'package:pilipala/pages/member/index.dart';
|
||||
import 'package:pilipala/pages/member_archive/index.dart';
|
||||
import 'package:pilipala/pages/member_coin/index.dart';
|
||||
import 'package:pilipala/pages/member_dynamics/index.dart';
|
||||
import 'package:pilipala/pages/member_like/index.dart';
|
||||
import 'package:pilipala/pages/member_search/index.dart';
|
||||
import 'package:pilipala/pages/member_seasons/index.dart';
|
||||
import 'package:pilipala/pages/preview/index.dart';
|
||||
import 'package:pilipala/pages/search/index.dart';
|
||||
import 'package:pilipala/pages/searchResult/index.dart';
|
||||
@ -125,6 +130,19 @@ class Routes {
|
||||
CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()),
|
||||
// 登录页面
|
||||
CustomGetPage(name: '/loginPage', page: () => const LoginPage()),
|
||||
// 用户动态
|
||||
CustomGetPage(
|
||||
name: '/memberDynamics', page: () => const MemberDynamicsPage()),
|
||||
// 用户投稿
|
||||
CustomGetPage(
|
||||
name: '/memberArchive', page: () => const MemberArchivePage()),
|
||||
// 用户最近投币
|
||||
CustomGetPage(name: '/memberCoin', page: () => const MemberCoinPage()),
|
||||
// 用户最近喜欢
|
||||
CustomGetPage(name: '/memberLike', page: () => const MemberLikePage()),
|
||||
// 用户专栏
|
||||
CustomGetPage(
|
||||
name: '/memberSeasons', page: () => const MemberSeasonsPage()),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -146,7 +146,7 @@ class Utils {
|
||||
int.parse(MM) == DateTime.now().month) {
|
||||
// 当天
|
||||
if (int.parse(DD) == DateTime.now().day) {
|
||||
return date.split(' ')[1];
|
||||
return '今天';
|
||||
}
|
||||
}
|
||||
return date;
|
||||
|
Reference in New Issue
Block a user