refa query follow up

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-07-31 15:00:23 +08:00
parent 1029621b63
commit e945daba3a
8 changed files with 168 additions and 204 deletions

View File

@@ -544,9 +544,11 @@ class MemberHttp {
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return Success( return Success(
FollowData( FollowData(
list: (res.data['data'] as List?) list:
(res.data['data'] as List?)
?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e)) ?.map<FollowItemModel>((e) => FollowItemModel.fromJson(e))
.toList(), .toList() ??
<FollowItemModel>[],
), ),
); );
} else { } else {

View File

@@ -1,20 +1,21 @@
class FollowUpModel { class FollowUpModel {
FollowUpModel({ FollowUpModel({
this.liveUsers, this.liveUsers,
this.upList, required this.upList,
}); });
String? errMsg;
LiveUsers? liveUsers; LiveUsers? liveUsers;
List<UpItem>? upList; late List<UpItem> upList;
FollowUpModel.fromJson(Map<String, dynamic> json) { FollowUpModel.fromJson(Map<String, dynamic> json) {
liveUsers = json['live_users'] != null liveUsers = json['live_users'] != null
? LiveUsers.fromJson(json['live_users']) ? LiveUsers.fromJson(json['live_users'])
: null; : null;
upList = (json['up_list'] as List?) upList =
(json['up_list'] as List?)
?.map<UpItem>((e) => UpItem.fromJson(e)) ?.map<UpItem>((e) => UpItem.fromJson(e))
.toList(); .toList() ??
<UpItem>[];
} }
} }
@@ -38,54 +39,40 @@ class LiveUsers {
} }
} }
sealed class UserItem { class LiveUserItem extends UpItem {
String? face;
bool? hasUpdate;
late int mid;
String? uname;
UserItem({
this.face,
this.hasUpdate,
int? mid,
this.uname,
}) : mid = mid ?? -1;
}
class LiveUserItem extends UserItem {
bool? isReserveRecall; bool? isReserveRecall;
String? jumpUrl; String? jumpUrl;
int? roomId; int? roomId;
String? title; String? title;
LiveUserItem.fromJson(Map<String, dynamic> json) { LiveUserItem.fromJson(Map<String, dynamic> json)
: super(mid: json['mid'] ?? 0) {
face = json['face']; face = json['face'];
isReserveRecall = json['is_reserve_recall']; isReserveRecall = json['is_reserve_recall'];
jumpUrl = json['jump_url']; jumpUrl = json['jump_url'];
mid = json['mid'] ?? -1;
roomId = json['room_id']; roomId = json['room_id'];
title = json['title']; title = json['title'];
uname = json['uname']; uname = json['uname'];
hasUpdate = false;
} }
} }
class UpItem extends UserItem { class UpItem {
String? face;
bool? hasUpdate;
late int mid;
String? uname;
UpItem({ UpItem({
super.face, this.face,
super.hasUpdate, this.hasUpdate,
// this.isReserveRecall, required this.mid,
super.mid, this.uname,
super.uname,
}); });
// bool? isReserveRecall;
UpItem.fromJson(Map<String, dynamic> json) { UpItem.fromJson(Map<String, dynamic> json) {
face = json['face']; face = json['face'];
hasUpdate = json['has_update']; hasUpdate = json['has_update'];
// isReserveRecall = json['is_reserve_recall']; mid = json['mid'] ?? 0;
mid = json['mid'] ?? -1;
uname = json['uname']; uname = json['uname'];
} }
} }

View File

