feat: 观看记录查询
This commit is contained in:
@ -1,8 +1,11 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:pilipala/http/api.dart';
|
||||
import 'package:pilipala/http/init.dart';
|
||||
import 'package:pilipala/models/model_hot_video_item.dart';
|
||||
import 'package:pilipala/models/user/fav_detail.dart';
|
||||
import 'package:pilipala/models/user/fav_folder.dart';
|
||||
import 'package:pilipala/models/user/history.dart';
|
||||
import 'package:pilipala/models/user/info.dart';
|
||||
import 'package:pilipala/models/user/stat.dart';
|
||||
|
||||
@ -94,4 +97,19 @@ class UserHttp {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
|
||||
// 观看历史
|
||||
static Future historyList(int? max, int? viewAt) async {
|
||||
var res = await Request().get(Api.historyList, data: {
|
||||
'type': 'all',
|
||||
'ps': 20,
|
||||
'max': max ?? 0,
|
||||
'view_at': viewAt ?? 0,
|
||||
});
|
||||
if (res.data['code'] == 0) {
|
||||
return {'status': true, 'data': HistoryData.fromJson(res.data['data'])};
|
||||
} else {
|
||||
return {'status': false, 'data': [], 'msg': res.data['message']};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
168
lib/models/user/history.dart
Normal file
168
lib/models/user/history.dart
Normal file
@ -0,0 +1,168 @@
|
||||
class HistoryData {
|
||||
HistoryData({
|
||||
this.cursor,
|
||||
this.tab,
|
||||
this.list,
|
||||
});
|
||||
|
||||
Cursor? cursor;
|
||||
List<HisTabItem>? tab;
|
||||
List<HisListItem>? list;
|
||||
|
||||
HistoryData.fromJson(Map<String, dynamic> json) {
|
||||
cursor = Cursor.fromJson(json['cursor']);
|
||||
tab = json['tab'].map<HisTabItem>((e) => HisTabItem.fromJson(e)).toList();
|
||||
list =
|
||||
json['list'].map<HisListItem>((e) => HisListItem.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class Cursor {
|
||||
Cursor({
|
||||
this.max,
|
||||
this.viewAt,
|
||||
this.business,
|
||||
this.ps,
|
||||
});
|
||||
|
||||
int? max;
|
||||
int? viewAt;
|
||||
String? business;
|
||||
int? ps;
|
||||
|
||||
Cursor.fromJson(Map<String, dynamic> json) {
|
||||
max = json['max'];
|
||||
viewAt = json['view_at'];
|
||||
business = json['business'];
|
||||
ps = json['ps'];
|
||||
}
|
||||
}
|
||||
|
||||
class HisTabItem {
|
||||
HisTabItem({
|
||||
this.type,
|
||||
this.name,
|
||||
});
|
||||
|
||||
String? type;
|
||||
String? name;
|
||||
|
||||
HisTabItem.fromJson(Map<String, dynamic> json) {
|
||||
type = json['type'];
|
||||
name = json['name'];
|
||||
}
|
||||
}
|
||||
|
||||
class HisListItem {
|
||||
HisListItem({
|
||||
this.title,
|
||||
this.longTitle,
|
||||
this.cover,
|
||||
this.pic,
|
||||
this.covers,
|
||||
this.uri,
|
||||
this.history,
|
||||
this.videos,
|
||||
this.authorName,
|
||||
this.authorFace,
|
||||
this.authorMid,
|
||||
this.viewAt,
|
||||
this.progress,
|
||||
this.badge,
|
||||
this.showTitle,
|
||||
this.duration,
|
||||
this.current,
|
||||
this.total,
|
||||
this.newDesc,
|
||||
this.isFinish,
|
||||
this.isFav,
|
||||
this.kid,
|
||||
this.tagName,
|
||||
this.liveStatus,
|
||||
});
|
||||
|
||||
String? title;
|
||||
String? longTitle;
|
||||
String? cover;
|
||||
String? pic;
|
||||
String? covers;
|
||||
String? uri;
|
||||
History? history;
|
||||
int? videos;
|
||||
String? authorName;
|
||||
String? authorFace;
|
||||
int? authorMid;
|
||||
int? viewAt;
|
||||
int? progress;
|
||||
String? badge;
|
||||
String? showTitle;
|
||||
int? duration;
|
||||
String? current;
|
||||
int? total;
|
||||
String? newDesc;
|
||||
int? isFinish;
|
||||
int? isFav;
|
||||
int? kid;
|
||||
String? tagName;
|
||||
int? liveStatus;
|
||||
|
||||
HisListItem.fromJson(Map<String, dynamic> json) {
|
||||
title = json['title'];
|
||||
longTitle = json['long_title'];
|
||||
cover = json['cover'];
|
||||
pic = json['cover'] ?? '';
|
||||
covers = json['covers'] ?? '';
|
||||
uri = json['uri'];
|
||||
history = History.fromJson(json['history']);
|
||||
videos = json['videos'];
|
||||
authorName = json['author_name'];
|
||||
authorFace = json['author_face'];
|
||||
authorMid = json['author_mid'];
|
||||
viewAt = json['view_at'];
|
||||
progress = json['progress'];
|
||||
badge = json['badge'];
|
||||
showTitle = json['show_title'];
|
||||
duration = json['duration'];
|
||||
current = json['current'];
|
||||
total = json['total'];
|
||||
newDesc = json['new_desc'];
|
||||
isFinish = json['is_finish'];
|
||||
isFav = json['is_fav'];
|
||||
kid = json['kid'];
|
||||
tagName = json['tag_name'];
|
||||
liveStatus = json['live_status'];
|
||||
}
|
||||
}
|
||||
|
||||
class History {
|
||||
History({
|
||||
this.oid,
|
||||
this.epid,
|
||||
this.bvid,
|
||||
this.page,
|
||||
this.cid,
|
||||
this.part,
|
||||
this.business,
|
||||
this.dt,
|
||||
});
|
||||
|
||||
int? oid;
|
||||
int? epid;
|
||||
String? bvid;
|
||||
int? page;
|
||||
int? cid;
|
||||
String? part;
|
||||
String? business;
|
||||
int? dt;
|
||||
|
||||
History.fromJson(Map<String, dynamic> json) {
|
||||
oid = json['oid'];
|
||||
epid = json['epid'];
|
||||
bvid = json['bvid'];
|
||||
page = json['page'];
|
||||
cid = json['cid'];
|
||||
part = json['part'];
|
||||
business = json['business'];
|
||||
dt = json['dt'];
|
||||
}
|
||||
}
|
44
lib/pages/history/controller.dart
Normal file
44
lib/pages/history/controller.dart
Normal file
@ -0,0 +1,44 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/http/user.dart';
|
||||
import 'package:pilipala/models/user/history.dart';
|
||||
|
||||
class HistoryController extends GetxController {
|
||||
final ScrollController scrollController = ScrollController();
|
||||
RxList<HisListItem> historyList = [HisListItem()].obs;
|
||||
bool isLoadingMore = false;
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
queryHistoryList();
|
||||
super.onInit();
|
||||
}
|
||||
|
||||
Future queryHistoryList({type = 'init'}) async {
|
||||
int max = 0;
|
||||
int viewAt = 0;
|
||||
if (type == 'onload') {
|
||||
max = historyList.last.history!.oid!;
|
||||
viewAt = historyList.last.viewAt!;
|
||||
}
|
||||
isLoadingMore = true;
|
||||
var res = await UserHttp.historyList(max, viewAt);
|
||||
isLoadingMore = false;
|
||||
if (res['status']) {
|
||||
if (type == 'onload') {
|
||||
historyList.addAll(res['data'].list);
|
||||
} else {
|
||||
historyList.value = res['data'].list;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Future onLoad() async {
|
||||
queryHistoryList(type: 'onload');
|
||||
}
|
||||
|
||||
Future onRefresh() async {
|
||||
queryHistoryList(type: 'onRefresh');
|
||||
}
|
||||
}
|
4
lib/pages/history/index.dart
Normal file
4
lib/pages/history/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library history;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
94
lib/pages/history/view.dart
Normal file
94
lib/pages/history/view.dart
Normal file
@ -0,0 +1,94 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/skeleton/video_card_h.dart';
|
||||
import 'package:pilipala/common/widgets/http_error.dart';
|
||||
import 'package:pilipala/pages/history/index.dart';
|
||||
|
||||
import 'widgets/item.dart';
|
||||
|
||||
class HistoryPage extends StatefulWidget {
|
||||
const HistoryPage({super.key});
|
||||
|
||||
@override
|
||||
State<HistoryPage> createState() => _HistoryPageState();
|
||||
}
|
||||
|
||||
class _HistoryPageState extends State<HistoryPage> {
|
||||
final HistoryController _historyController = Get.put(HistoryController());
|
||||
Future? _futureBuilderFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_futureBuilderFuture = _historyController.queryHistoryList();
|
||||
super.initState();
|
||||
|
||||
_historyController.scrollController.addListener(
|
||||
() {
|
||||
if (_historyController.scrollController.position.pixels >=
|
||||
_historyController.scrollController.position.maxScrollExtent -
|
||||
300) {
|
||||
if (!_historyController.isLoadingMore) {
|
||||
_historyController.onLoad();
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('观看记录'),
|
||||
centerTitle: false,
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () async {
|
||||
await _historyController.onRefresh();
|
||||
return;
|
||||
},
|
||||
child: CustomScrollView(
|
||||
controller: _historyController.scrollController,
|
||||
slivers: [
|
||||
FutureBuilder(
|
||||
future: _futureBuilderFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.done) {
|
||||
Map data = snapshot.data;
|
||||
if (data['status']) {
|
||||
return Obx(
|
||||
() => SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return HistoryItem(
|
||||
videoItem: _historyController.historyList[index],
|
||||
);
|
||||
}, childCount: _historyController.historyList.length),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return HttpError(
|
||||
errMsg: data['msg'],
|
||||
fn: () => setState(() {}),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// 骨架屏
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
return const VideoCardHSkeleton();
|
||||
}, childCount: 10),
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
SliverToBoxAdapter(
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).padding.bottom + 10,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
154
lib/pages/history/widgets/item.dart
Normal file
154
lib/pages/history/widgets/item.dart
Normal file
@ -0,0 +1,154 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/common/constants.dart';
|
||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||
import 'package:pilipala/utils/utils.dart';
|
||||
|
||||
class HistoryItem extends StatelessWidget {
|
||||
var videoItem;
|
||||
HistoryItem({super.key, required this.videoItem});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
int aid = videoItem.history.oid;
|
||||
String heroTag = Utils.makeHeroTag(aid);
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
await Future.delayed(const Duration(milliseconds: 200));
|
||||
Get.toNamed('/video?aid=$aid',
|
||||
arguments: {'heroTag': heroTag, 'pic': videoItem.cover});
|
||||
},
|
||||
child: Column(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
StyleString.cardSpace, 7, StyleString.cardSpace, 7),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double width =
|
||||
(boxConstraints.maxWidth - StyleString.cardSpace * 6) / 2;
|
||||
return SizedBox(
|
||||
height: width / StyleString.aspectRatio,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AspectRatio(
|
||||
aspectRatio: StyleString.aspectRatio,
|
||||
child: LayoutBuilder(
|
||||
builder: (context, boxConstraints) {
|
||||
double maxWidth = boxConstraints.maxWidth;
|
||||
double maxHeight = boxConstraints.maxHeight;
|
||||
double PR = MediaQuery.of(context).devicePixelRatio;
|
||||
return Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: heroTag,
|
||||
child: NetworkImgLayer(
|
||||
// src: videoItem['pic'] +
|
||||
// '@${(maxWidth * 2).toInt()}w',
|
||||
src: videoItem.cover + '@.webp',
|
||||
width: maxWidth,
|
||||
height: maxHeight,
|
||||
),
|
||||
),
|
||||
Positioned(
|
||||
right: 4,
|
||||
bottom: 4,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 1, horizontal: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Colors.black54.withOpacity(0.4)),
|
||||
child: Text(
|
||||
Utils.timeFormat(videoItem.duration!),
|
||||
style: const TextStyle(
|
||||
fontSize: 11, color: Colors.white),
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
VideoContent(videoItem: videoItem)
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Divider(
|
||||
height: 1,
|
||||
indent: 8,
|
||||
endIndent: 12,
|
||||
color: Theme.of(context).dividerColor.withOpacity(0.08),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VideoContent extends StatelessWidget {
|
||||
final videoItem;
|
||||
const VideoContent({super.key, required this.videoItem});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
videoItem.title,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
fontWeight: FontWeight.w500),
|
||||
maxLines: videoItem.videos > 1 ? 1 : 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (videoItem.videos > 1)
|
||||
Text(
|
||||
videoItem.showTitle,
|
||||
textAlign: TextAlign.start,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Spacer(),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
videoItem.authorName,
|
||||
style: TextStyle(
|
||||
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||
color: Theme.of(context).colorScheme.outline,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
Utils.dateFormat(videoItem.viewAt!),
|
||||
style: TextStyle(
|
||||
fontSize: 11,
|
||||
color: Theme.of(context).colorScheme.outline),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ class MediaController extends GetxController {
|
||||
{
|
||||
'icon': Icons.history,
|
||||
'title': '观看记录',
|
||||
'onTap': () {},
|
||||
'onTap': () => Get.toNamed('/history'),
|
||||
},
|
||||
{
|
||||
'icon': Icons.star_border,
|
||||
|
@ -41,18 +41,22 @@ class VideoDetailController extends GetxController {
|
||||
videoItem['pic'] = args.pic;
|
||||
}
|
||||
}
|
||||
if (Get.arguments.containsKey('pic')) {
|
||||
videoItem['pic'] = Get.arguments['pic'];
|
||||
}
|
||||
heroTag = Get.arguments['heroTag'];
|
||||
}
|
||||
}
|
||||
|
||||
showReplyReplyPanel() {
|
||||
PersistentBottomSheetController<void>? ctr = scaffoldKey.currentState?.showBottomSheet<void>((BuildContext context) {
|
||||
PersistentBottomSheetController<void>? ctr =
|
||||
scaffoldKey.currentState?.showBottomSheet<void>((BuildContext context) {
|
||||
return VideoReplyReplyPanel(
|
||||
oid: oid,
|
||||
rpid: fRpid,
|
||||
closePanel: ()=> {
|
||||
closePanel: () => {
|
||||
fRpid = 0,
|
||||
},
|
||||
},
|
||||
firstFloor: firstFloor,
|
||||
);
|
||||
});
|
||||
|
@ -380,11 +380,18 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
|
||||
duration: const Duration(milliseconds: 150),
|
||||
child: SizedBox(
|
||||
height: 36,
|
||||
child: Obx(()=>
|
||||
videoIntroController.followStatus.isNotEmpty ? ElevatedButton(
|
||||
onPressed: () => videoIntroController.actionRelationMod(),
|
||||
child: Text(videoIntroController.followStatus['attribute'] == 0 ? '关注' : '已关注'),
|
||||
) : const SizedBox(),
|
||||
child: Obx(
|
||||
() => videoIntroController.followStatus.isNotEmpty
|
||||
? ElevatedButton(
|
||||
onPressed: () => videoIntroController
|
||||
.actionRelationMod(),
|
||||
child: Text(videoIntroController
|
||||
.followStatus['attribute'] ==
|
||||
0
|
||||
? '关注'
|
||||
: '已关注'),
|
||||
)
|
||||
: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -45,7 +45,7 @@ class VideoReplyController extends GetxController {
|
||||
if (res['data'].replies.isNotEmpty) {
|
||||
currentPage = currentPage + 1;
|
||||
noMore.value = '加载中';
|
||||
if(res['data'].page.count == res['data'].page.acount){
|
||||
if (res['data'].page.count == res['data'].page.acount) {
|
||||
noMore.value = '没有更多了';
|
||||
}
|
||||
} else {
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:pilipala/pages/fav/index.dart';
|
||||
import 'package:pilipala/pages/favDetail/index.dart';
|
||||
import 'package:pilipala/pages/history/index.dart';
|
||||
import 'package:pilipala/pages/home/index.dart';
|
||||
import 'package:pilipala/pages/hot/index.dart';
|
||||
import 'package:pilipala/pages/later/index.dart';
|
||||
@ -38,5 +39,7 @@ class Routes {
|
||||
GetPage(name: '/favDetail', page: () => const FavDetailPage()),
|
||||
// 稍后再看
|
||||
GetPage(name: '/later', page: () => const LaterPage()),
|
||||
// 历史记录
|
||||
GetPage(name: '/history', page: () => const HistoryPage()),
|
||||
];
|
||||
}
|
||||
|
Reference in New Issue
Block a user