diff --git a/lib/http/user.dart b/lib/http/user.dart index 2d220524..26af3258 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -101,7 +101,10 @@ class UserHttp { contentType: Headers.formUrlEncodedContentType, )); if (res.data['code'] == 0) { - return {'status': true, 'data': res.data['data']}; + return { + 'status': true, + 'data': FavFolderItemData.fromJson(res.data['data']) + }; } else { return {'status': false, 'msg': res.data['message']}; } diff --git a/lib/models/user/fav_detail.dart b/lib/models/user/fav_detail.dart index f9de284f..a95817de 100644 --- a/lib/models/user/fav_detail.dart +++ b/lib/models/user/fav_detail.dart @@ -1,4 +1,5 @@ import 'package:PiliPalaX/models/model_owner.dart'; +import 'package:PiliPalaX/models/user/fav_folder.dart'; class FavDetailData { FavDetailData({ @@ -7,12 +8,13 @@ class FavDetailData { this.hasMore, }); - Map? info; + FavFolderItemData? info; List? medias; bool? hasMore; FavDetailData.fromJson(Map json) { - info = json['info']; + info = + json['info'] == null ? null : FavFolderItemData.fromJson(json['info']); medias = json['medias'] != null ? json['medias'] .map((e) => FavDetailItemData.fromJson(e)) diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 3ce9272b..5801848a 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -1,6 +1,6 @@ import 'package:PiliPalaX/common/skeleton/video_card_h.dart'; +import 'package:PiliPalaX/common/widgets/refresh_indicator.dart'; import 'package:PiliPalaX/http/loading_state.dart'; -import 'package:PiliPalaX/models/user/fav_folder.dart'; import 'package:PiliPalaX/pages/fav_search/view.dart'; import 'package:PiliPalaX/utils/utils.dart'; import 'package:easy_debounce/easy_throttle.dart'; @@ -57,17 +57,7 @@ class _FavPageState extends State { List list = _favController.loadingState.value is Success ? (_favController.loadingState.value as Success).response : []; - list.insert( - list.isNotEmpty ? 1 : 0, - FavFolderItemData( - id: data['id'], - fid: data['fid'], - attr: data['attr'], - title: data['title'], - favState: data['fav_state'], - mediaCount: data['media_count'], - ), - ); + list.insert(list.isNotEmpty ? 1 : 0, data); _favController.loadingState.value = LoadingState.success(list); } @@ -95,14 +85,19 @@ class _FavPageState extends State { const SizedBox(width: 6), ], ), - body: CustomScrollView( - controller: _favController.scrollController, - physics: const AlwaysScrollableScrollPhysics(), - slivers: [ - Obx( - () => _buildBody(_favController.loadingState.value), - ), - ], + body: refreshIndicator( + onRefresh: () async { + await _favController.onRefresh(); + }, + child: CustomScrollView( + controller: _favController.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + Obx( + () => _buildBody(_favController.loadingState.value), + ), + ], + ), ), ); } @@ -124,30 +119,34 @@ class _FavPageState extends State { ), ), Success() => (loadingState.response as List?)?.isNotEmpty == true - ? SliverGrid( - gridDelegate: SliverGridDelegateWithExtentAndRatio( - mainAxisSpacing: StyleString.cardSpace, - crossAxisSpacing: StyleString.safeSpace, - maxCrossAxisExtent: Grid.maxRowWidth * 2, - childAspectRatio: StyleString.aspectRatio * 2.4, - mainAxisExtent: 0), - delegate: SliverChildBuilderDelegate( - childCount: loadingState.response.length, - (BuildContext context, int index) { - String heroTag = - Utils.makeHeroTag(loadingState.response[index].fid); - return FavItem( - heroTag: heroTag, - favFolderItem: loadingState.response[index], - onTap: () { - Get.toNamed( - '/favDetail', - arguments: loadingState.response[index], - parameters: { - 'heroTag': heroTag, - 'mediaId': loadingState.response[index].id.toString(), - }, - )?.then((res) { + ? SliverPadding( + padding: EdgeInsets.only( + bottom: 80 + MediaQuery.paddingOf(context).bottom), + sliver: SliverGrid( + gridDelegate: SliverGridDelegateWithExtentAndRatio( + mainAxisSpacing: StyleString.cardSpace, + crossAxisSpacing: StyleString.safeSpace, + maxCrossAxisExtent: Grid.maxRowWidth * 2, + childAspectRatio: StyleString.aspectRatio * 2.4, + mainAxisExtent: 0), + delegate: SliverChildBuilderDelegate( + childCount: loadingState.response.length, + (BuildContext context, int index) { + String heroTag = + Utils.makeHeroTag(loadingState.response[index].fid); + return FavItem( + heroTag: heroTag, + favFolderItem: loadingState.response[index], + onTap: () async { + dynamic res = await Get.toNamed( + '/favDetail', + arguments: loadingState.response[index], + parameters: { + 'heroTag': heroTag, + 'mediaId': + loadingState.response[index].id.toString(), + }, + ); if (res == true) { List list = (_favController.loadingState.value as Success) @@ -155,11 +154,15 @@ class _FavPageState extends State { list.removeAt(index); _favController.loadingState.value = LoadingState.success(list); + } else { + Future.delayed(const Duration(milliseconds: 150), () { + _favController.onRefresh(); + }); } - }); - }, - ); - }, + }, + ); + }, + ), ), ) : HttpError( diff --git a/lib/pages/fav/widgets/item.dart b/lib/pages/fav/widgets/item.dart index e4fb75a6..22f8f39b 100644 --- a/lib/pages/fav/widgets/item.dart +++ b/lib/pages/fav/widgets/item.dart @@ -1,3 +1,5 @@ +import 'package:PiliPalaX/models/user/fav_folder.dart'; +import 'package:PiliPalaX/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:PiliPalaX/common/constants.dart'; import 'package:PiliPalaX/common/widgets/network_img_layer.dart'; @@ -58,7 +60,7 @@ class FavItem extends StatelessWidget { } class VideoContent extends StatelessWidget { - final dynamic favFolderItem; + final FavFolderItemData favFolderItem; const VideoContent({super.key, required this.favFolderItem}); @override @@ -70,17 +72,16 @@ class VideoContent extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - favFolderItem.title, + favFolderItem.title ?? '', textAlign: TextAlign.start, style: const TextStyle( fontWeight: FontWeight.w400, letterSpacing: 0.3, ), ), - if (favFolderItem.intro.isNotEmpty) + if (favFolderItem.intro?.isNotEmpty == true) Text( - favFolderItem.intro, - textAlign: TextAlign.start, + favFolderItem.intro!, style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, color: Theme.of(context).colorScheme.outline, @@ -88,7 +89,14 @@ class VideoContent extends StatelessWidget { ), Text( '${favFolderItem.mediaCount}个内容', - textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + const Spacer(), + Text( + Utils.isPublicText(favFolderItem.attr ?? 0), style: TextStyle( fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, color: Theme.of(context).colorScheme.outline, diff --git a/lib/pages/fav_detail/controller.dart b/lib/pages/fav_detail/controller.dart index 20e0e276..a5ee9e0a 100644 --- a/lib/pages/fav_detail/controller.dart +++ b/lib/pages/fav_detail/controller.dart @@ -1,5 +1,6 @@ import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/user.dart'; +import 'package:PiliPalaX/models/user/fav_folder.dart'; import 'package:PiliPalaX/pages/common/common_controller.dart'; import 'package:PiliPalaX/utils/extension.dart'; import 'package:PiliPalaX/utils/storage.dart'; @@ -8,15 +9,10 @@ import 'package:get/get.dart'; import 'package:PiliPalaX/http/video.dart'; class FavDetailController extends CommonController { - // FavFolderItemData? item; + Rx item = FavFolderItemData().obs; int? mediaId; late String heroTag; RxString loadingText = '加载中...'.obs; - int mediaCount = 0; - RxString title = ''.obs; - RxString cover = ''.obs; - RxString name = ''.obs; - late int attr; RxBool isOwner = false.obs; @override @@ -48,12 +44,8 @@ class FavDetailController extends CommonController { @override bool customHandleResponse(Success response) { if (currentPage == 1) { - title.value = response.response.info['title']; - cover.value = response.response.info['cover']; - name.value = response.response.info['upper']['name']; - mediaCount = response.response.info['media_count']; - attr = response.response.info['attr']; - isOwner.value = response.response.info['mid'] == + item.value = response.response.info; + isOwner.value = response.response.info.mid == GStorage.userInfo.get('userInfoCache')?.mid; } List currentList = loadingState.value is Success @@ -63,7 +55,7 @@ class FavDetailController extends CommonController { ? response.response.medias : currentList + response.response.medias; loadingState.value = LoadingState.success(dataList); - if (dataList.length >= mediaCount) { + if (dataList.length >= response.response.info.mediaCount) { loadingText.value = '没有更多了'; } return true; diff --git a/lib/pages/fav_detail/view.dart b/lib/pages/fav_detail/view.dart index 27043b1c..304a7e18 100644 --- a/lib/pages/fav_detail/view.dart +++ b/lib/pages/fav_detail/view.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:PiliPalaX/http/loading_state.dart'; import 'package:PiliPalaX/http/user.dart'; +import 'package:PiliPalaX/models/user/fav_folder.dart'; import 'package:PiliPalaX/pages/fav_search/view.dart' show SearchType; import 'package:PiliPalaX/utils/utils.dart'; import 'package:easy_debounce/easy_throttle.dart'; @@ -86,11 +87,11 @@ class _FavDetailPageState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - _favDetailController.title.value, + _favDetailController.item.value.title ?? '', style: Theme.of(context).textTheme.titleMedium, ), Text( - '共${_favDetailController.mediaCount}条视频', + '共${_favDetailController.item.value.mediaCount}条视频', style: Theme.of(context).textTheme.labelMedium, ) ], @@ -125,11 +126,16 @@ class _FavDetailPageState extends State { Get.toNamed( '/createFav', parameters: {'mediaId': mediaId}, - ); + )?.then((res) { + if (res is FavFolderItemData) { + _favDetailController.item.value = res; + } + }); }, child: Text('编辑信息'), ), - if (!Utils.isDefault(_favDetailController.attr)) + if (!Utils.isDefault( + _favDetailController.item.value.attr ?? 0)) PopupMenuItem( onTap: () { UserHttp.deleteFolder(mediaIds: [mediaId]) @@ -152,13 +158,6 @@ class _FavDetailPageState extends State { ], flexibleSpace: FlexibleSpaceBar( background: Container( - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide( - // color: Theme.of(context).dividerColor.withOpacity(0.2), - // ), - // ), - // ), padding: EdgeInsets.only( top: kTextTabBarHeight + MediaQuery.of(context).padding.top + @@ -167,65 +166,84 @@ class _FavDetailPageState extends State { right: 20), child: SizedBox( height: 110, - child: Row( - // mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Obx( - () => Hero( + child: Obx( + () => Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Hero( tag: _favDetailController.heroTag, child: NetworkImgLayer( width: 180, height: 110, - src: _favDetailController.cover.value, + src: _favDetailController.item.value.cover, ), ), - ), - const SizedBox(width: 14), - Obx( - () => Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const SizedBox(height: 4), - Text( - _favDetailController.title.value, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .titleMedium! - .fontSize, - fontWeight: FontWeight.bold), - ), - const SizedBox(height: 4), - Text( - _favDetailController.name.value, - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: - Theme.of(context).colorScheme.outline), - ), - const Spacer(), - Text( - '共${_favDetailController.mediaCount}条视频', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize, - color: - Theme.of(context).colorScheme.outline), - ), - const SizedBox(height: 20), - ], + const SizedBox(width: 14), + Expanded( + child: SizedBox( + height: 110, + child: Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: 4), + Text( + _favDetailController.item.value.title ?? '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .titleMedium! + .fontSize, + fontWeight: FontWeight.bold), + ), + if (_favDetailController + .item.value.intro?.isNotEmpty == + true) + Text( + _favDetailController.item.value.intro ?? '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + const SizedBox(height: 4), + Text( + _favDetailController.item.value.upper?.name ?? + '', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + const Spacer(), + if (_favDetailController.item.value.attr != + null) + Text( + '共${_favDetailController.item.value.mediaCount}条视频 · ${Utils.isPublicText(_favDetailController.item.value.attr ?? 0)}', + style: TextStyle( + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize, + color: Theme.of(context) + .colorScheme + .outline), + ), + ], + ), ), ), - ), - ], + ], + ), ), ), ), diff --git a/lib/pages/fav_search/view.dart b/lib/pages/fav_search/view.dart index 9506ecdf..403d42db 100644 --- a/lib/pages/fav_search/view.dart +++ b/lib/pages/fav_search/view.dart @@ -83,10 +83,12 @@ class _FavSearchPageState extends State { Widget _buildBody(LoadingState loadingState) { return switch (loadingState) { - Loading() => loadingWidget, + Loading() => errorWidget(), Success() => (loadingState.response as List?)?.isNotEmpty == true ? _favSearchCtr.searchType == SearchType.fav - ? ListView.builder( + ? ListView.separated( + separatorBuilder: (context, index) => + const SizedBox(height: 10), controller: _favSearchCtr.scrollController, itemCount: loadingState.response.length + 1, itemBuilder: (context, index) { diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart index b940e4b0..8389ca70 100644 --- a/lib/pages/media/view.dart +++ b/lib/pages/media/view.dart @@ -134,7 +134,12 @@ class _MediaPageState extends State color: Theme.of(context).dividerColor.withOpacity(0.1), ), ListTile( - onTap: () => Get.toNamed('/fav'), + onTap: () async { + await Get.toNamed('/fav'); + Future.delayed(const Duration(milliseconds: 150), () { + mediaController.onRefresh(); + }); + }, leading: null, dense: true, title: Padding( @@ -212,7 +217,12 @@ class _MediaPageState extends State .withOpacity(0.5); }), ), - onPressed: () => Get.toNamed('/fav'), + onPressed: () async { + await Get.toNamed('/fav'); + Future.delayed(const Duration(milliseconds: 150), () { + mediaController.onRefresh(); + }); + }, icon: Icon( Icons.arrow_forward_ios, size: 18, @@ -222,9 +232,25 @@ class _MediaPageState extends State ), ); } else { + String heroTag = + Utils.makeHeroTag(loadingState.response.list[index].fid); return FavFolderItem( + heroTag: heroTag, item: loadingState.response.list[index], index: index, + onTap: () async { + await Get.toNamed( + '/favDetail', + arguments: loadingState.response.list[index], + parameters: { + 'mediaId': loadingState.response.list[index].id.toString(), + 'heroTag': heroTag, + }, + ); + Future.delayed(const Duration(milliseconds: 150), () { + mediaController.onRefresh(); + }); + }, ); } }, @@ -242,19 +268,25 @@ class _MediaPageState extends State } class FavFolderItem extends StatelessWidget { - const FavFolderItem({super.key, this.item, this.index}); + const FavFolderItem({ + super.key, + this.item, + this.index, + required this.onTap, + required this.heroTag, + }); + final FavFolderItemData? item; final int? index; + final GestureTapCallback onTap; + final String heroTag; + @override Widget build(BuildContext context) { - String heroTag = Utils.makeHeroTag(item!.fid); - return Container( margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14), child: GestureDetector( - onTap: () => Get.toNamed('/favDetail', - arguments: item, - parameters: {'mediaId': item!.id.toString(), 'heroTag': heroTag}), + onTap: onTap, child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -302,7 +334,7 @@ class FavFolderItem extends StatelessWidget { maxLines: 1, ), Text( - ' 共${item!.mediaCount}条视频', + ' 共${item!.mediaCount}条视频 · ${Utils.isPublicText(item?.attr ?? 0)}', style: Theme.of(context) .textTheme .labelSmall! diff --git a/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart b/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart index a1531e75..bff1c4d0 100644 --- a/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart +++ b/lib/pages/member/new/content/member_contribute/content/favorite/member_favorite.dart @@ -119,25 +119,28 @@ class _MemberFavoriteState extends State children: [ ...(data.mediaListResponse?.list as List).map( (item1) => ListTile( - onTap: () { + onTap: () async { if (item1.state == 1) { // invalid return; } if (item1.type == 0) { - Get.toNamed( + dynamic res = await Get.toNamed( '/favDetail', parameters: { 'mediaId': item1.id.toString(), 'heroTag': widget.heroTag ?? '', }, - )?.then((res) { - if (res == true) { - _controller.first.value.mediaListResponse?.list - ?.remove(item1); - _controller.first.refresh(); - } - }); + ); + if (res == true) { + _controller.first.value.mediaListResponse?.list + ?.remove(item1); + _controller.first.refresh(); + } else { + Future.delayed(const Duration(milliseconds: 100), () { + _controller.onRefresh(); + }); + } } else if (item1.type == 21) { PiliScheme.routePush(Uri.parse(item1.link ?? '')); } else if (item1.type == 11) { @@ -213,7 +216,7 @@ class _MemberFavoriteState extends State ), subtitle: Text( item1.type == 0 - ? '${item1.mediaCount}个内容 · ${Utils.isPublic(item1.attr ?? 0) ? '公开' : '私密'}' + ? '${item1.mediaCount}个内容 · ${Utils.isPublicText(item1.attr ?? 0)}' : item1.type == 11 ? '${item1.mediaCount}个内容 · ${item1.upper?.name}' : item1.type == 21 diff --git a/lib/pages/video/detail/introduction/widgets/fav_panel.dart b/lib/pages/video/detail/introduction/widgets/fav_panel.dart index 1affea80..316e670a 100644 --- a/lib/pages/video/detail/introduction/widgets/fav_panel.dart +++ b/lib/pages/video/detail/introduction/widgets/fav_panel.dart @@ -64,17 +64,7 @@ class _FavPanelState extends State { if (data != null) { (widget.ctr?.favFolderData.value as FavFolderData?) ?.list - ?.insert( - 1, - FavFolderItemData( - id: data['id'], - fid: data['fid'], - attr: data['attr'], - title: data['title'], - favState: data['fav_state'], - mediaCount: data['media_count'], - ), - ); + ?.insert(1, data); widget.ctr?.favFolderData.refresh(); } }); @@ -125,7 +115,7 @@ class _FavPanelState extends State { title: Text(widget.ctr!.favFolderData.value .list![index].title!), subtitle: Text( - '${widget.ctr!.favFolderData.value.list![index].mediaCount}个内容 . ${Utils.isPublic(widget.ctr!.favFolderData.value.list![index].attr) ? '公开' : '私密'}', + '${widget.ctr!.favFolderData.value.list![index].mediaCount}个内容 . ${Utils.isPublicText(widget.ctr!.favFolderData.value.list![index].attr)}', ), trailing: Transform.scale( scale: 0.9, diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index cd9d9247..d1ce5394 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -29,6 +29,10 @@ class Utils { return (attr & 2) == 0; } + static String isPublicText(int attr) { + return isPublic(attr) ? '公开' : '私密'; + } + static bool isPublic(int attr) { return (attr & 1) == 0; }