From 522ce60750afbd8716b4789e13ed7f9a21bb7550 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 11 Jul 2023 15:17:30 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=85=B3=E6=B3=A8=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 13 ++++ lib/http/follow.dart | 27 ++++++++ lib/models/follow/result.dart | 52 ++++++++++++++ lib/pages/dynamics/widgets/up_panel.dart | 15 +++- lib/pages/follow/controller.dart | 36 ++++++++++ lib/pages/follow/index.dart | 4 ++ lib/pages/follow/view.dart | 84 +++++++++++++++++++++++ lib/pages/follow/widgets/follow_item.dart | 23 +++++++ lib/router/app_pages.dart | 5 +- 9 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 lib/http/follow.dart create mode 100644 lib/models/follow/result.dart create mode 100644 lib/pages/follow/controller.dart create mode 100644 lib/pages/follow/index.dart create mode 100644 lib/pages/follow/view.dart create mode 100644 lib/pages/follow/widgets/follow_item.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 6c65c56d..9218c37d 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -167,4 +167,17 @@ class Api { // 番剧/剧集明细 static const String bangumiInfo = '/pgc/view/web/season'; + + // 全部关注的up + // vmid 用户id pn 页码 ps 每页个数,最大50 order: desc + // order_type 排序规则 最近访问传空,最常访问传 attention + static const String followings = '/x/relation/followings'; + + // 指定分类的关注 + // https://api.bilibili.com/x/relation/tag?mid=17340771&tagid=-10&pn=1&ps=20 + static const String tagFollowings = '/x/relation/tag'; + + // 关注分类 + // https://api.bilibili.com/x/relation/tags + static const String followingsClass = '/x/relation/tags'; } diff --git a/lib/http/follow.dart b/lib/http/follow.dart new file mode 100644 index 00000000..f50762c4 --- /dev/null +++ b/lib/http/follow.dart @@ -0,0 +1,27 @@ +import 'package:pilipala/http/index.dart'; +import 'package:pilipala/models/follow/result.dart'; + +class FollowHttp { + static Future followings( + {int? vmid, int? pn, int? ps, String? orderType}) async { + var res = await Request().get(Api.followings, data: { + 'vmid': vmid, + 'pn': pn, + 'ps': ps, + 'order': 'desc', + 'order_type': orderType, + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': FollowDataModel.fromJson(res.data['data']) + }; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/models/follow/result.dart b/lib/models/follow/result.dart new file mode 100644 index 00000000..c6656165 --- /dev/null +++ b/lib/models/follow/result.dart @@ -0,0 +1,52 @@ +class FollowDataModel { + FollowDataModel({ + this.total, + this.list, + }); + + int? total; + List? list; + + FollowDataModel.fromJson(Map json) { + total = json['total']; + list = json['list'] + .map((e) => FollowItemModel.fromJson(e)) + .toList(); + } +} + +class FollowItemModel { + FollowItemModel({ + this.mid, + this.attribute, + this.mtime, + this.tag, + this.special, + this.uname, + this.face, + this.sign, + this.officialVerify, + }); + + int? mid; + int? attribute; + int? mtime; + List? tag; + int? special; + String? uname; + String? face; + String? sign; + Map? officialVerify; + + FollowItemModel.fromJson(Map json) { + mid = json['mid']; + attribute = json['attribute']; + mtime = json['mtime']; + tag = json['tag']; + special = json['special']; + uname = json['uname']; + face = json['face']; + sign = json['sign'] == '' ? '还没有签名' : json['sign']; + officialVerify = json['official_verify']; + } +} diff --git a/lib/pages/dynamics/widgets/up_panel.dart b/lib/pages/dynamics/widgets/up_panel.dart index 60317fd0..07604676 100644 --- a/lib/pages/dynamics/widgets/up_panel.dart +++ b/lib/pages/dynamics/widgets/up_panel.dart @@ -62,11 +62,22 @@ class _UpPanelState extends State { top: 5, left: 12, right: 12, bottom: 5), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ - Text( + children: [ + const Text( '最常访问', style: TextStyle(fontWeight: FontWeight.bold), ), + SizedBox( + height: 26, + child: TextButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => Get.toNamed('/follow'), + child: + const Text('查看全部', style: TextStyle(fontSize: 12)), + ), + ) ], ), ), diff --git a/lib/pages/follow/controller.dart b/lib/pages/follow/controller.dart new file mode 100644 index 00000000..49fbdbd4 --- /dev/null +++ b/lib/pages/follow/controller.dart @@ -0,0 +1,36 @@ +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/http/follow.dart'; +import 'package:pilipala/models/follow/result.dart'; +import 'package:pilipala/utils/storage.dart'; + +class FollowController extends GetxController { + Box user = GStrorage.user; + int pn = 1; + int total = 0; + RxList followList = [FollowItemModel()].obs; + + Future queryFollowings(type) async { + if (type == 'init') { + pn = 1; + } + var res = await FollowHttp.followings( + vmid: user.get(UserBoxKey.userMid), + pn: pn, + ps: 20, + orderType: 'attention', + ); + if (res['status']) { + if (type == 'init') { + followList.value = res['data'].list; + total = res['data'].total; + } else if (type == 'onRefresh') { + followList.insertAll(0, res['data'].list); + } else if (type == 'onLoad') { + followList.addAll(res['data'].list); + } + pn += 1; + } + return res; + } +} diff --git a/lib/pages/follow/index.dart b/lib/pages/follow/index.dart new file mode 100644 index 00000000..9113361d --- /dev/null +++ b/lib/pages/follow/index.dart @@ -0,0 +1,4 @@ +library following; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/follow/view.dart b/lib/pages/follow/view.dart new file mode 100644 index 00000000..0f64d9da --- /dev/null +++ b/lib/pages/follow/view.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; +import 'package:pilipala/models/follow/result.dart'; + +import 'controller.dart'; +import 'widgets/follow_item.dart'; + +class FollowPage extends StatefulWidget { + const FollowPage({super.key}); + + @override + State createState() => _FollowPageState(); +} + +class _FollowPageState extends State { + final FollowController _followController = Get.put(FollowController()); + final ScrollController scrollController = ScrollController(); + Future? _futureBuilderFuture; + bool _isLoadingMore = false; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _followController.queryFollowings('init'); + scrollController.addListener( + () async { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + if (!_isLoadingMore) { + _isLoadingMore = true; + await _followController.queryFollowings('onLoad'); + _isLoadingMore = false; + } + } + }, + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + elevation: 0, + scrolledUnderElevation: 0, + centerTitle: false, + title: const Text('关注的用户'), + ), + body: RefreshIndicator( + onRefresh: () async => + await _followController.queryFollowings('init'), + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + var data = snapshot.data; + if (data['status']) { + List list = _followController.followList; + return Obx( + () => list.length == 1 + ? SizedBox() + : ListView.builder( + controller: scrollController, + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + return followItem(item: list[index]); + }, + ), + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => _followController.queryFollowings('init'), + ); + } + } else { + // 骨架屏 + return SizedBox(); + } + }, + )), + ); + } +} diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart new file mode 100644 index 00000000..37a7b761 --- /dev/null +++ b/lib/pages/follow/widgets/follow_item.dart @@ -0,0 +1,23 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; + +Widget followItem({item}) { + return ListTile( + onTap: () {}, + leading: NetworkImgLayer( + width: 38, + height: 38, + type: 'avatar', + src: item.face, + ), + title: Text(item.uname), + subtitle: Text( + item.sign, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + dense: true, + trailing: const SizedBox(width: 6), + ); +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 11775c2b..04182f42 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -3,6 +3,7 @@ import 'package:pilipala/pages/dynamics/deatil/index.dart'; import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/fav/index.dart'; import 'package:pilipala/pages/favDetail/index.dart'; +import 'package:pilipala/pages/follow/index.dart'; import 'package:pilipala/pages/history/index.dart'; import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/hot/index.dart'; @@ -52,6 +53,8 @@ class Routes { // 动态 GetPage(name: '/dynamics', page: () => const DynamicsPage()), // 动态详情 - GetPage(name: '/dynamicDetail', page: () => const DynamicDetailPage()) + GetPage(name: '/dynamicDetail', page: () => const DynamicDetailPage()), + // 关注 + GetPage(name: '/follow', page: () => const FollowPage()), ]; }