@@ -1,16 +1,18 @@
import 'package:PiliPlus/models_new/follow/list.dart'; import 'package:PiliPlus/models_new/follow/list.dart';
class FollowData { class FollowData {
List<FollowItemModel>? list; late List<FollowItemModel> list;
int? reVersion; int? reVersion;
int? total; int? total;
FollowData({this.list, this.reVersion, this.total}); FollowData({required this.list, this.reVersion, this.total});
factory FollowData.fromJson(Map<String, dynamic> json) => FollowData( factory FollowData.fromJson(Map<String, dynamic> json) => FollowData(
list: (json['list'] as List<dynamic>?) list:
(json['list'] as List<dynamic>?)
?.map((e) => FollowItemModel.fromJson(e as Map<String, dynamic>)) ?.map((e) => FollowItemModel.fromJson(e as Map<String, dynamic>))
.toList(), .toList() ??
<FollowItemModel>[],
reVersion: json['re_version'] as int?, reVersion: json['re_version'] as int?,
total: json['total'] as int?, total: json['total'] as int?,
); );

View File

@@ -1,13 +1,11 @@
import 'package:PiliPlus/models/dynamics/up.dart';
import 'package:PiliPlus/models/model_avatar.dart'; import 'package:PiliPlus/models/model_avatar.dart';
class FollowItemModel { class FollowItemModel extends UpItem {
int? mid;
int? attribute; int? attribute;
int? mtime; int? mtime;
dynamic tag; dynamic tag;
int? special; int? special;
String? uname;
String? face;
String? sign; String? sign;
int? faceNft; int? faceNft;
BaseOfficialVerify? officialVerify; BaseOfficialVerify? officialVerify;
@@ -18,13 +16,13 @@ class FollowItemModel {
String? followTime; String? followTime;
FollowItemModel({ FollowItemModel({
this.mid, required super.mid,
this.attribute, this.attribute,
this.mtime, this.mtime,
this.tag, this.tag,
this.special, this.special,
this.uname, super.uname,
this.face, super.face,
this.sign, this.sign,
this.faceNft, this.faceNft,
this.officialVerify, this.officialVerify,
@@ -37,7 +35,7 @@ class FollowItemModel {
factory FollowItemModel.fromJson(Map<String, dynamic> json) => factory FollowItemModel.fromJson(Map<String, dynamic> json) =>
FollowItemModel( FollowItemModel(
mid: json['mid'] as int?, mid: json['mid'] as int? ?? 0,
attribute: json['attribute'] as int?, attribute: json['attribute'] as int?,
mtime: json['mtime'] as int?, mtime: json['mtime'] as int?,
tag: json['tag'] as dynamic, tag: json['tag'] as dynamic,

View File

@@ -5,7 +5,7 @@ import 'package:PiliPlus/http/follow.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart';
import 'package:PiliPlus/models/dynamics/up.dart'; import 'package:PiliPlus/models/dynamics/up.dart';
import 'package:PiliPlus/models_new/follow/list.dart'; import 'package:PiliPlus/models_new/follow/data.dart';
import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/pages/common/common_controller.dart';
import 'package:PiliPlus/pages/dynamics_tab/controller.dart'; import 'package:PiliPlus/pages/dynamics_tab/controller.dart';
import 'package:PiliPlus/services/account_service.dart'; import 'package:PiliPlus/services/account_service.dart';
@@ -13,25 +13,30 @@ import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/storage_pref.dart'; import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:easy_debounce/easy_throttle.dart'; import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
class DynamicsController extends GetxController class DynamicsController extends GetxController
with GetSingleTickerProviderStateMixin, ScrollOrRefreshMixin { with GetSingleTickerProviderStateMixin, ScrollOrRefreshMixin {
@override @override
final ScrollController scrollController = ScrollController(); final ScrollController scrollController = ScrollController();
String? offset = ''; late final TabController tabController = TabController(
Rx<FollowUpModel> upData = FollowUpModel().obs; length: DynamicsTabType.values.length,
// 默认获取全部动态 vsync: this,
RxInt mid = (-1).obs; initialIndex: Pref.defaultDynamicType,
late TabController tabController; );
Set<int> tempBannedList = <int>{};
List<UpItem> hasUpdatedUps = <UpItem>[];
int allFollowedUpsPage = 1;
int allFollowedUpsTotal = 0;
late final RxInt mid = (-1).obs;
late int currentMid = -1; late int currentMid = -1;
late bool showLiveItems = Pref.expandDynLivePanel;
Set<int> tempBannedList = <int>{};
final Rx<LoadingState<FollowUpModel>> upState =
LoadingState<FollowUpModel>.loading().obs;
late int _upPage = 1;
late bool _upEnd = false;
List<UpItem>? _cacheUpList;
late final showAllUp = Pref.dynamicsShowAllFollowedUp;
late bool showLiveUp = Pref.expandDynLivePanel;
final upPanelPosition = Pref.upPanelPosition; final upPanelPosition = Pref.upPanelPosition;
@@ -50,114 +55,98 @@ class DynamicsController extends GetxController
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
tabController = TabController( if (showAllUp) {
length: DynamicsTabType.values.length, scrollController.addListener(listener);
vsync: this, }
initialIndex: Pref.defaultDynamicType,
);
queryFollowUp(); queryFollowUp();
} }
void listener() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle(
'following',
const Duration(seconds: 1),
queryFollowing2,
);
}
}
Future<void> queryFollowing2() async { Future<void> queryFollowing2() async {
if (upData.value.upList != null && if (isQuerying) return;
upData.value.upList!.length >= allFollowedUpsTotal) { isQuerying = true;
if (_upEnd) {
isQuerying = false;
return; return;
} }
var res = await FollowHttp.followings(
final res = await FollowHttp.followings(
vmid: accountService.mid, vmid: accountService.mid,
pn: allFollowedUpsPage, pn: _upPage,
orderType: 'attention', orderType: 'attention',
ps: 50, ps: 50,
); );
if (res.isSuccess) { if (res.isSuccess) {
upData.value.upList ??= <UpItem>[]; _upPage++;
upData.value.upList!.addAll( final list = res.data.list;
res.data.list! if (list.isEmpty) {
.where((e) => hasUpdatedUps.every((e1) => e.mid != e1.mid)) _upEnd = true;
.map(
(FollowItemModel e) => UpItem(
face: e.face,
mid: e.mid,
uname: e.uname,
hasUpdate: false,
),
),
);
allFollowedUpsPage += 1;
allFollowedUpsTotal = res.data.total!;
upData.refresh();
} else {
res.toast();
} }
upState
..value.data.upList.addAll(
list..removeWhere((e) => _cacheUpList?.contains(e) == true),
)
..refresh();
}
isQuerying = false;
} }
late bool isQuerying = false; late bool isQuerying = false;
Future<void> queryFollowUp() async { Future<void> queryFollowUp() async {
if (isQuerying) return; if (isQuerying) return;
isQuerying = true; isQuerying = true;
if (!accountService.isLogin.value) { if (!accountService.isLogin.value) {
upData upState.value = const Error(null);
..value.errMsg = '账号未登录' isQuerying = false;
..refresh(); return;
} }
upData.value.errMsg = null;
if (Pref.dynamicsShowAllFollowedUp) { final res = await Future.wait([
allFollowedUpsPage = 1; DynamicsHttp.followUp(),
final f1 = DynamicsHttp.followUp(); if (showAllUp)
final f2 = FollowHttp.followings( FollowHttp.followings(
vmid: accountService.mid, vmid: accountService.mid,
pn: allFollowedUpsPage, pn: _upPage,
orderType: 'attention', orderType: 'attention',
ps: 50, ps: 50,
);
final res0 = await f1;
if (!res0.isSuccess) {
SmartDialog.showToast("获取关注动态失败:$res0");
upData
..value.errMsg = (res0 as Error).errMsg
..refresh();
} else {
upData
..value.liveUsers = res0.data.liveUsers
..refresh();
hasUpdatedUps = res0.data.upList!;
}
List<UpItem> allFollowedUps = <UpItem>[];
final res1 = await f2;
if (!res1.isSuccess) {
SmartDialog.showToast("获取关注列表失败:$res1");
} else {
allFollowedUps = res1.data.list!
.where((e) => hasUpdatedUps.every((e1) => e.mid != e1.mid))
.map(
(e) => UpItem(
face: e.face,
mid: e.mid,
uname: e.uname,
hasUpdate: false,
), ),
) ]);
.toList();
allFollowedUpsPage += 1; final first = res.first;
allFollowedUpsTotal = res1.data.total!; final second = res.getOrNull(1);
if (first.isSuccess) {
FollowUpModel data = first.data as FollowUpModel;
if (second != null && second.isSuccess) {
_cacheUpList = List<UpItem>.from(data.upList);
FollowData data1 = second.data as FollowData;
final list1 = data1.list;
_upPage++;
if (list1.isEmpty || list1.length >= (data1.total ?? 0)) {
_upEnd = true;
} }
upData
..value.upList = hasUpdatedUps + allFollowedUps final list = data.upList;
..refresh(); list.addAll(list1..removeWhere((e) => list.contains(e)));
}
upState.value = Success(data);
} else { } else {
var res = await DynamicsHttp.followUp(); upState.value = const Error(null);
if (res.isSuccess) {
upData.value = res.data;
if (upData.value.upList.isNullOrEmpty) {
mid.value = -1;
}
} else {
upData
..value.errMsg = (res as Error).errMsg
..refresh();
}
} }
isQuerying = false; isQuerying = false;
} }
@@ -177,6 +166,10 @@ class DynamicsController extends GetxController
@override @override
Future<void> onRefresh() async { Future<void> onRefresh() async {
if (showAllUp) {
_upPage = 1;
_cacheUpList = null;
}
queryFollowUp(); queryFollowUp();
await controller?.onRefresh(); await controller?.onRefresh();
} }
@@ -212,7 +205,9 @@ class DynamicsController extends GetxController
@override @override
void onClose() { void onClose() {
tabController.dispose(); tabController.dispose();
scrollController.dispose(); scrollController
..removeListener(listener)
..dispose();
super.onClose(); super.onClose();
} }
} }

View File

@@ -1,12 +1,12 @@
import 'package:PiliPlus/common/widgets/scroll_physics.dart'; import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart'; import 'package:PiliPlus/models/common/dynamic/dynamics_type.dart';
import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart'; import 'package:PiliPlus/models/common/dynamic/up_panel_position.dart';
import 'package:PiliPlus/models/dynamics/up.dart';
import 'package:PiliPlus/pages/dynamics/controller.dart'; import 'package:PiliPlus/pages/dynamics/controller.dart';
import 'package:PiliPlus/pages/dynamics/widgets/up_panel.dart'; import 'package:PiliPlus/pages/dynamics/widgets/up_panel.dart';
import 'package:PiliPlus/pages/dynamics_create/view.dart'; import 'package:PiliPlus/pages/dynamics_create/view.dart';
import 'package:PiliPlus/pages/dynamics_tab/view.dart'; import 'package:PiliPlus/pages/dynamics_tab/view.dart';
import 'package:PiliPlus/utils/storage_pref.dart';
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart' hide DraggableScrollableSheet; import 'package:flutter/material.dart' hide DraggableScrollableSheet;
import 'package:get/get.dart'; import 'package:get/get.dart';
@@ -52,29 +52,6 @@ class _DynamicsPageState extends State<DynamicsPage>
), ),
); );
@override
void initState() {
super.initState();
if (Pref.dynamicsShowAllFollowedUp) {
_dynamicsController.scrollController.addListener(listener);
}
}
void listener() {
if (_dynamicsController.scrollController.position.pixels >=
_dynamicsController.scrollController.position.maxScrollExtent - 300) {
EasyThrottle.throttle('following', const Duration(seconds: 1), () {
_dynamicsController.queryFollowing2();
});
}
}
@override
void dispose() {
_dynamicsController.scrollController.removeListener(listener);
super.dispose();
}
Widget upPanelPart(ThemeData theme) { Widget upPanelPart(ThemeData theme) {
bool isTop = upPanelPosition == UpPanelPosition.top; bool isTop = upPanelPosition == UpPanelPosition.top;
bool needBg = upPanelPosition.index > 1; bool needBg = upPanelPosition.index > 1;
@@ -84,25 +61,28 @@ class _DynamicsPageState extends State<DynamicsPage>
child: SizedBox( child: SizedBox(
width: isTop ? null : 64, width: isTop ? null : 64,
height: isTop ? 76 : null, height: isTop ? 76 : null,
child: Obx( child: Obx(() => _buildUpPanel(_dynamicsController.upState.value)),
() {
final upData = _dynamicsController.upData.value;
if (upData.upList == null && upData.liveUsers == null) {
return const SizedBox.shrink();
} else if (upData.errMsg != null) {
return Center(
child: IconButton(
icon: const Icon(Icons.refresh),
onPressed: _dynamicsController.queryFollowUp,
), ),
); );
} else {
return UpPanel(dynamicsController: _dynamicsController);
} }
Widget _buildUpPanel(LoadingState<FollowUpModel> upState) {
return switch (upState) {
Loading() => const SizedBox.shrink(),
Success<FollowUpModel>() => UpPanel(
dynamicsController: _dynamicsController,
),
Error() => Center(
child: IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
_dynamicsController
..upState.value = LoadingState<FollowUpModel>.loading()
..queryFollowUp();
}, },
), ),
), ),
); };
} }
@override @override

