feat: 收藏夹

This commit is contained in:
guozhigq
2023-05-11 00:14:00 +08:00
parent e612e60361
commit ea674c4b4a
15 changed files with 481 additions and 71 deletions

View File

@ -26,4 +26,8 @@ class Api {
// 获取当前用户状态
static const String userStatOwner = '/x/web-interface/nav/stat';
// 收藏夹
// https://api.bilibili.com/x/v3/fav/folder/created/list?pn=1&ps=10&up_mid=17340771
static const String userFavFolder = '/x/v3/fav/folder/created/list';
}

View File

@ -1,5 +1,6 @@
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart';
@ -29,7 +30,26 @@ class UserHttp {
UserStat data = UserStat.fromJson(res.data['data']);
return {'status': true, 'data': data};
} else {
return {'status': false};
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
// 收藏夹
static Future<dynamic> userfavFolder({
required int pn,
required int ps,
required int mid,
}) async {
var res = await Request().get(Api.userFavFolder, data: {
'pn': pn,
'ps': ps,
'up_mid': mid,
});
if (res.data['code'] == 0) {
FavFolderData data = FavFolderData.fromJson(res.data['data']);
return {'status': true, 'data': data};
} else {
return {'status': false, 'data': [], 'msg': res.data['message']};
}
}
}

View File

@ -0,0 +1,108 @@
class FavFolderData {
FavFolderData({
this.count,
this.list,
this.hasMore,
});
int? count;
List<FavFolderItemData>? list;
bool? hasMore;
FavFolderData.fromJson(Map<String, dynamic> json) {
count = json['count'];
list = json['list'] != null
? json['list']
.map<FavFolderItemData>((e) => FavFolderItemData.fromJson(e))
.toList()
: [FavFolderItemData()];
hasMore = json['has_more'];
}
}
class FavFolderItemData {
FavFolderItemData({
this.id,
this.fid,
this.mid,
this.attr,
this.title,
this.cover,
this.upper,
this.coverType,
this.intro,
this.ctime,
this.mtime,
this.state,
this.favState,
this.mediaCount,
this.viewCount,
this.vt,
this.playSwitch,
this.type,
this.link,
this.bvid,
});
int? id;
int? fid;
int? mid;
int? attr;
String? title;
String? cover;
Upper? upper;
int? coverType;
String? intro;
int? ctime;
int? mtime;
int? state;
int? favState;
int? mediaCount;
int? viewCount;
int? vt;
int? playSwitch;
int? type;
String? link;
String? bvid;
FavFolderItemData.fromJson(Map<String, dynamic> json) {
id = json['id'];
fid = json['fid'];
mid = json['mid'];
attr = json['attr'];
title = json['title'];
cover = json['cover'];
upper = Upper.fromJson(json['upper']);
coverType = json['cover_type'];
intro = json['intro'];
ctime = json['ctime'];
mtime = json['mtime'];
state = json['state'];
favState = json['fav_state'];
mediaCount = json['media_count'];
viewCount = json['view_count'];
vt = json['vt'];
playSwitch = json['play_switch'];
type = json['type'];
link = json['link'];
bvid = json['bvid'];
}
}
class Upper {
Upper({
this.mid,
this.name,
this.face,
});
int? mid;
String? name;
String? face;
Upper.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
name = json['name'];
face = json['face'];
}
}

View File

@ -0,0 +1,3 @@
import 'package:get/get.dart';
class FavController extends GetxController {}

4
lib/pages/fav/index.dart Normal file
View File

@ -0,0 +1,4 @@
library fav;
export './controller.dart';
export './view.dart';

20
lib/pages/fav/view.dart Normal file
View File

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
class FavPage extends StatefulWidget {
const FavPage({super.key});
@override
State<FavPage> createState() => _FavPageState();
}
class _FavPageState extends State<FavPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: false,
title: Text('我的收藏'),
),
);
}
}

View File

