From 6d55321699f5f667c85e10db72196bfcccebb6a3 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Thu, 7 Aug 2025 11:19:59 +0800 Subject: [PATCH] feat: member cheese feat: fav pugv Signed-off-by: bggRGjQaUbCoE --- lib/http/api.dart | 8 ++ lib/http/fav.dart | 53 ++++++++ lib/http/member.dart | 21 +++ lib/models/common/fav_type.dart | 4 +- lib/models/common/member/tab_type.dart | 12 +- lib/models_new/pgc/pgc_info_model/brief.dart | 6 +- .../pgc/pgc_info_model/user_status.dart | 3 + lib/models_new/space/space_cheese/data.dart | 19 +++ lib/models_new/space/space_cheese/item.dart | 48 +++++++ lib/models_new/space/space_cheese/page.dart | 16 +++ lib/pages/fav/cheese/controller.dart | 40 ++++++ lib/pages/fav/cheese/view.dart | 87 ++++++++++++ lib/pages/fav/view.dart | 4 + .../later/widgets/video_card_h_later.dart | 3 +- lib/pages/member/controller.dart | 9 +- lib/pages/member/view.dart | 5 + lib/pages/member_audio copy/controller.dart | 38 ++++++ lib/pages/member_cheese/controller.dart | 31 +++++ lib/pages/member_cheese/view.dart | 80 +++++++++++ lib/pages/member_cheese/widgets/item.dart | 124 ++++++++++++++++++ .../video/introduction/pgc/controller.dart | 14 ++ lib/pages/video/introduction/pgc/view.dart | 26 +++- 22 files changed, 634 insertions(+), 17 deletions(-) create mode 100644 lib/models_new/space/space_cheese/data.dart create mode 100644 lib/models_new/space/space_cheese/item.dart create mode 100644 lib/models_new/space/space_cheese/page.dart create mode 100644 lib/pages/fav/cheese/controller.dart create mode 100644 lib/pages/fav/cheese/view.dart create mode 100644 lib/pages/member_audio copy/controller.dart create mode 100644 lib/pages/member_cheese/controller.dart create mode 100644 lib/pages/member_cheese/view.dart create mode 100644 lib/pages/member_cheese/widgets/item.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 0184a5c1..ef772141 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -850,6 +850,12 @@ class Api { static const String dynReserve = '/x/dynamic/feed/reserve/click'; + static const String favPugv = '/pugv/app/web/favorite/page'; + + static const String addFavPugv = '/pugv/app/web/favorite/add'; + + static const String delFavPugv = '/pugv/app/web/favorite/del'; + static const String favTopicList = '/x/topic/web/fav/list'; static const String addFavTopic = '/x/topic/fav/sub/add'; @@ -920,6 +926,8 @@ class Api { static const String spaceAudio = '/audio/music-service/web/song/upper'; + static const String spaceCheese = '/pugv/app/web/season/page'; + static const String dynMention = '/x/polymer/web-dynamic/v1/mention/search'; static const String createVote = '/x/vote/create'; diff --git a/lib/http/fav.dart b/lib/http/fav.dart index d762fe14..ea00dc32 100644 --- a/lib/http/fav.dart +++ b/lib/http/fav.dart @@ -10,6 +10,7 @@ import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; import 'package:PiliPlus/models_new/fav/fav_note/list.dart'; import 'package:PiliPlus/models_new/fav/fav_pgc/data.dart'; import 'package:PiliPlus/models_new/fav/fav_topic/data.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/data.dart'; import 'package:PiliPlus/models_new/space/space_fav/data.dart'; import 'package:PiliPlus/models_new/sub/sub_detail/data.dart'; import 'package:PiliPlus/utils/accounts.dart'; @@ -142,6 +143,58 @@ class FavHttp { } } + static Future> favPugv({ + required int mid, + required int page, + }) async { + var res = await Request().get( + Api.favPugv, + queryParameters: { + 'mid': mid, + 'ps': 20, + 'pn': page, + 'web_location': 333.1387, + }, + ); + if (res.data['code'] == 0) { + return Success(SpaceCheeseData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } + + static Future addFavPugv(seasonId) async { + var res = await Request().post( + Api.addFavPugv, + data: { + 'season_id': seasonId, + 'csrf': Accounts.main.csrf, + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return {'status': true}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + + static Future delFavPugv(seasonId) async { + var res = await Request().post( + Api.delFavPugv, + data: { + 'season_id': seasonId, + 'csrf': Accounts.main.csrf, + }, + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return {'status': true}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } + static Future> favTopic({ required int page, }) async { diff --git a/lib/http/member.dart b/lib/http/member.dart index db00de76..805ae4b6 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -20,6 +20,7 @@ import 'package:PiliPlus/models_new/space/space/data.dart'; import 'package:PiliPlus/models_new/space/space_archive/data.dart'; import 'package:PiliPlus/models_new/space/space_article/data.dart'; import 'package:PiliPlus/models_new/space/space_audio/data.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/data.dart'; import 'package:PiliPlus/models_new/space/space_opus/data.dart'; import 'package:PiliPlus/models_new/space/space_season_series/item.dart'; import 'package:PiliPlus/models_new/upower_rank/data.dart'; @@ -186,6 +187,26 @@ class MemberHttp { } } + static Future> spaceCheese({ + required int page, + required mid, + }) async { + var res = await Request().get( + Api.spaceCheese, + queryParameters: { + 'pn': page, + 'ps': 30, + 'mid': mid, + 'web_location': 333.1387, + }, + ); + if (res.data['code'] == 0) { + return Success(SpaceCheeseData.fromJson(res.data['data'])); + } else { + return Error(res.data['message']); + } + } + static Future spaceStory({ required mid, required aid, diff --git a/lib/models/common/fav_type.dart b/lib/models/common/fav_type.dart index 01ed454c..e1719b6b 100644 --- a/lib/models/common/fav_type.dart +++ b/lib/models/common/fav_type.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/pages/fav/article/view.dart'; +import 'package:PiliPlus/pages/fav/cheese/view.dart'; import 'package:PiliPlus/pages/fav/note/view.dart'; import 'package:PiliPlus/pages/fav/pgc/view.dart'; import 'package:PiliPlus/pages/fav/topic/view.dart'; @@ -11,7 +12,8 @@ enum FavTabType { cinema('追剧', FavPgcPage(type: 2)), article('专栏', FavArticlePage()), note('笔记', FavNotePage()), - topic('话题', FavTopicPage()); + topic('话题', FavTopicPage()), + cheese('课堂', FavCheesePage()); final String title; final Widget page; diff --git a/lib/models/common/member/tab_type.dart b/lib/models/common/member/tab_type.dart index a174362d..8cbb2e35 100644 --- a/lib/models/common/member/tab_type.dart +++ b/lib/models/common/member/tab_type.dart @@ -4,7 +4,17 @@ enum MemberTabType { dynamic('动态'), contribute('投稿'), favorite('收藏'), - bangumi('番剧'); + bangumi('番剧'), + cheese('课堂'); + + static bool contains(String type) { + for (var e in MemberTabType.values) { + if (e.name == type) { + return true; + } + } + return false; + } final String title; const MemberTabType(this.title); diff --git a/lib/models_new/pgc/pgc_info_model/brief.dart b/lib/models_new/pgc/pgc_info_model/brief.dart index 06b067a3..a36a1e0f 100644 --- a/lib/models_new/pgc/pgc_info_model/brief.dart +++ b/lib/models_new/pgc/pgc_info_model/brief.dart @@ -11,16 +11,16 @@ class Brief { } class Img { - num? aspectRatio; + num aspectRatio; String? url; Img({ - this.aspectRatio, + required this.aspectRatio, this.url, }); factory Img.fromJson(Map json) => Img( - aspectRatio: json['aspect_ratio'], + aspectRatio: json['aspect_ratio'] ?? 1, url: json['url'] as String?, ); } diff --git a/lib/models_new/pgc/pgc_info_model/user_status.dart b/lib/models_new/pgc/pgc_info_model/user_status.dart index 8c68d562..488066fc 100644 --- a/lib/models_new/pgc/pgc_info_model/user_status.dart +++ b/lib/models_new/pgc/pgc_info_model/user_status.dart @@ -10,6 +10,7 @@ class UserStatus { int? payPackPaid; int? sponsor; UserProgress? progress; + int? favored; UserStatus({ this.areaLimit, @@ -21,6 +22,7 @@ class UserStatus { this.payPackPaid, this.sponsor, this.progress, + this.favored, }); factory UserStatus.fromJson(Map json) => UserStatus( @@ -35,5 +37,6 @@ class UserStatus { progress: json['progress'] == null ? null : UserProgress.fromJson(json['progress']), + favored: json['favored'] as int?, ); } diff --git a/lib/models_new/space/space_cheese/data.dart b/lib/models_new/space/space_cheese/data.dart new file mode 100644 index 00000000..19950558 --- /dev/null +++ b/lib/models_new/space/space_cheese/data.dart @@ -0,0 +1,19 @@ +import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/page.dart'; + +class SpaceCheeseData { + List? items; + SpaceCheesePage? page; + + SpaceCheeseData({this.items, this.page}); + + factory SpaceCheeseData.fromJson(Map json) => + SpaceCheeseData( + items: (json['items'] as List?) + ?.map((e) => SpaceCheeseItem.fromJson(e as Map)) + .toList(), + page: json['page'] == null + ? null + : SpaceCheesePage.fromJson(json['page'] as Map), + ); +} diff --git a/lib/models_new/space/space_cheese/item.dart b/lib/models_new/space/space_cheese/item.dart new file mode 100644 index 00000000..6e279c27 --- /dev/null +++ b/lib/models_new/space/space_cheese/item.dart @@ -0,0 +1,48 @@ +class SpaceCheeseItem { + bool? cooperated; + String? cooperationMark; + String? cover; + int? epCount; + String? link; + List? marks; + int? page; + int? play; + int? seasonId; + String? status; + String? subtitle; + String? title; + String? ctime; + + SpaceCheeseItem({ + this.cooperated, + this.cooperationMark, + this.cover, + this.epCount, + this.link, + this.marks, + this.page, + this.play, + this.seasonId, + this.status, + this.subtitle, + this.title, + this.ctime, + }); + + factory SpaceCheeseItem.fromJson(Map json) => + SpaceCheeseItem( + cooperated: json['cooperated'] as bool?, + cooperationMark: json['cooperation_mark'] as String?, + cover: json['cover'] as String?, + epCount: json['ep_count'] as int?, + link: json['link'] as String?, + marks: (json['marks'] as List?)?.cast(), + page: json['page'] as int?, + play: json['play'] as int?, + seasonId: json['season_id'] as int?, + status: json['status'] as String?, + subtitle: json['subtitle'] as String?, + title: json['title'] as String?, + ctime: json['ctime'] as String?, + ); +} diff --git a/lib/models_new/space/space_cheese/page.dart b/lib/models_new/space/space_cheese/page.dart new file mode 100644 index 00000000..a2dd06ed --- /dev/null +++ b/lib/models_new/space/space_cheese/page.dart @@ -0,0 +1,16 @@ +class SpaceCheesePage { + bool? next; + int? num; + int? size; + int? total; + + SpaceCheesePage({this.next, this.num, this.size, this.total}); + + factory SpaceCheesePage.fromJson(Map json) => + SpaceCheesePage( + next: json['next'] as bool?, + num: json['num'] as int?, + size: json['size'] as int?, + total: json['total'] as int?, + ); +} diff --git a/lib/pages/fav/cheese/controller.dart b/lib/pages/fav/cheese/controller.dart new file mode 100644 index 00000000..87c2e4f0 --- /dev/null +++ b/lib/pages/fav/cheese/controller.dart @@ -0,0 +1,40 @@ +import 'package:PiliPlus/http/fav.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/data.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; +import 'package:PiliPlus/utils/accounts.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; + +class FavCheeseController + extends CommonListController { + final mid = Accounts.main.mid; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + List? getDataList(SpaceCheeseData response) { + isEnd = response.page?.next == false; + return response.items; + } + + @override + Future> customGetData() => + FavHttp.favPugv(mid: mid, page: page); + + Future onRemove(int index, int? sid) async { + var res = await FavHttp.delFavPugv(sid); + if (res['status']) { + loadingState + ..value.data!.removeAt(index) + ..refresh(); + SmartDialog.showToast('已取消收藏'); + } else { + SmartDialog.showToast(res['msg']); + } + } +} diff --git a/lib/pages/fav/cheese/view.dart b/lib/pages/fav/cheese/view.dart new file mode 100644 index 00000000..28a2baf7 --- /dev/null +++ b/lib/pages/fav/cheese/view.dart @@ -0,0 +1,87 @@ +import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; +import 'package:PiliPlus/pages/fav/cheese/controller.dart'; +import 'package:PiliPlus/pages/member_cheese/widgets/item.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class FavCheesePage extends StatefulWidget { + const FavCheesePage({super.key}); + + @override + State createState() => _FavCheesePageState(); +} + +class _FavCheesePageState extends State + with AutomaticKeepAliveClientMixin { + final FavCheeseController _controller = Get.put(FavCheeseController()); + + @override + bool get wantKeepAlive => true; + + @override + Widget build(BuildContext context) { + super.build(context); + final ThemeData theme = Theme.of(context); + return refreshIndicator( + onRefresh: _controller.onRefresh, + child: CustomScrollView( + controller: _controller.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: 7, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx( + () => _buildBody(theme, _controller.loadingState.value), + ), + ), + ], + ), + ); + } + + Widget _buildBody( + ThemeData theme, + LoadingState?> loadingState, + ) { + return switch (loadingState) { + Loading() => linearLoading, + Success(:var response) => + response?.isNotEmpty == true + ? SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + final item = response[index]; + return MemberCheeseItem( + item: item, + onRemove: () => showConfirmDialog( + context: context, + title: '确定取消收藏该课堂?', + onConfirm: () => + _controller.onRemove(index, item.seasonId), + ), + ); + }, + childCount: response!.length, + ), + ) + : HttpError(onReload: _controller.onReload), + Error(:var errMsg) => HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ), + }; + } +} diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 0e42f905..71d1f50b 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/models/common/fav_type.dart'; import 'package:PiliPlus/models_new/fav/fav_folder/list.dart'; import 'package:PiliPlus/pages/fav/article/controller.dart'; +import 'package:PiliPlus/pages/fav/cheese/controller.dart'; import 'package:PiliPlus/pages/fav/topic/controller.dart'; import 'package:PiliPlus/pages/fav/video/controller.dart'; import 'package:PiliPlus/pages/fav_folder_sort/view.dart'; @@ -141,6 +142,9 @@ class _FavPageState extends State with SingleTickerProviderStateMixin { .animToTop(); case FavTabType.topic: Get.find().scrollController.animToTop(); + case FavTabType.cheese: + Get.find().scrollController + .animToTop(); default: } } diff --git a/lib/pages/later/widgets/video_card_h_later.dart b/lib/pages/later/widgets/video_card_h_later.dart index c5c6cb71..eefc008e 100644 --- a/lib/pages/later/widgets/video_card_h_later.dart +++ b/lib/pages/later/widgets/video_card_h_later.dart @@ -182,6 +182,7 @@ class VideoCardHLater extends StatelessWidget { ); return Expanded( child: Stack( + clipBehavior: Clip.none, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -249,7 +250,7 @@ class VideoCardHLater extends StatelessWidget { ), Positioned( right: 0, - bottom: 0, + bottom: -8, child: iconButton( tooltip: '移除', context: context, diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index b77935ed..c1d54698 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -41,13 +41,6 @@ class MemberController extends CommonDataController late List tabs; TabController? tabController; RxInt contributeInitialIndex = 0.obs; - late final implTabs = const [ - 'home', - 'dynamic', - 'contribute', - 'favorite', - 'bangumi', - ]; bool? hasSeasonOrSeries; @@ -83,7 +76,7 @@ class MemberController extends CommonDataController data.series?.item?.isNotEmpty == true) { hasSeasonOrSeries = true; } - tab2?.retainWhere((item) => implTabs.contains(item.param)); + tab2?.retainWhere((item) => MemberTabType.contains(item.param!)); if (tab2?.isNotEmpty == true) { if (data.hasItem != true && tab2!.first.param == 'home') { // remove empty home tab diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 96a05d2b..106b6faf 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -9,6 +9,7 @@ import 'package:PiliPlus/pages/exp_log/view.dart'; import 'package:PiliPlus/pages/login_log/view.dart'; import 'package:PiliPlus/pages/member/controller.dart'; import 'package:PiliPlus/pages/member/widget/user_info_card.dart'; +import 'package:PiliPlus/pages/member_cheese/view.dart'; import 'package:PiliPlus/pages/member_contribute/view.dart'; import 'package:PiliPlus/pages/member_dynamics/view.dart'; import 'package:PiliPlus/pages/member_favorite/view.dart'; @@ -325,6 +326,10 @@ class _MemberPageState extends State { heroTag: _heroTag, mid: _mid, ), + 'cheese' => MemberCheese( + heroTag: _heroTag, + mid: _mid, + ), _ => Center(child: Text(item.title ?? '')), }; }).toList(), diff --git a/lib/pages/member_audio copy/controller.dart b/lib/pages/member_audio copy/controller.dart new file mode 100644 index 00000000..affdb660 --- /dev/null +++ b/lib/pages/member_audio copy/controller.dart @@ -0,0 +1,38 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models_new/space/space_audio/data.dart'; +import 'package:PiliPlus/models_new/space/space_audio/item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; + +class MemberAudioController + extends CommonListController { + MemberAudioController(this.mid); + + final int mid; + int? totalSize; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + void checkIsEnd(int length) { + if (totalSize != null && length >= totalSize!) { + isEnd = true; + } + } + + @override + List? getDataList(SpaceAudioData response) { + totalSize = response.totalSize; + return response.items; + } + + @override + Future> customGetData() => MemberHttp.spaceAudio( + page: page, + mid: mid, + ); +} diff --git a/lib/pages/member_cheese/controller.dart b/lib/pages/member_cheese/controller.dart new file mode 100644 index 00000000..0a07a1d9 --- /dev/null +++ b/lib/pages/member_cheese/controller.dart @@ -0,0 +1,31 @@ +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/http/member.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/data.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; + +class MemberCheeseController + extends CommonListController { + MemberCheeseController(this.mid); + + final int mid; + + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + List? getDataList(SpaceCheeseData response) { + isEnd = response.page?.next == false; + return response.items; + } + + @override + Future> customGetData() => + MemberHttp.spaceCheese( + page: page, + mid: mid, + ); +} diff --git a/lib/pages/member_cheese/view.dart b/lib/pages/member_cheese/view.dart new file mode 100644 index 00000000..92884e1a --- /dev/null +++ b/lib/pages/member_cheese/view.dart @@ -0,0 +1,80 @@ +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; +import 'package:PiliPlus/pages/member_cheese/controller.dart'; +import 'package:PiliPlus/pages/member_cheese/widgets/item.dart'; +import 'package:PiliPlus/utils/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class MemberCheese extends StatefulWidget { + const MemberCheese({ + super.key, + required this.heroTag, + required this.mid, + }); + + final String? heroTag; + final int mid; + + @override + State createState() => _MemberCheeseState(); +} + +class _MemberCheeseState extends State + with AutomaticKeepAliveClientMixin { + late final _controller = Get.put( + MemberCheeseController(widget.mid), + tag: widget.heroTag, + ); + + @override + Widget build(BuildContext context) { + super.build(context); + return refreshIndicator( + onRefresh: _controller.onRefresh, + child: CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + top: 7, + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_controller.loadingState.value)), + ), + ], + ), + ); + } + + @override + bool get wantKeepAlive => true; + + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => linearLoading, + Success(:var response) => + response?.isNotEmpty == true + ? SliverGrid( + gridDelegate: Grid.videoCardHDelegate(context), + delegate: SliverChildBuilderDelegate( + (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return MemberCheeseItem(item: response[index]); + }, + childCount: response!.length, + ), + ) + : HttpError(onReload: _controller.onReload), + Error(:var errMsg) => HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ), + }; + } +} diff --git a/lib/pages/member_cheese/widgets/item.dart b/lib/pages/member_cheese/widgets/item.dart new file mode 100644 index 00000000..24796ee3 --- /dev/null +++ b/lib/pages/member_cheese/widgets/item.dart @@ -0,0 +1,124 @@ +import 'package:PiliPlus/common/constants.dart'; +import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/button/icon_button.dart'; +import 'package:PiliPlus/common/widgets/image/image_save.dart'; +import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; +import 'package:PiliPlus/models_new/space/space_cheese/item.dart'; +import 'package:PiliPlus/utils/date_util.dart'; +import 'package:PiliPlus/utils/page_utils.dart'; +import 'package:flutter/material.dart'; + +class MemberCheeseItem extends StatelessWidget { + const MemberCheeseItem({ + super.key, + required this.item, + this.onRemove, + }); + + final SpaceCheeseItem item; + final VoidCallback? onRemove; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + Widget child = Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title!, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (item.status != null) ...[ + const SizedBox(height: 6), + Text( + item.status!, + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ], + if (item.ctime != null) ...[ + const Spacer(), + Text( + '收藏于${DateUtil.dateFormat(int.parse(item.ctime!))}', + style: TextStyle( + fontSize: 12, + color: theme.colorScheme.outline, + ), + ), + ], + ], + ); + if (onRemove != null) { + child = Stack( + clipBehavior: Clip.none, + children: [ + child, + Positioned( + right: 0, + bottom: -8, + child: iconButton( + tooltip: '移除', + context: context, + onPressed: onRemove, + icon: Icons.clear, + iconColor: theme.colorScheme.outline, + bgColor: Colors.transparent, + ), + ), + ], + ); + } + return Material( + type: MaterialType.transparency, + child: InkWell( + onTap: () => PageUtils.viewPugv(seasonId: item.seasonId), + onLongPress: () => + imageSaveDialog(title: item.title, cover: item.cover), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace, + vertical: 5, + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + Widget child = NetworkImgLayer( + radius: 4, + src: item.cover, + width: boxConstraints.maxWidth, + height: boxConstraints.maxHeight, + ); + if (item.marks?.isNotEmpty == true) { + return Stack( + clipBehavior: Clip.none, + children: [ + child, + PBadge( + right: 6, + top: 6, + text: item.marks!.join('|'), + ), + ], + ); + } + return child; + }, + ), + ), + const SizedBox(width: 10), + Expanded(child: child), + ], + ), + ), + ), + ); + } +} diff --git a/lib/pages/video/introduction/pgc/controller.dart b/lib/pages/video/introduction/pgc/controller.dart index adf944d8..0e9f9bfe 100644 --- a/lib/pages/video/introduction/pgc/controller.dart +++ b/lib/pages/video/introduction/pgc/controller.dart @@ -5,6 +5,7 @@ import 'package:PiliPlus/grpc/bilibili/app/viewunite/pgcanymodel.pb.dart' show ViewPgcAny; import 'package:PiliPlus/grpc/view.dart'; import 'package:PiliPlus/http/constants.dart'; +import 'package:PiliPlus/http/fav.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/video/source_type.dart'; @@ -51,6 +52,7 @@ class PgcIntroController extends CommonIntroController { late final RxBool isFollowed = false.obs; late final RxInt followStatus = (-1).obs; + late final RxBool isFav = (pgcItem.userStatus?.favored == 1).obs; @override void onInit() { @@ -479,4 +481,16 @@ class PgcIntroController extends CommonIntroController { artist: pgcItem.title, ); } + + Future onFavPugv(bool isFav) async { + final res = isFav + ? await FavHttp.delFavPugv(seasonId) + : await FavHttp.addFavPugv(seasonId); + if (res['status']) { + this.isFav.value = !isFav; + SmartDialog.showToast('${isFav ? '取消' : ''}收藏成功'); + } else { + SmartDialog.showToast(res['msg']); + } + } } diff --git a/lib/pages/video/introduction/pgc/view.dart b/lib/pages/video/introduction/pgc/view.dart index ff8be74c..8fce6d09 100644 --- a/lib/pages/video/introduction/pgc/view.dart +++ b/lib/pages/video/introduction/pgc/view.dart @@ -3,6 +3,7 @@ import 'dart:math'; import 'package:PiliPlus/common/constants.dart'; import 'package:PiliPlus/common/widgets/badge.dart'; +import 'package:PiliPlus/common/widgets/button/icon_button.dart'; import 'package:PiliPlus/common/widgets/dialog/dialog.dart'; import 'package:PiliPlus/common/widgets/image/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/stat/stat.dart'; @@ -80,7 +81,7 @@ class _PgcIntroPageState extends State crossAxisAlignment: CrossAxisAlignment.start, spacing: 10, children: [ - _buildCover(isLandscape, item), + _buildCover(theme, isLandscape, item), Expanded(child: _buildInfoPanel(isLandscape, theme, item)), ], ), @@ -142,6 +143,7 @@ class _PgcIntroPageState extends State radius: 0, src: e.url, width: imgWidth, + height: imgWidth * e.aspectRatio, ), ); }).toList(), @@ -153,7 +155,7 @@ class _PgcIntroPageState extends State return null; } - Widget _buildCover(bool isLandscape, PgcInfoModel item) { + Widget _buildCover(ThemeData theme, bool isLandscape, PgcInfoModel item) { return Stack( clipBehavior: Clip.none, children: [ @@ -183,6 +185,24 @@ class _PgcIntroPageState extends State bottom: 6, left: null, ), + if (!pgcIntroController.isPgc) + Positioned( + right: 6, + bottom: 6, + child: Obx(() { + final isFav = pgcIntroController.isFav.value; + return iconButton( + context: context, + size: 28, + iconSize: 26, + tooltip: '${isFav ? '取消' : ''}收藏', + onPressed: () => pgcIntroController.onFavPugv(isFav), + icon: isFav ? Icons.star_rounded : Icons.star_border_rounded, + bgColor: isFav ? null : theme.colorScheme.onInverseSurface, + iconColor: isFav ? null : theme.colorScheme.onSurfaceVariant, + ); + }), + ), ], ); } @@ -374,7 +394,7 @@ class _PgcIntroPageState extends State item.upInfo!.avatar!, item.upInfo!.uname!, ), - const SizedBox(height: 8), + const SizedBox(height: 6), ], Text( item.title!,