diff --git a/lib/http/api.dart b/lib/http/api.dart index 9218c37df..c6cfe3974 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -180,4 +180,9 @@ class Api { // 关注分类 // https://api.bilibili.com/x/relation/tags static const String followingsClass = '/x/relation/tags'; + + // 粉丝 + // vmid 用户id pn 页码 ps 每页个数,最大50 order: desc + // order_type 排序规则 最近访问传空,最常访问传 attention + static const String fans = 'https://api.bilibili.com/x/relation/fans'; } diff --git a/lib/http/fan.dart b/lib/http/fan.dart new file mode 100644 index 000000000..932cc79f2 --- /dev/null +++ b/lib/http/fan.dart @@ -0,0 +1,23 @@ +import 'package:pilipala/http/index.dart'; +import 'package:pilipala/models/fans/result.dart'; + +class FanHttp { + static Future fans({int? vmid, int? pn, int? ps, String? orderType}) async { + var res = await Request().get(Api.fans, data: { + 'vmid': vmid, + 'pn': pn, + 'ps': ps, + 'order': 'desc', + 'order_type': orderType, + }); + if (res.data['code'] == 0) { + return {'status': true, 'data': FansDataModel.fromJson(res.data['data'])}; + } else { + return { + 'status': false, + 'data': [], + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/models/fans/result.dart b/lib/models/fans/result.dart new file mode 100644 index 000000000..44345940e --- /dev/null +++ b/lib/models/fans/result.dart @@ -0,0 +1,52 @@ +class FansDataModel { + FansDataModel({ + this.total, + this.list, + }); + + int? total; + List? list; + + FansDataModel.fromJson(Map json) { + total = json['total']; + list = json['list'] + .map((e) => FansItemModel.fromJson(e)) + .toList(); + } +} + +class FansItemModel { + FansItemModel({ + 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; + + FansItemModel.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/fan/controller.dart b/lib/pages/fan/controller.dart new file mode 100644 index 000000000..6639e5ead --- /dev/null +++ b/lib/pages/fan/controller.dart @@ -0,0 +1,36 @@ +import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/http/fan.dart'; +import 'package:pilipala/models/fans/result.dart'; +import 'package:pilipala/utils/storage.dart'; + +class FansController extends GetxController { + Box user = GStrorage.user; + int pn = 1; + int total = 0; + RxList fansList = [FansItemModel()].obs; + + Future queryFans(type) async { + if (type == 'init') { + pn = 1; + } + var res = await FanHttp.fans( + vmid: user.get(UserBoxKey.userMid), + pn: pn, + ps: 20, + orderType: 'attention', + ); + if (res['status']) { + if (type == 'init') { + fansList.value = res['data'].list; + total = res['data'].total; + } else if (type == 'onRefresh') { + fansList.insertAll(0, res['data'].list); + } else if (type == 'onLoad') { + fansList.addAll(res['data'].list); + } + pn += 1; + } + return res; + } +} diff --git a/lib/pages/fan/index.dart b/lib/pages/fan/index.dart new file mode 100644 index 000000000..84ac7b450 --- /dev/null +++ b/lib/pages/fan/index.dart @@ -0,0 +1,4 @@ +library fan; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart new file mode 100644 index 000000000..26fd0a858 --- /dev/null +++ b/lib/pages/fan/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/fans/result.dart'; + +import 'controller.dart'; +import 'widgets/fan_item.dart'; + +class FansPage extends StatefulWidget { + const FansPage({super.key}); + + @override + State createState() => _FansPageState(); +} + +class _FansPageState extends State { + final FansController _fansController = Get.put(FansController()); + final ScrollController scrollController = ScrollController(); + Future? _futureBuilderFuture; + bool _isLoadingMore = false; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _fansController.queryFans('init'); + scrollController.addListener( + () async { + if (scrollController.position.pixels >= + scrollController.position.maxScrollExtent - 200) { + if (!_isLoadingMore) { + _isLoadingMore = true; + await _fansController.queryFans('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 _fansController.queryFans('init'), + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + var data = snapshot.data; + if (data['status']) { + List list = _fansController.fansList; + return Obx( + () => list.length == 1 + ? SizedBox() + : ListView.builder( + controller: scrollController, + itemCount: list.length, + itemBuilder: (BuildContext context, int index) { + return fanItem(item: list[index]); + }, + ), + ); + } else { + return HttpError( + errMsg: data['msg'], + fn: () => _fansController.queryFans('init'), + ); + } + } else { + // 骨架屏 + return SizedBox(); + } + }, + ), + ), + ); + } +} diff --git a/lib/pages/fan/widgets/fan_item.dart b/lib/pages/fan/widgets/fan_item.dart new file mode 100644 index 000000000..074a8d5eb --- /dev/null +++ b/lib/pages/fan/widgets/fan_item.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; + +Widget fanItem({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/pages/follow/view.dart b/lib/pages/follow/view.dart index 0f64d9da9..714b93008 100644 --- a/lib/pages/follow/view.dart +++ b/lib/pages/follow/view.dart @@ -44,7 +44,7 @@ class _FollowPageState extends State { elevation: 0, scrolledUnderElevation: 0, centerTitle: false, - title: const Text('关注的用户'), + title: const Text('我的关注'), ), body: RefreshIndicator( onRefresh: () async => diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 23dc4b43c..cf7657788 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -55,7 +55,8 @@ class MinePage extends StatelessWidget { builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { if (snapshot.data['status']) { - return Obx(() => userInfoBuild(mineController, context)); + return Obx( + () => userInfoBuild(mineController, context)); } else { return userInfoBuild(mineController, context); } @@ -250,7 +251,7 @@ class MinePage extends StatelessWidget { ), ), InkWell( - onTap: () {}, + onTap: () => Get.toNamed('/follow'), borderRadius: StyleString.mdRadius, child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -280,7 +281,7 @@ class MinePage extends StatelessWidget { ), ), InkWell( - onTap: () {}, + onTap: () => Get.toNamed('/fan'), borderRadius: StyleString.mdRadius, child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 04182f424..150b03884 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; import 'package:pilipala/pages/dynamics/deatil/index.dart'; import 'package:pilipala/pages/dynamics/index.dart'; +import 'package:pilipala/pages/fan/index.dart'; import 'package:pilipala/pages/fav/index.dart'; import 'package:pilipala/pages/favDetail/index.dart'; import 'package:pilipala/pages/follow/index.dart'; @@ -56,5 +57,7 @@ class Routes { GetPage(name: '/dynamicDetail', page: () => const DynamicDetailPage()), // 关注 GetPage(name: '/follow', page: () => const FollowPage()), + // 粉丝 + GetPage(name: '/fan', page: () => const FansPage()), ]; }