diff --git a/lib/http/api.dart b/lib/http/api.dart index 1f0c78c0..0a3a54a2 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -872,4 +872,6 @@ class Api { static const String topicPubSearch = '${HttpString.appBaseUrl}/x/topic/pub/search'; + + static const String upowerRank = '/x/upower/up/member/rank/v2'; } diff --git a/lib/http/member.dart b/lib/http/member.dart index f2c88cb4..11302419 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -20,6 +20,7 @@ import 'package:PiliPlus/models/space_archive/data.dart'; import 'package:PiliPlus/models/space_article/data.dart'; import 'package:PiliPlus/models/space_fav/space_fav.dart'; import 'package:PiliPlus/models/space_opus/data.dart'; +import 'package:PiliPlus/models/upower_rank/data.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; import 'package:PiliPlus/utils/wbi_sign.dart'; @@ -854,4 +855,28 @@ class MemberHttp { return Error(res.data['message']); } } + + static Future> upowerRank({ + required upMid, + required int page, + int? privilegeType, + }) async { + var res = await Request().get( + Api.upowerRank, + queryParameters: { + 'up_mid': upMid, + 'pn': page, + 'ps': 100, + if (privilegeType != null) 'privilege_type': privilegeType, + 'mobi_app': 'web', + 'web_location': 333.1196, + if (Accounts.main.isLogin) 'csrf': Accounts.main.csrf, + }, + ); + if (res.data['code'] == 0) { + return Success(UpowerRankData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/models/upower_rank/data.dart b/lib/models/upower_rank/data.dart new file mode 100644 index 00000000..4a454605 --- /dev/null +++ b/lib/models/upower_rank/data.dart @@ -0,0 +1,56 @@ +import 'package:PiliPlus/models/upower_rank/level_info.dart'; +import 'package:PiliPlus/models/upower_rank/rank_info.dart'; +import 'package:PiliPlus/models/upower_rank/up_info.dart'; +import 'package:PiliPlus/models/upower_rank/user_info.dart'; + +class UpowerRankData { + UpInfo? upInfo; + List? rankInfo; + UserInfo? userInfo; + int? memberTotal; + int? privilegeType; + bool? isCharge; + List? tabs; + List? levelInfo; + + UpowerRankData({ + this.upInfo, + this.rankInfo, + this.userInfo, + this.memberTotal, + this.privilegeType, + this.isCharge, + this.tabs, + this.levelInfo, + }); + + factory UpowerRankData.fromJson(Map json) => UpowerRankData( + upInfo: json['up_info'] == null + ? null + : UpInfo.fromJson(json['up_info'] as Map), + rankInfo: (json['rank_info'] as List?) + ?.map((e) => UpowerRankInfo.fromJson(e as Map)) + .toList(), + userInfo: json['user_info'] == null + ? null + : UserInfo.fromJson(json['user_info'] as Map), + memberTotal: json['member_total'] as int?, + privilegeType: json['privilege_type'] as int?, + isCharge: json['is_charge'] as bool?, + tabs: (json['tabs'] as List?)?.map((e) => (e as num).toInt()).toList(), + levelInfo: (json['level_info'] as List?) + ?.map((e) => LevelInfo.fromJson(e as Map)) + .toList(), + ); + + Map toJson() => { + 'up_info': upInfo?.toJson(), + 'rank_info': rankInfo?.map((e) => e.toJson()).toList(), + 'user_info': userInfo?.toJson(), + 'member_total': memberTotal, + 'privilege_type': privilegeType, + 'is_charge': isCharge, + 'tabs': tabs, + 'level_info': levelInfo?.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/upower_rank/level_info.dart b/lib/models/upower_rank/level_info.dart new file mode 100644 index 00000000..f98db096 --- /dev/null +++ b/lib/models/upower_rank/level_info.dart @@ -0,0 +1,22 @@ +class LevelInfo { + int? privilegeType; + String? name; + int? price; + int? memberTotal; + + LevelInfo({this.privilegeType, this.name, this.price, this.memberTotal}); + + factory LevelInfo.fromJson(Map json) => LevelInfo( + privilegeType: json['privilege_type'] as int?, + name: json['name'] as String?, + price: json['price'] as int?, + memberTotal: json['member_total'] as int?, + ); + + Map toJson() => { + 'privilege_type': privilegeType, + 'name': name, + 'price': price, + 'member_total': memberTotal, + }; +} diff --git a/lib/models/upower_rank/rank_info.dart b/lib/models/upower_rank/rank_info.dart new file mode 100644 index 00000000..d5b51394 --- /dev/null +++ b/lib/models/upower_rank/rank_info.dart @@ -0,0 +1,39 @@ +class UpowerRankInfo { + int? mid; + String? nickname; + String? avatar; + int? rank; + int? day; + int? expireAt; + int? remainDays; + + UpowerRankInfo({ + this.mid, + this.nickname, + this.avatar, + this.rank, + this.day, + this.expireAt, + this.remainDays, + }); + + factory UpowerRankInfo.fromJson(Map json) => UpowerRankInfo( + mid: json['mid'] as int?, + nickname: json['nickname'] as String?, + avatar: json['avatar'] as String?, + rank: json['rank'] as int?, + day: json['day'] as int?, + expireAt: json['expire_at'] as int?, + remainDays: json['remain_days'] as int?, + ); + + Map toJson() => { + 'mid': mid, + 'nickname': nickname, + 'avatar': avatar, + 'rank': rank, + 'day': day, + 'expire_at': expireAt, + 'remain_days': remainDays, + }; +} diff --git a/lib/models/upower_rank/up_info.dart b/lib/models/upower_rank/up_info.dart new file mode 100644 index 00000000..d1c103be --- /dev/null +++ b/lib/models/upower_rank/up_info.dart @@ -0,0 +1,35 @@ +class UpInfo { + int? mid; + String? nickname; + String? avatar; + int? type; + String? title; + int? upowerState; + + UpInfo({ + this.mid, + this.nickname, + this.avatar, + this.type, + this.title, + this.upowerState, + }); + + factory UpInfo.fromJson(Map json) => UpInfo( + mid: json['mid'] as int?, + nickname: json['nickname'] as String?, + avatar: json['avatar'] as String?, + type: json['type'] as int?, + title: json['title'] as String?, + upowerState: json['upower_state'] as int?, + ); + + Map toJson() => { + 'mid': mid, + 'nickname': nickname, + 'avatar': avatar, + 'type': type, + 'title': title, + 'upower_state': upowerState, + }; +} diff --git a/lib/models/upower_rank/user_info.dart b/lib/models/upower_rank/user_info.dart new file mode 100644 index 00000000..3cda51a3 --- /dev/null +++ b/lib/models/upower_rank/user_info.dart @@ -0,0 +1,39 @@ +class UserInfo { + int? mid; + String? nickname; + String? avatar; + int? rank; + int? day; + int? expireAt; + int? remainDays; + + UserInfo({ + this.mid, + this.nickname, + this.avatar, + this.rank, + this.day, + this.expireAt, + this.remainDays, + }); + + factory UserInfo.fromJson(Map json) => UserInfo( + mid: json['mid'] as int?, + nickname: json['nickname'] as String?, + avatar: json['avatar'] as String?, + rank: json['rank'] as int?, + day: json['day'] as int?, + expireAt: json['expire_at'] as int?, + remainDays: json['remain_days'] as int?, + ); + + Map toJson() => { + 'mid': mid, + 'nickname': nickname, + 'avatar': avatar, + 'rank': rank, + 'day': day, + 'expire_at': expireAt, + 'remain_days': remainDays, + }; +} diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 5c766707..bec4da44 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -119,6 +119,22 @@ class _MemberPageState extends State { ], ), ), + PopupMenuItem( + onTap: () => Get.toNamed( + '/upowerRank', + parameters: { + 'mid': _userController.mid.toString(), + }, + ), + child: const Row( + mainAxisSize: MainAxisSize.min, + children: [ + Icon(Icons.electric_bolt, size: 19), + SizedBox(width: 10), + Text('充电排行榜'), + ], + ), + ), if (_userController.ownerMid != null && _userController.mid != _userController.ownerMid) ...[ const PopupMenuDivider(), diff --git a/lib/pages/member_upower_rank/controller.dart b/lib/pages/member_upower_rank/controller.dart new file mode 100644 index 00000000..7f7f4805 --- /dev/null +++ b/lib/pages/member_upower_rank/controller.dart @@ -0,0 +1,42 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models/upower_rank/data.dart'; +import 'package:PiliPlus/models/upower_rank/level_info.dart'; +import 'package:PiliPlus/models/upower_rank/rank_info.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:get/get.dart'; + +class UpowerRankController + extends CommonListController { + UpowerRankController(this.privilegeType); + int? privilegeType; + + final upMid = Get.parameters['mid']; + final Rx name = Rx(null); + final Rx?> tabs = Rx?>(null); + int? memberTotal; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + List? getDataList(UpowerRankData response) { + isEnd = true; + memberTotal = response.memberTotal ?? 0; + if (response.levelInfo != null && response.levelInfo!.length > 1) { + tabs.value = response.levelInfo; + } + name.value = response.upInfo!.nickname; + return response.rankInfo; + } + + @override + Future> customGetData() => MemberHttp.upowerRank( + upMid: upMid, + page: page, + privilegeType: privilegeType, + ); +} diff --git a/lib/pages/member_upower_rank/view.dart b/lib/pages/member_upower_rank/view.dart new file mode 100644 index 00000000..a55a0be7 --- /dev/null +++ b/lib/pages/member_upower_rank/view.dart @@ -0,0 +1,177 @@ +import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/common/widgets/scroll_physics.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models/common/image_type.dart'; +import 'package:PiliPlus/models/upower_rank/rank_info.dart'; +import 'package:PiliPlus/pages/member_upower_rank/controller.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class UpowerRankPage extends StatefulWidget { + const UpowerRankPage({super.key, this.privilegeType}); + + final int? privilegeType; + + @override + State createState() => _UpowerRankPageState(); +} + +class _UpowerRankPageState extends State + with AutomaticKeepAliveClientMixin { + late final _controller = Get.put( + UpowerRankController(widget.privilegeType), + tag: Utils.generateRandomString(8), + ); + + @override + Widget build(BuildContext context) { + super.build(context); + final child = refreshIndicator( + onRefresh: _controller.onRefresh, + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _bilidBody(_controller.loadingState.value)), + ), + ], + ), + ); + if (widget.privilegeType == null) { + return Scaffold( + appBar: AppBar( + title: Obx(() => _controller.name.value == null + ? const SizedBox.shrink() + : Text( + '${_controller.name.value}充电排行榜${_controller.memberTotal == 0 ? '' : '(${_controller.memberTotal})'}')), + ), + body: SafeArea( + top: false, + bottom: false, + child: Obx( + () => _controller.tabs.value != null + ? DefaultTabController( + length: _controller.tabs.value!.length, + child: Column( + children: [ + TabBar( + isScrollable: true, + tabAlignment: TabAlignment.start, + tabs: _controller.tabs.value! + .map((e) => Tab( + text: '${e.name!}(${e.memberTotal ?? 0})')) + .toList(), + ), + Expanded( + child: Material( + color: Colors.transparent, + child: tabBarView( + children: [ + child, + ..._controller.tabs.value!.sublist(1).map((e) => + UpowerRankPage( + privilegeType: e.privilegeType)) + ], + ), + ), + ), + ], + ), + ) + : child, + ), + ), + ); + } else { + return child; + } + } + + Widget _bilidBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => widget.privilegeType == null + ? const SliverToBoxAdapter(child: LinearProgressIndicator()) + : const SliverToBoxAdapter( + child: SizedBox( + height: 125, + child: Center( + child: CircularProgressIndicator(), + ), + ), + ), + Success?>(:var response) => + response?.isNotEmpty == true + ? SliverList.builder( + itemCount: response!.length, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + final item = response[index]; + return InkWell( + onTap: () => Get.toNamed('/member?mid=${item.mid}'), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16, vertical: 12), + child: Row( + children: [ + SizedBox( + width: 32, + child: Text( + (index + 1).toString(), + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 13), + ), + ), + const SizedBox(width: 6), + NetworkImgLayer( + width: 36, + height: 36, + src: item.avatar, + type: ImageType.avatar, + ), + const SizedBox(width: 12), + Text( + item.nickname!, + style: const TextStyle(fontSize: 14), + ), + const Spacer(), + Text.rich( + TextSpan( + children: [ + TextSpan( + text: item.day!.toString(), + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + ), + ), + const TextSpan( + text: ' 天', + style: TextStyle(fontSize: 13), + ), + ], + ), + ), + ], + ), + ), + ); + }, + ) + : HttpError(onReload: _controller.onReload), + Error(:var errMsg) => HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ), + }; + } + + @override + bool get wantKeepAlive => true; +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 4c74cfa4..d86f37cf 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -27,6 +27,7 @@ import 'package:PiliPlus/pages/member/view.dart'; import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_profile/view.dart'; import 'package:PiliPlus/pages/member_search/view.dart'; +import 'package:PiliPlus/pages/member_upower_rank/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/at_me/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/like_me/view.dart'; import 'package:PiliPlus/pages/msg_feed_top/reply_me/view.dart'; @@ -173,6 +174,7 @@ class Routes { CustomGetPage(name: '/dynTopic', page: () => const DynTopicPage()), CustomGetPage(name: '/articleList', page: () => const ArticleListPage()), CustomGetPage(name: '/barSetting', page: () => const BarSetPage()), + CustomGetPage(name: '/upowerRank', page: () => const UpowerRankPage()), ]; }