View File

@@ -30,8 +30,8 @@ class _UpPanelState extends State<UpPanel> {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final theme = Theme.of(context); final theme = Theme.of(context);
final upData = controller.upData.value; final upData = controller.upState.value.data;
final List<UpItem>? upList = upData.upList; final List<UpItem> upList = upData.upList;
final List<LiveUserItem>? liveList = upData.liveUsers?.items; final List<LiveUserItem>? liveList = upData.liveUsers?.items;
return CustomScrollView( return CustomScrollView(
scrollDirection: isTop ? Axis.horizontal : Axis.vertical, scrollDirection: isTop ? Axis.horizontal : Axis.vertical,
@@ -41,7 +41,7 @@ class _UpPanelState extends State<UpPanel> {
SliverToBoxAdapter( SliverToBoxAdapter(
child: InkWell( child: InkWell(
onTap: () => setState(() { onTap: () => setState(() {
controller.showLiveItems = !controller.showLiveItems; controller.showLiveUp = !controller.showLiveUp;
}), }),
onLongPress: () => Get.to(const LiveFollowPage()), onLongPress: () => Get.to(const LiveFollowPage()),
child: Container( child: Container(
@@ -64,7 +64,7 @@ class _UpPanelState extends State<UpPanel> {
WidgetSpan( WidgetSpan(
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,
child: Icon( child: Icon(
controller.showLiveItems controller.showLiveUp
? Icons.expand_less ? Icons.expand_less
: Icons.expand_more, : Icons.expand_more,
size: 12, size: 12,
@@ -75,7 +75,7 @@ class _UpPanelState extends State<UpPanel> {
WidgetSpan( WidgetSpan(
alignment: PlaceholderAlignment.middle, alignment: PlaceholderAlignment.middle,
child: Icon( child: Icon(
controller.showLiveItems controller.showLiveUp
? Icons.keyboard_arrow_right ? Icons.keyboard_arrow_right
: Icons.keyboard_arrow_left, : Icons.keyboard_arrow_left,
color: theme.colorScheme.primary, color: theme.colorScheme.primary,
@@ -88,7 +88,7 @@ class _UpPanelState extends State<UpPanel> {
), ),
), ),
), ),
if (controller.showLiveItems && liveList?.isNotEmpty == true) if (controller.showLiveUp && liveList?.isNotEmpty == true)
SliverList.builder( SliverList.builder(
itemCount: liveList!.length, itemCount: liveList!.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
@@ -110,9 +110,9 @@ class _UpPanelState extends State<UpPanel> {
), ),
), ),
), ),
if (upList?.isNotEmpty == true) if (upList.isNotEmpty == true)
SliverList.builder( SliverList.builder(
itemCount: upList!.length, itemCount: upList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return upItemBuild(theme, upList[index]); return upItemBuild(theme, upList[index]);
}, },
@@ -122,7 +122,7 @@ class _UpPanelState extends State<UpPanel> {
); );
} }
void _onSelect(UserItem data) { void _onSelect(UpItem data) {
controller controller
..currentMid = data.mid ..currentMid = data.mid
..onSelectUp(data.mid); ..onSelectUp(data.mid);
@@ -132,7 +132,7 @@ class _UpPanelState extends State<UpPanel> {
setState(() {}); setState(() {});
} }
Widget upItemBuild(ThemeData theme, UserItem data) { Widget upItemBuild(ThemeData theme, UpItem data) {
final currentMid = controller.currentMid; final currentMid = controller.currentMid;
final isLive = data is LiveUserItem; final isLive = data is LiveUserItem;
bool isCurrent = isLive || currentMid == data.mid || currentMid == -1; bool isCurrent = isLive || currentMid == data.mid || currentMid == -1;
@@ -143,14 +143,14 @@ class _UpPanelState extends State<UpPanel> {
onTap: () { onTap: () {
feedBack(); feedBack();
switch (data) { switch (data) {
case LiveUserItem():
Get.toNamed('/liveRoom?roomid=${data.roomId}');
case UpItem(): case UpItem():
_onSelect(data); _onSelect(data);
break; break;
case LiveUserItem():
Get.toNamed('/liveRoom?roomid=${data.roomId}');
} }
}, },
onDoubleTap: isLive ? () => _onSelect(data) : null, // onDoubleTap: isLive ? () => _onSelect(data) : null,
onLongPress: data.mid == -1 onLongPress: data.mid == -1
? null ? null
: () => Get.toNamed('/member?mid=${data.mid}'), : () => Get.toNamed('/member?mid=${data.mid}'),

View File

@@ -31,7 +31,7 @@ class FollowItem extends StatelessWidget {
if (onSelect != null) { if (onSelect != null) {
onSelect!.call( onSelect!.call(
UserModel( UserModel(
mid: item.mid!, mid: item.mid,
name: item.uname!, name: item.uname!,
avatar: item.face!, avatar: item.face!,
), ),