diff --git a/lib/models/common/video/cdn_type.dart b/lib/models/common/video/cdn_type.dart index 5bdfc4b9d..55326f9e9 100644 --- a/lib/models/common/video/cdn_type.dart +++ b/lib/models/common/video/cdn_type.dart @@ -32,6 +32,7 @@ enum CDNService { const CDNService(this.desc, [this.host]); } + // from https://rec.danmuji.org/dev/cdn-info/ // { // 'cn-ahwh-ct-': {'01': 16}, @@ -107,4 +108,4 @@ enum CDNService { // 'cn-zjhz-wasu-': {'03': 21, '04': 12}, // 'cn-sh-ix-': {'01': 13}, // 'cn-hk-eq-': {'01': 14} -// } \ No newline at end of file +// } diff --git a/lib/pages/member_favorite/controller.dart b/lib/pages/member_favorite/controller.dart index 37d2ada9b..cd5add940 100644 --- a/lib/pages/member_favorite/controller.dart +++ b/lib/pages/member_favorite/controller.dart @@ -11,21 +11,37 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class MemberFavoriteCtr - extends CommonDataController?, dynamic> { + extends CommonDataController?, List?> { MemberFavoriteCtr({ required this.mid, }); final int mid; - Rx first = SpaceFavData().obs; - Rx second = SpaceFavData().obs; + late int favPage = 2; + bool _favExpand = true; + final RxBool favEnd = true.obs; + final Rx favState = SpaceFavData().obs; - RxBool firstEnd = true.obs; - RxBool secondEnd = true.obs; + late int subPage = 2; + bool _subExpand = true; + final RxBool subEnd = true.obs; + final Rx subState = SpaceFavData().obs; - late int page = 2; - late int page1 = 2; + bool isExpand(bool isFav) { + return isFav ? _favExpand : _subExpand; + } + + void setExpand(bool isFav) { + if (isFav) { + flag = _favExpand; + _favExpand = !_favExpand; + } else { + _subExpand = !_subExpand; + } + } + + bool flag = false; @override void onInit() { @@ -35,8 +51,8 @@ class MemberFavoriteCtr @override Future onRefresh() { - page = 2; - page1 = 2; + favPage = 2; + subPage = 2; return super.onRefresh(); } @@ -47,13 +63,13 @@ class MemberFavoriteCtr ) { try { List res = response.response!; - first.value = res.first; - second.value = res[1]; + favState.value = res.first; + subState.value = res[1]; - firstEnd.value = + favEnd.value = (res.first.mediaListResponse?.count ?? -1) <= (res.first.mediaListResponse?.list?.length ?? -1); - secondEnd.value = + subEnd.value = (res[1].mediaListResponse?.count ?? -1) <= (res[1].mediaListResponse?.list?.length ?? -1); } catch (e) { @@ -63,62 +79,76 @@ class MemberFavoriteCtr return true; } - Future userfavFolder() async { - var res = await Request().get( - Api.userFavFolder, - queryParameters: { - 'pn': page, - 'ps': 20, - 'up_mid': mid, - }, - ); - if (res.data['code'] == 0) { - page++; - firstEnd.value = res.data['data']['has_more'] == false; - if (res.data['data'] != null) { - List list = - (res.data['data']?['list'] as List?) - ?.map((item) => SpaceFavItemModel.fromJson(item)) - .toList() ?? - []; - first - ..value.mediaListResponse?.list?.addAll(list) - ..refresh(); + Future userFavFolder() async { + try { + final res = await Request().get( + Api.userFavFolder, + queryParameters: { + 'pn': favPage, + 'ps': 20, + 'up_mid': mid, + }, + ); + if (res.data['code'] == 0) { + favPage++; + final data = res.data['data']; + if (data != null) { + favEnd.value = data['has_more'] == false; + final list = (data['list'] as List?) + ?.map((item) => SpaceFavItemModel.fromJson(item)) + .toList(); + if (list != null && list.isNotEmpty) { + favState + ..value.mediaListResponse!.list!.addAll(list) + ..refresh(); + } else { + favEnd.value = true; + } + } else { + favEnd.value = true; + } } else { - firstEnd.value = true; + SmartDialog.showToast(res.data['message']); } - } else { - SmartDialog.showToast(res.data['message'] ?? '账号未登录'); + } catch (e) { + SmartDialog.showToast(e.toString()); } } Future userSubFolder() async { - var res = await Request().get( - Api.userSubFolder, - queryParameters: { - 'up_mid': mid, - 'ps': 20, - 'pn': page1, - 'platform': 'web', - }, - ); - if (res.data['code'] == 0) { - page++; - secondEnd.value = res.data['data']['has_more'] == false; - if (res.data['data'] != null) { - List list = - (res.data['data']?['list'] as List?) - ?.map((item) => SpaceFavItemModel.fromJson(item)) - .toList() ?? - []; - second - ..value.mediaListResponse?.list?.addAll(list) - ..refresh(); + try { + final res = await Request().get( + Api.userSubFolder, + queryParameters: { + 'up_mid': mid, + 'ps': 20, + 'pn': subPage, + 'platform': 'web', + }, + ); + if (res.data['code'] == 0) { + subPage++; + final data = res.data['data']; + if (data != null) { + subEnd.value = data['has_more'] == false; + final list = (data['list'] as List?) + ?.map((item) => SpaceFavItemModel.fromJson(item)) + .toList(); + if (list != null && list.isNotEmpty) { + subState + ..value.mediaListResponse!.list!.addAll(list) + ..refresh(); + } else { + subEnd.value = true; + } + } else { + subEnd.value = true; + } } else { - secondEnd.value = true; + SmartDialog.showToast(res.data['message']); } - } else { - SmartDialog.showToast(res.data['message'] ?? '账号未登录'); + } catch (e) { + SmartDialog.showToast(e.toString()); } } diff --git a/lib/pages/member_favorite/view.dart b/lib/pages/member_favorite/view.dart index 8cb8cbeb8..53da1199b 100644 --- a/lib/pages/member_favorite/view.dart +++ b/lib/pages/member_favorite/view.dart @@ -1,4 +1,5 @@ import 'package:PiliPlus/common/skeleton/video_card_h.dart'; +import 'package:PiliPlus/common/widgets/custom_sliver_persistent_header_delegate.dart'; import 'package:PiliPlus/common/widgets/flutter/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -40,7 +41,7 @@ class _MemberFavoriteState extends State return refreshIndicator( onRefresh: _controller.onRefresh, child: CustomScrollView( - physics: const AlwaysScrollableScrollPhysics(), + physics: _FavScrollPhysics(controller: _controller), slivers: [ SliverPadding( padding: EdgeInsets.only( @@ -55,7 +56,10 @@ class _MemberFavoriteState extends State ); } - Widget _buildBody(ThemeData theme, LoadingState loadingState) { + Widget _buildBody( + ThemeData theme, + LoadingState?> loadingState, + ) { return switch (loadingState) { Loading() => SliverPadding( padding: const EdgeInsets.only(top: 7), @@ -65,19 +69,21 @@ class _MemberFavoriteState extends State itemCount: 10, ), ), - Success(:var response) => - (response as List?)?.isNotEmpty == true + Success(:final response) => + response?.isNotEmpty == true ? SliverMainAxisGroup( slivers: [ - SliverToBoxAdapter( - child: Obx( - () => _buildItem(theme, _controller.first.value, true), - ), + _buildItem( + theme, + data: _controller.favState, + isEnd: _controller.favEnd, + isFav: true, ), - SliverToBoxAdapter( - child: Obx( - () => _buildItem(theme, _controller.second.value, false), - ), + _buildItem( + theme, + data: _controller.subState, + isEnd: _controller.subEnd, + isFav: false, ), ], ) @@ -89,78 +95,159 @@ class _MemberFavoriteState extends State }; } - Theme _buildItem(ThemeData theme, SpaceFavData data, bool isFirst) { - return Theme( - data: theme.copyWith( - dividerColor: Colors.transparent, - ), - child: ExpansionTile( - dense: true, - initiallyExpanded: true, - title: Text.rich( - TextSpan( - children: [ - TextSpan( - text: data.name, - style: const TextStyle(fontSize: 14), - ), - TextSpan( - text: ' ${data.mediaListResponse?.count}', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.outline, - ), - ), - ], - ), - ), - controlAffinity: ListTileControlAffinity.leading, - children: [ - ...?data.mediaListResponse?.list?.map( - (item) => SizedBox( - height: 98, - child: MemberFavItem( - item: item, - callback: (res) { - if (res == true) { - _controller - ..first.value.mediaListResponse?.list?.remove(item) - ..first.refresh(); - } + Widget _buildItem( + ThemeData theme, { + required Rx data, + required RxBool isEnd, + required bool isFav, + }) { + return SliverMainAxisGroup( + slivers: [ + SliverPersistentHeader( + pinned: true, + delegate: CustomSliverPersistentHeaderDelegate( + child: Material( + color: theme.colorScheme.surface, + child: Builder( + builder: (context) { + return InkWell( + onTap: () { + _controller.setExpand(isFav); + (context as Element).markNeedsBuild(); + data.refresh(); + }, + child: Container( + height: 45, + alignment: .centerLeft, + padding: const .only(left: 12), + child: Text.rich( + TextSpan( + children: [ + WidgetSpan( + alignment: .middle, + child: Icon( + _controller.isExpand(isFav) + ? Icons.expand_less + : Icons.expand_more, + color: theme.colorScheme.outline, + ), + ), + TextSpan( + text: ' ${data.value.name}', + style: const TextStyle(fontSize: 14), + ), + TextSpan( + text: ' ${data.value.mediaListResponse?.count}', + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.outline, + ), + ), + ], + ), + ), + ), + ); }, ), ), + bgColor: null, ), - Obx( - () => - (isFirst - ? _controller.firstEnd.value - : _controller.secondEnd.value) - ? const SizedBox.shrink() - : _buildLoadMoreItem(theme, isFirst), - ), - ], - ), + ), + Obx(() { + final list = data.value.mediaListResponse?.list; + if (!_controller.isExpand(isFav)) { + return const SliverToBoxAdapter(); + } + final end = isEnd.value; + if (list != null && list.isNotEmpty) { + return SliverList.builder( + itemCount: list.length + (end ? 0 : 1), + itemBuilder: (context, index) { + if (!end && index == list.length) { + return Obx( + () => isEnd.value + ? const SizedBox.shrink() + : _buildLoadMoreItem(theme, isFav), + ); + } + final item = list[index]; + return SizedBox( + height: 98, + child: MemberFavItem( + item: item, + callback: (res) { + if (res == true) { + _controller.favState + ..value.mediaListResponse?.list?.remove(item) + ..refresh(); + } + }, + ), + ); + }, + ); + } + return const SliverToBoxAdapter(); + }), + ], ); } - ListTile _buildLoadMoreItem(ThemeData theme, bool isFirst) { - return ListTile( - dense: true, - onTap: () { - if (isFirst) { - _controller.userfavFolder(); - } else { - _controller.userSubFolder(); - } - }, - title: Text( - '查看更多内容', - textAlign: TextAlign.center, - style: TextStyle( - color: theme.colorScheme.primary, + Widget _buildLoadMoreItem(ThemeData theme, bool isFav) { + return Padding( + padding: const .only(top: 7), + child: InkWell( + onTap: () { + if (isFav) { + _controller.userFavFolder(); + } else { + _controller.userSubFolder(); + } + }, + child: Container( + height: 40, + alignment: .center, + child: Text( + '查看更多内容', + textAlign: TextAlign.center, + style: TextStyle(color: theme.colorScheme.primary), + ), ), ), ); } } + +class _FavScrollPhysics extends AlwaysScrollableScrollPhysics { + const _FavScrollPhysics({super.parent, required this.controller}); + + final MemberFavoriteCtr controller; + + @override + _FavScrollPhysics applyTo(ScrollPhysics? ancestor) { + return _FavScrollPhysics( + parent: buildParent(ancestor), + controller: controller, + ); + } + + @override + double adjustPositionForNewDimensions({ + required ScrollMetrics oldPosition, + required ScrollMetrics newPosition, + required bool isScrolling, + required double velocity, + }) { + if (controller.flag) { + controller.flag = false; + return 0; + } + return super.adjustPositionForNewDimensions( + oldPosition: oldPosition, + newPosition: newPosition, + isScrolling: isScrolling, + velocity: velocity, + ); + } +}