From 3aee691d00b9db885b122f8c126cf4296527362e Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 21 Apr 2023 16:07:34 +0800 Subject: [PATCH] =?UTF-8?q?mod:=20=E6=8E=A5=E5=8F=A3=E6=95=B4=E7=90=86?= =?UTF-8?q?=E3=80=81=E5=A2=9E=E5=8A=A0up=E7=B2=89=E4=B8=9D=E8=AF=B7?= =?UTF-8?q?=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/http_error.dart | 33 ++++++++++ lib/http/api.dart | 6 +- lib/http/user.dart | 13 ++++ lib/http/video.dart | 66 +++++++++++++++++-- lib/pages/home/controller.dart | 31 ++++----- lib/pages/hot/controller.dart | 34 +++++----- .../video/detail/introduction/controller.dart | 45 +++++++++---- lib/pages/video/detail/introduction/view.dart | 56 +++++++++------- .../video/detail/related/controller.dart | 22 +------ lib/pages/video/detail/related/view.dart | 12 ++-- 10 files changed, 210 insertions(+), 108 deletions(-) create mode 100644 lib/common/widgets/http_error.dart create mode 100644 lib/http/user.dart diff --git a/lib/common/widgets/http_error.dart b/lib/common/widgets/http_error.dart new file mode 100644 index 00000000..b3aa348d --- /dev/null +++ b/lib/common/widgets/http_error.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class HttpError extends StatelessWidget { + HttpError({required this.errMsg, required this.fn, super.key}); + + String errMsg = ''; + final Function()? fn; + + @override + Widget build(BuildContext context) { + return SliverToBoxAdapter( + child: SizedBox( + height: 150, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + errMsg, + style: Theme.of(context).textTheme.titleMedium, + ), + const SizedBox(height: 10), + ElevatedButton( + onPressed: () { + fn!(); + }, + child: const Text('点击重试')) + ], + ), + ), + ); + } +} diff --git a/lib/http/api.dart b/lib/http/api.dart index 4f088015..4c378780 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -5,8 +5,12 @@ class Api { static const String hotList = '/x/web-interface/popular'; // 视频详情 // 竖屏 https://api.bilibili.com/x/web-interface/view?aid=527403921 - static const String videoDetail = '/x/web-interface/view'; + static const String videoIntro = '/x/web-interface/view'; // 视频详情页 相关视频 static const String relatedList = '/x/web-interface/archive/related'; + + // 用户(被)关注数、投稿数 + // https://api.bilibili.com/x/relation/stat?vmid=697166795 + static const String userStat = '/x/relation/stat'; } diff --git a/lib/http/user.dart b/lib/http/user.dart new file mode 100644 index 00000000..327f0ba5 --- /dev/null +++ b/lib/http/user.dart @@ -0,0 +1,13 @@ +import 'package:pilipala/http/api.dart'; +import 'package:pilipala/http/init.dart'; + +class UserHttp { + static Future userStat(mid) async { + var res = await Request().get(Api.userStat, data: {'vmid': mid}); + if (res.data['code'] == 0) { + return {'status': true, 'data': res.data['data']}; + } else { + return {'status': false}; + } + } +} diff --git a/lib/http/video.dart b/lib/http/video.dart index 9e023051..b263f3ba 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -1,11 +1,58 @@ 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/model_rec_video_item.dart'; import 'package:pilipala/models/video_detail_res.dart'; +/// res.data['code'] == 0 请求正常返回结果 +/// res.data['data'] 为结果 +/// 返回{'status': bool, 'data': List} +/// view层根据 status 判断渲染逻辑 class VideoHttp { + // 首页推荐视频 + static Future rcmdVideoList(data) async { + var res = await Request().get( + Api.recommendList, + data: { + 'feed_version': 'V3', + 'ps': data['ps'], + 'fresh_idx': data['fresh_idx'] + }, + ); + if (res.data['code'] == 0) { + List list = []; + for (var i in res.data['data']['item']) { + list.add(RecVideoItemModel.fromJson(i)); + } + return {'status': true, 'data': list}; + } else { + return {'status': false, 'data': []}; + } + } + + // 最热视频 + static Future hotVideoList(data) async { + var res = await Request().get( + Api.hotList, + data: { + 'pn': data['pn'], + 'ps': data['ps'], + }, + ); + if (res.data['code'] == 0) { + List list = []; + for (var i in res.data['data']['list']) { + list.add(HotVideoItemModel.fromJson(i)); + } + return {'status': true, 'data': list}; + } else { + return {'status': false, 'data': []}; + } + } + // 视频信息 标题、简介 - static Future videoDetail(data) async { - var res = await Request().get(Api.videoDetail, data: data); + static Future videoIntro(aid) async { + var res = await Request().get(Api.videoIntro, data: {'aid': aid}); VideoDetailResponse result = VideoDetailResponse.fromJson(res.data); if (result.code == 0) { return {'status': true, 'data': result.data!}; @@ -25,8 +72,17 @@ class VideoHttp { } } - static Future videoRecommend(data) async { - var res = await Request().get(Api.relatedList, data: data); - return res; + // 相关视频 + static Future relatedVideoList(aid) async { + var res = await Request().get(Api.relatedList, data: {'aid': aid}); + if (res.data['code'] == 0) { + List list = []; + for (var i in res.data['data']) { + list.add(HotVideoItemModel.fromJson(i)); + } + return {'status': true, 'data': list}; + } else { + return {'status': true, 'data': []}; + } } } diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index 20f166e6..809eda17 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -1,7 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; -import 'package:pilipala/http/api.dart'; -import 'package:pilipala/http/init.dart'; +import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/model_rec_video_item.dart'; class HomeController extends GetxController { @@ -21,22 +20,20 @@ class HomeController extends GetxController { // 获取推荐 Future queryRcmdFeed(type) async { - var res = await Request().get( - Api.recommendList, - data: {'feed_version': "V3", 'ps': count, 'fresh_idx': _currentPage}, - ); - List list = []; - for (var i in res.data['data']['item']) { - list.add(RecVideoItemModel.fromJson(i)); + var res = await VideoHttp.rcmdVideoList({ + 'ps': count, + 'fresh_idx': _currentPage, + }); + if (res['status']) { + if (type == 'init') { + videoList.value = res['data']; + } else if (type == 'onRefresh') { + videoList.insertAll(0, res['data']); + } else if (type == 'onLoad') { + videoList.addAll(res['data']); + } + _currentPage += 1; } - if (type == 'init') { - videoList.value = list; - } else if (type == 'onRefresh') { - videoList.insertAll(0, list); - } else if (type == 'onLoad') { - videoList.addAll(list); - } - _currentPage += 1; isLoadingMore = false; } diff --git a/lib/pages/hot/controller.dart b/lib/pages/hot/controller.dart index ced43a7b..3690b9e9 100644 --- a/lib/pages/hot/controller.dart +++ b/lib/pages/hot/controller.dart @@ -1,8 +1,6 @@ -import 'package:flutter/animation.dart'; -import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; -import 'package:pilipala/http/api.dart'; -import 'package:pilipala/http/init.dart'; +import 'package:flutter/material.dart'; +import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/model_hot_video_item.dart'; class HotController extends GetxController { @@ -21,22 +19,20 @@ class HotController extends GetxController { // 获取推荐 Future queryHotFeed(type) async { - var res = await Request().get( - Api.hotList, - data: {'pn': _currentPage, 'ps': _count}, - ); - List list = []; - for (var i in res.data['data']['list']) { - list.add(HotVideoItemModel.fromJson(i)); + var res = await VideoHttp.hotVideoList({ + 'pn': _currentPage, + 'ps': _count, + }); + if (res['status']) { + if (type == 'init') { + videoList.value = res['data']; + } else if (type == 'onRefresh') { + videoList.insertAll(0, res['data']); + } else if (type == 'onLoad') { + videoList.addAll(res['data']); + } + _currentPage += 1; } - if (type == 'init') { - videoList.value = list; - } else if (type == 'onRefresh') { - videoList.insertAll(0, list); - } else if (type == 'onLoad') { - videoList.addAll(list); - } - _currentPage += 1; isLoadingMore = false; } diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index e098880d..d3740d05 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -1,6 +1,6 @@ import 'package:get/get.dart'; -import 'package:pilipala/http/api.dart'; -import 'package:pilipala/http/init.dart'; +import 'package:pilipala/http/user.dart'; +import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/controller.dart'; @@ -20,6 +20,12 @@ class VideoIntroController extends GetxController { // 视频详情 请求返回 Rx videoDetail = VideoDetailData().obs; + // 请求返回的信息 + String responseMsg = '请求异常'; + + // up主粉丝数 + Map userStat = {'follower': '-'}; + @override void onInit() { super.onInit(); @@ -36,17 +42,28 @@ class VideoIntroController extends GetxController { } } - Future queryVideoDetail() async { - var res = await Request().get(Api.videoDetail, data: { - 'aid': aid, - }); - VideoDetailResponse result = VideoDetailResponse.fromJson(res.data); - videoDetail.value = result.data!; - Get.find().tabs.value = [ - '简介', - '评论 ${result.data!.stat!.reply}' - ]; - // await Future.delayed(const Duration(seconds: 3)); - return true; + // 获取视频简介 + Future queryVideoIntro() async { + var result = await VideoHttp.videoIntro(aid); + if (result['status']) { + videoDetail.value = result['data']!; + Get.find().tabs.value = [ + '简介', + '评论 ${result['data']!.stat!.reply}' + ]; + } else { + responseMsg = result['msg']; + } + // 获取到粉丝数再返回 + await queryUserStat(); + return result; + } + + // 获取up主粉丝数 + Future queryUserStat() async { + var result = await UserHttp.userStat(videoDetail.value.owner!.mid); + if (result['status']) { + userStat = result['data']; + } } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 41e624d4..de89e948 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/pages/video/detail/widgets/expandable_section.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart'; @@ -43,27 +44,29 @@ class _VideoIntroPanelState extends State @override Widget build(BuildContext context) { return FutureBuilder( - future: videoIntroController.queryVideoDetail(), + future: videoIntroController.queryVideoIntro(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data) { + if (snapshot.data['status']) { // 请求成功 // return _buildView(context, false, videoDetail); - return VideoInfo(loadingStatus: false, videoDetail: videoDetail); + return VideoInfo( + loadingStatus: false, + videoDetail: videoDetail, + videoIntroController: videoIntroController); } else { // 请求错误 - return Center( - child: IconButton( - icon: const Icon(Icons.refresh), - onPressed: () { - setState(() {}); - }, - ), + return HttpError( + errMsg: snapshot.data['msg'], + fn: () => setState(() {}), ); } } else { // return _buildView(context, true, videoDetail); - return VideoInfo(loadingStatus: true, videoDetail: videoDetail); + return VideoInfo( + loadingStatus: true, + videoDetail: videoDetail, + videoIntroController: videoIntroController); } }, ); @@ -90,8 +93,13 @@ class _VideoIntroPanelState extends State class VideoInfo extends StatefulWidget { bool loadingStatus = false; VideoDetailData? videoDetail; + VideoIntroController? videoIntroController; - VideoInfo({Key? key, required this.loadingStatus, this.videoDetail}) + VideoInfo( + {Key? key, + required this.loadingStatus, + this.videoDetail, + this.videoIntroController}) : super(key: key); @override @@ -150,19 +158,17 @@ class _VideoInfoState extends State with TickerProviderStateMixin { ? widget.videoDetail!.owner!.name : videoItem['owner'].name), const SizedBox(height: 2), - // Text.rich( - // TextSpan( - // style: TextStyle( - // color: Theme.of(context) - // .colorScheme - // .outline, - // fontSize: 11), - // children: const [ - // TextSpan(text: '2.6万粉丝'), - // TextSpan(text: ' '), - // TextSpan(text: '2.6万粉丝'), - // ]), - // ), + Text( + widget.loadingStatus + ? '- 粉丝' + : '${Utils.numFormat(widget.videoIntroController!.userStat['follower'])}粉丝', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context).colorScheme.outline), + ) ]), const Spacer(), AnimatedOpacity( diff --git a/lib/pages/video/detail/related/controller.dart b/lib/pages/video/detail/related/controller.dart index 872f8871..ed9cad3a 100644 --- a/lib/pages/video/detail/related/controller.dart +++ b/lib/pages/video/detail/related/controller.dart @@ -1,8 +1,5 @@ -import 'dart:convert'; - import 'package:get/get.dart'; import 'package:pilipala/http/video.dart'; -import 'package:pilipala/models/model_hot_video_item.dart'; class ReleatedController extends GetxController { // 视频aid @@ -10,22 +7,5 @@ class ReleatedController extends GetxController { // 推荐视频列表 List relatedVideoList = []; - Future queryVideoRecommend() async { - try { - var res = await VideoHttp.videoRecommend({'aid': aid}); - List list = []; - try { - for (var i in res.data['data']) { - list.add(HotVideoItemModel.fromJson(i)); - } - relatedVideoList = list; - } catch (err) { - return err.toString(); - } - - return res.data['data']; - } catch (err) { - return err.toString(); - } - } + Future queryRelatedVideo() => VideoHttp.relatedVideoList(aid); } diff --git a/lib/pages/video/detail/related/view.dart b/lib/pages/video/detail/related/view.dart index 319e75d4..7eca3157 100644 --- a/lib/pages/video/detail/related/view.dart +++ b/lib/pages/video/detail/related/view.dart @@ -16,22 +16,22 @@ class _RelatedVideoPanelState extends State { @override Widget build(BuildContext context) { return FutureBuilder( - future: _releatedController.queryVideoRecommend(), + future: _releatedController.queryRelatedVideo(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data!.isNotEmpty) { + if (snapshot.data!['status']) { // 请求成功 - List videoList = _releatedController.relatedVideoList; + // List videoList = _releatedController.relatedVideoList; return SliverList( delegate: SliverChildBuilderDelegate((context, index) { - if (index == videoList.length) { + if (index == snapshot.data['data'].length) { return SizedBox(height: MediaQuery.of(context).padding.bottom); } else { return VideoCardH( - videoItem: videoList[index], + videoItem: snapshot.data['data'][index], ); } - }, childCount: videoList.length + 1)); + }, childCount: snapshot.data['data'].length + 1)); } else { // 请求错误 return const Center(