@ -2,6 +2,8 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart';
import 'package:pilipala/pages/mine/view.dart';
class HomeAppBar extends StatelessWidget {
const HomeAppBar({super.key});
@ -42,6 +44,12 @@ class HomeAppBar extends StatelessWidget {
// onPressed: () {},
// icon: const Icon(CupertinoIcons.bell, size: 22),
// ),
IconButton(
onPressed: () {
Get.bottomSheet(const MinePage());
},
icon: const Icon(CupertinoIcons.person, size: 22),
),
const SizedBox(width: 10)
],
elevation: 0,

View File

@ -5,26 +5,26 @@ import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/home/view.dart';
import 'package:pilipala/pages/hot/view.dart';
import 'package:pilipala/pages/mine/view.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/storage.dart';
class MainController extends GetxController {
List<Widget> pages = <Widget>[
const HomePage(),
const HotPage(),
const MinePage(),
const MediaPage(),
];
RxList navigationBars = [
{
// 'icon': const Icon(Icons.home_outlined),
// 'selectedIcon': const Icon(Icons.home),
'icon': const Icon(
CupertinoIcons.house,
size: 18,
CupertinoIcons.square_favorites_alt,
size: 21,
),
'selectedIcon': const Icon(
CupertinoIcons.house_fill,
size: 18,
CupertinoIcons.square_favorites_alt_fill,
size: 21,
),
'label': "推荐",
},
@ -41,46 +41,57 @@ class MainController extends GetxController {
),
'label': "热门",
},
// {
// 'icon': const Icon(
// CupertinoIcons.person,
// size: 21,
// ),
// 'selectedIcon': const Icon(
// CupertinoIcons.person_fill,
// size: 21,
// ),
// 'label': "我的",
// },
{
// 'icon': const Icon(Icons.person_outline),
// 'selectedIcon': const Icon(Icons.person),
'icon': const Icon(
CupertinoIcons.person,
CupertinoIcons.tray_full,
size: 21,
),
'selectedIcon': const Icon(
CupertinoIcons.person_fill,
CupertinoIcons.tray_full_fill,
size: 21,
),
'label': "我的",
'label': "媒体库",
}
].obs;
@override
void onInit() {
super.onInit();
readuUserFace();
// readuUserFace();
}
// 设置头像
readuUserFace() async {
Box user = GStrorage.user;
if (user.get(UserBoxKey.userFace) != null) {
navigationBars.last['icon'] =
navigationBars.last['selectedIcon'] = NetworkImgLayer(
width: 25,
height: 25,
type: 'avatar',
src: user.get(UserBoxKey.userFace),
);
navigationBars.last['label'] = '';
}
}
// readuUserFace() async {
// Box user = GStrorage.user;
// if (user.get(UserBoxKey.userFace) != null) {
// navigationBars.last['icon'] =
// navigationBars.last['selectedIcon'] = NetworkImgLayer(
// width: 25,
// height: 25,
// type: 'avatar',
// src: user.get(UserBoxKey.userFace),
// );
// navigationBars.last['label'] = '我';
// }
// }
// 重置
resetLast() {
navigationBars.last['icon'] = const Icon(Icons.person_outline);
navigationBars.last['selectedIcon'] = const Icon(Icons.person);
navigationBars.last['label'] = '我的';
}
// resetLast() {
// navigationBars.last['icon'] = const Icon(Icons.person_outline);
// navigationBars.last['selectedIcon'] = const Icon(Icons.person);
// navigationBars.last['label'] = '我的';
// }
}

View File

@ -20,7 +20,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
late AnimationController? _animationController;
late Animation<double>? _fadeAnimation;
late Animation<double>? _slideAnimation;
int selectedIndex = 2;
int selectedIndex = 0;
int? _lastSelectTime; //上次点击时间
@override

View File

@ -0,0 +1,41 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/utils/storage.dart';
class MediaController extends GetxController {
Rx<FavFolderData> favFolderData = FavFolderData().obs;
List list = [
{
'icon': Icons.file_download_outlined,
'title': '离线缓存',
'onTap': () => Get.toNamed('/fav'),
},
{
'icon': Icons.history,
'title': '观看记录',
'onTap': () => Get.toNamed('/fav'),
},
{
'icon': Icons.star_border,
'title': '我的收藏',
'onTap': () => Get.toNamed('/fav'),
},
{
'icon': Icons.watch_later_outlined,
'title': '稍后再看',
'onTap': () => Get.toNamed('/fav'),
},
];
Future<dynamic> queryFavFolder() async {
var res = await await UserHttp.userfavFolder(
pn: 1,
ps: 5,
mid: GStrorage.user.get(UserBoxKey.userMid),
);
favFolderData.value = res['data'];
return res;
}
}

View File

@ -0,0 +1,4 @@
library media;
export './controller.dart';
export './view.dart';

214
lib/pages/media/view.dart Normal file
View File

