refa: member fav

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-12-03 12:52:23 +08:00
parent 9c8e5b53e7
commit b7a277a57c
3 changed files with 255 additions and 137 deletions

View File

@@ -32,6 +32,7 @@ enum CDNService {
const CDNService(this.desc, [this.host]); const CDNService(this.desc, [this.host]);
} }
// from https://rec.danmuji.org/dev/cdn-info/ // from https://rec.danmuji.org/dev/cdn-info/
// { // {
// 'cn-ahwh-ct-': {'01': 16}, // 'cn-ahwh-ct-': {'01': 16},

View File

@@ -11,21 +11,37 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class MemberFavoriteCtr class MemberFavoriteCtr
extends CommonDataController<List<SpaceFavData>?, dynamic> { extends CommonDataController<List<SpaceFavData>?, List<SpaceFavData>?> {
MemberFavoriteCtr({ MemberFavoriteCtr({
required this.mid, required this.mid,
}); });
final int mid; final int mid;
Rx<SpaceFavData> first = SpaceFavData().obs; late int favPage = 2;
Rx<SpaceFavData> second = SpaceFavData().obs; bool _favExpand = true;
final RxBool favEnd = true.obs;
final Rx<SpaceFavData> favState = SpaceFavData().obs;
RxBool firstEnd = true.obs; late int subPage = 2;
RxBool secondEnd = true.obs; bool _subExpand = true;
final RxBool subEnd = true.obs;
final Rx<SpaceFavData> subState = SpaceFavData().obs;
late int page = 2; bool isExpand(bool isFav) {
late int page1 = 2; return isFav ? _favExpand : _subExpand;
}
void setExpand(bool isFav) {
if (isFav) {
flag = _favExpand;
_favExpand = !_favExpand;
} else {
_subExpand = !_subExpand;
}
}
bool flag = false;
@override @override
void onInit() { void onInit() {
@@ -35,8 +51,8 @@ class MemberFavoriteCtr
@override @override
Future<void> onRefresh() { Future<void> onRefresh() {
page = 2; favPage = 2;
page1 = 2; subPage = 2;
return super.onRefresh(); return super.onRefresh();
} }
@@ -47,13 +63,13 @@ class MemberFavoriteCtr
) { ) {
try { try {
List<SpaceFavData> res = response.response!; List<SpaceFavData> res = response.response!;
first.value = res.first; favState.value = res.first;
second.value = res[1]; subState.value = res[1];
firstEnd.value = favEnd.value =
(res.first.mediaListResponse?.count ?? -1) <= (res.first.mediaListResponse?.count ?? -1) <=
(res.first.mediaListResponse?.list?.length ?? -1); (res.first.mediaListResponse?.list?.length ?? -1);
secondEnd.value = subEnd.value =
(res[1].mediaListResponse?.count ?? -1) <= (res[1].mediaListResponse?.count ?? -1) <=
(res[1].mediaListResponse?.list?.length ?? -1); (res[1].mediaListResponse?.list?.length ?? -1);
} catch (e) { } catch (e) {
@@ -63,62 +79,76 @@ class MemberFavoriteCtr
return true; return true;
} }
Future<void> userfavFolder() async { Future<void> userFavFolder() async {
var res = await Request().get( try {
Api.userFavFolder, final res = await Request().get(
queryParameters: { Api.userFavFolder,
'pn': page, queryParameters: {
'ps': 20, 'pn': favPage,
'up_mid': mid, 'ps': 20,
}, 'up_mid': mid,
); },
if (res.data['code'] == 0) { );
page++; if (res.data['code'] == 0) {
firstEnd.value = res.data['data']['has_more'] == false; favPage++;
if (res.data['data'] != null) { final data = res.data['data'];
List<SpaceFavItemModel> list = if (data != null) {
(res.data['data']?['list'] as List<dynamic>?) favEnd.value = data['has_more'] == false;
?.map((item) => SpaceFavItemModel.fromJson(item)) final list = (data['list'] as List<dynamic>?)
.toList() ?? ?.map((item) => SpaceFavItemModel.fromJson(item))
<SpaceFavItemModel>[]; .toList();
first if (list != null && list.isNotEmpty) {
..value.mediaListResponse?.list?.addAll(list) favState
..refresh(); ..value.mediaListResponse!.list!.addAll(list)
..refresh();
} else {
favEnd.value = true;
}
} else {
favEnd.value = true;
}
} else { } else {
firstEnd.value = true; SmartDialog.showToast(res.data['message']);
} }
} else { } catch (e) {
SmartDialog.showToast(res.data['message'] ?? '账号未登录'); SmartDialog.showToast(e.toString());
} }
} }
Future<void> userSubFolder() async { Future<void> userSubFolder() async {
var res = await Request().get( try {
Api.userSubFolder, final res = await Request().get(
queryParameters: { Api.userSubFolder,
'up_mid': mid, queryParameters: {
'ps': 20, 'up_mid': mid,
'pn': page1, 'ps': 20,
'platform': 'web', 'pn': subPage,
}, 'platform': 'web',
); },
if (res.data['code'] == 0) { );
page++; if (res.data['code'] == 0) {
secondEnd.value = res.data['data']['has_more'] == false; subPage++;
if (res.data['data'] != null) { final data = res.data['data'];
List<SpaceFavItemModel> list = if (data != null) {
(res.data['data']?['list'] as List<dynamic>?) subEnd.value = data['has_more'] == false;
?.map((item) => SpaceFavItemModel.fromJson(item)) final list = (data['list'] as List<dynamic>?)
.toList() ?? ?.map((item) => SpaceFavItemModel.fromJson(item))
<SpaceFavItemModel>[]; .toList();
second if (list != null && list.isNotEmpty) {
..value.mediaListResponse?.list?.addAll(list) subState
..refresh(); ..value.mediaListResponse!.list!.addAll(list)
..refresh();
} else {
subEnd.value = true;
}
} else {
subEnd.value = true;
}
} else { } else {
secondEnd.value = true; SmartDialog.showToast(res.data['message']);
} }
} else { } catch (e) {
SmartDialog.showToast(res.data['message'] ?? '账号未登录'); SmartDialog.showToast(e.toString());
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:PiliPlus/common/skeleton/video_card_h.dart'; 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/flutter/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
@@ -40,7 +41,7 @@ class _MemberFavoriteState extends State<MemberFavorite>
return refreshIndicator( return refreshIndicator(
onRefresh: _controller.onRefresh, onRefresh: _controller.onRefresh,
child: CustomScrollView( child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: _FavScrollPhysics(controller: _controller),
slivers: [ slivers: [
SliverPadding( SliverPadding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
@@ -55,7 +56,10 @@ class _MemberFavoriteState extends State<MemberFavorite>
); );
} }
Widget _buildBody(ThemeData theme, LoadingState loadingState) { Widget _buildBody(
ThemeData theme,
LoadingState<List<SpaceFavData>?> loadingState,
) {
return switch (loadingState) { return switch (loadingState) {
Loading() => SliverPadding( Loading() => SliverPadding(
padding: const EdgeInsets.only(top: 7), padding: const EdgeInsets.only(top: 7),
@@ -65,19 +69,21 @@ class _MemberFavoriteState extends State<MemberFavorite>
itemCount: 10, itemCount: 10,
), ),
), ),
Success(:var response) => Success(:final response) =>
(response as List?)?.isNotEmpty == true response?.isNotEmpty == true
? SliverMainAxisGroup( ? SliverMainAxisGroup(
slivers: [ slivers: [
SliverToBoxAdapter( _buildItem(
child: Obx( theme,
() => _buildItem(theme, _controller.first.value, true), data: _controller.favState,
), isEnd: _controller.favEnd,
isFav: true,
), ),
SliverToBoxAdapter( _buildItem(
child: Obx( theme,
() => _buildItem(theme, _controller.second.value, false), data: _controller.subState,
), isEnd: _controller.subEnd,
isFav: false,
), ),
], ],
) )
@@ -89,78 +95,159 @@ class _MemberFavoriteState extends State<MemberFavorite>
}; };
} }
Theme _buildItem(ThemeData theme, SpaceFavData data, bool isFirst) { Widget _buildItem(
return Theme( ThemeData theme, {
data: theme.copyWith( required Rx<SpaceFavData> data,
dividerColor: Colors.transparent, required RxBool isEnd,
), required bool isFav,
child: ExpansionTile( }) {
dense: true, return SliverMainAxisGroup(
initiallyExpanded: true, slivers: [
title: Text.rich( SliverPersistentHeader(
TextSpan( pinned: true,
children: [ delegate: CustomSliverPersistentHeaderDelegate(
TextSpan( child: Material(
text: data.name, color: theme.colorScheme.surface,
style: const TextStyle(fontSize: 14), child: Builder(
), builder: (context) {
TextSpan( return InkWell(
text: ' ${data.mediaListResponse?.count}', onTap: () {
style: TextStyle( _controller.setExpand(isFav);
fontSize: 13, (context as Element).markNeedsBuild();
color: theme.colorScheme.outline, data.refresh();
), },
), child: Container(
], height: 45,
), alignment: .centerLeft,
), padding: const .only(left: 12),
controlAffinity: ListTileControlAffinity.leading, child: Text.rich(
children: [ TextSpan(
...?data.mediaListResponse?.list?.map( children: [
(item) => SizedBox( WidgetSpan(
height: 98, alignment: .middle,
child: MemberFavItem( child: Icon(
item: item, _controller.isExpand(isFav)
callback: (res) { ? Icons.expand_less
if (res == true) { : Icons.expand_more,
_controller color: theme.colorScheme.outline,
..first.value.mediaListResponse?.list?.remove(item) ),
..first.refresh(); ),
} 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( ),
() => Obx(() {
(isFirst final list = data.value.mediaListResponse?.list;
? _controller.firstEnd.value if (!_controller.isExpand(isFav)) {
: _controller.secondEnd.value) return const SliverToBoxAdapter();
? const SizedBox.shrink() }
: _buildLoadMoreItem(theme, isFirst), 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) { Widget _buildLoadMoreItem(ThemeData theme, bool isFav) {
return ListTile( return Padding(
dense: true, padding: const .only(top: 7),
onTap: () { child: InkWell(
if (isFirst) { onTap: () {
_controller.userfavFolder(); if (isFav) {
} else { _controller.userFavFolder();
_controller.userSubFolder(); } else {
} _controller.userSubFolder();
}, }
title: Text( },
'查看更多内容', child: Container(
textAlign: TextAlign.center, height: 40,
style: TextStyle( alignment: .center,
color: theme.colorScheme.primary, 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,
);
}
}