@ -0,0 +1,214 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/media/index.dart';
class MediaPage extends StatefulWidget {
const MediaPage({super.key});
@override
State<MediaPage> createState() => _MediaPageState();
}
class _MediaPageState extends State<MediaPage>
with AutomaticKeepAliveClientMixin {
final MediaController _mediaController = Get.put(MediaController());
Future? _futureBuilderFuture;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_futureBuilderFuture = _mediaController.queryFavFolder();
}
@override
Widget build(BuildContext context) {
Color primary = Theme.of(context).colorScheme.primary;
return Scaffold(
appBar: AppBar(toolbarHeight: 30),
body: Column(
children: [
ListTile(
leading: null,
title: Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(
'媒体库',
style: TextStyle(
fontSize: Theme.of(context).textTheme.titleLarge!.fontSize,
),
),
),
),
for (var i in _mediaController.list) ...[
ListTile(
onTap: () => i['onTap'](),
leading: Padding(
padding: const EdgeInsets.only(left: 15),
child: Icon(
i['icon'],
color: primary,
),
),
minLeadingWidth: 0,
title: Text(i['title']),
),
],
favFolder()
],
),
);
}
Widget favFolder() {
return Column(
children: [
Divider(
height: 35,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
ListTile(
onTap: () {},
leading: null,
dense: true,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Obx(
() => Text.rich(
TextSpan(
children: [
TextSpan(
text: '收藏夹 ',
style: TextStyle(
fontSize:
Theme.of(context).textTheme.titleMedium!.fontSize,
),
),
if (_mediaController.favFolderData.value.count != null)
TextSpan(
text: _mediaController.favFolderData.value.count
.toString(),
style: TextStyle(
fontSize:
Theme.of(context).textTheme.titleSmall!.fontSize,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
),
trailing: Padding(
padding: const EdgeInsets.only(right: 10),
child: Text(
'查看全部',
style: TextStyle(
fontSize: Theme.of(context).textTheme.labelMedium!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
),
),
// const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: 170,
child: ListView(
scrollDirection: Axis.horizontal,
children: [
const SizedBox(width: 20),
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data;
if (data['status']) {
return Obx(() => Row(
children: [
if (_mediaController.favFolderData.value.list !=
null) ...[
for (FavFolderItemData i in _mediaController
.favFolderData.value.list!) ...[
FavFolderItem(item: i),
const SizedBox(width: 14)
]
]
],
));
} else {
return SizedBox(
height: 160,
child: Center(child: Text(data['msg'])),
);
}
} else {
// 骨架屏
return SizedBox();
}
}),
// for (var i in [1, 2, 3]) ...[const FavFolderItem()],
const SizedBox(width: 10)
],
),
),
],
);
}
}
class FavFolderItem extends StatelessWidget {
FavFolderItem({super.key, this.item});
FavFolderItemData? item;
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
Container(
width: 110 * 16 / 9,
height: 110,
margin: const EdgeInsets.only(bottom: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(14),
color: Theme.of(context).colorScheme.onInverseSurface,
boxShadow: [
BoxShadow(
color: Theme.of(context).colorScheme.onInverseSurface,
offset: const Offset(4, -12), // 阴影与容器的距离
blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。
spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。
),
],
),
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
return NetworkImgLayer(
src: item!.cover,
width: box.maxWidth,
height: box.maxHeight,
);
},
),
),
Text(
' ${item!.title}',
overflow: TextOverflow.fade,
maxLines: 1,
),
Text(
'${item!.mediaCount}条视频',
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(color: Theme.of(context).colorScheme.outline),
)
],
);
}
}

View File

@ -36,7 +36,7 @@ class MineController extends GetxController {
user.put(UserBoxKey.userMid, res['data'].mid);
user.put(UserBoxKey.userLogin, true);
userLogin.value = true;
Get.find<MainController>().readuUserFace();
// Get.find<MainController>().readuUserFace();
} else {
resetUserInfo();
}
@ -64,6 +64,6 @@ class MineController extends GetxController {
await user.delete(UserBoxKey.userMid);
await user.delete(UserBoxKey.userLogin);
userLogin.value = false;
Get.find<MainController>().resetLast();
// Get.find<MainController>().resetLast();
}
}

View File

@ -19,6 +19,11 @@ class _MinePageState extends State<MinePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
scrolledUnderElevation: 0,
elevation: 0,
toolbarHeight: kTextTabBarHeight + 20,
backgroundColor: Colors.transparent,
title: null,
actions: [
IconButton(
@ -54,6 +59,7 @@ class _MinePageState extends State<MinePage> {
height: constraint.maxHeight,
child: Column(
children: [
const SizedBox(height: 10),
FutureBuilder(
future: _mineController.queryUserInfo(),
builder: (context, snapshot) {
@ -69,45 +75,6 @@ class _MinePageState extends State<MinePage> {
},
),
const SizedBox(height: 20),
Padding(
padding: const EdgeInsets.only(left: 12, right: 12),
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
height: constraints.maxWidth / 4 * 0.8,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 4,
childAspectRatio: 1.25,
children: <Widget>[
ActionItem(
icon:
const Icon(CupertinoIcons.cloud_download),
onTap: () => {},
text: '离线缓存',
),
ActionItem(
icon: const Icon(CupertinoIcons.time),
onTap: () => {},
text: '历史记录',
),
ActionItem(
icon: const Icon(CupertinoIcons.star),
onTap: () => {},
text: '我的收藏',
),
ActionItem(
icon: const Icon(CupertinoIcons.film),
onTap: () => {},
text: '稍后再看',
),
],
),
);
},
),
),
],
),
),

View File

@ -1,10 +1,12 @@
import 'package:get/get.dart';
import 'package:pilipala/pages/fav/index.dart';
import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/hot/index.dart';
import 'package:pilipala/pages/preview/index.dart';
import 'package:pilipala/pages/video/detail/index.dart';
import 'package:pilipala/pages/webview/index.dart';
import 'package:pilipala/pages/setting/index.dart';
import 'package:pilipala/pages/media/index.dart';
class Routes {
static final List<GetPage> getPages = [
@ -20,5 +22,9 @@ class Routes {
GetPage(name: '/webview', page: () => const WebviewPage()),
// 设置
GetPage(name: '/setting', page: () => const SettingPage()),
//
GetPage(name: '/media', page: () => const MediaPage()),
//
GetPage(name: '/fav', page: () => const FavPage()),
];
}