feat: remove fan

Closes #733

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-04-22 18:05:02 +08:00
parent 1f2f00d49c
commit 7856857cca
8 changed files with 139 additions and 70 deletions

View File

@@ -4,15 +4,21 @@ part 'relation.g.dart';
@JsonSerializable() @JsonSerializable()
class Relation { class Relation {
int? status; int? status;
@JsonKey(name: 'is_follow') @JsonKey(name: 'is_follow')
int? isFollow; int? isFollow;
@JsonKey(name: 'is_followed')
int? isFollowed;
Relation({this.status, this.isFollow}); Relation({
this.status,
this.isFollow,
this.isFollowed,
});
factory Relation.fromJson(Map<String, dynamic> json) { factory Relation.fromJson(Map<String, dynamic> json) {
return _$RelationFromJson(json); return _$RelationFromJson(json);
} }
Map<String, dynamic> toJson() => _$RelationToJson(this); Map<String, dynamic> toJson() => _$RelationToJson(this);
} }

View File

@@ -9,9 +9,11 @@ part of 'relation.dart';
Relation _$RelationFromJson(Map<String, dynamic> json) => Relation( Relation _$RelationFromJson(Map<String, dynamic> json) => Relation(
status: (json['status'] as num?)?.toInt(), status: (json['status'] as num?)?.toInt(),
isFollow: (json['is_follow'] as num?)?.toInt(), isFollow: (json['is_follow'] as num?)?.toInt(),
isFollowed: (json['is_followed'] as num?)?.toInt(),
); );
Map<String, dynamic> _$RelationToJson(Relation instance) => <String, dynamic>{ Map<String, dynamic> _$RelationToJson(Relation instance) => <String, dynamic>{
'status': instance.status, 'status': instance.status,
'is_follow': instance.isFollow, 'is_follow': instance.isFollow,
'is_followed': instance.isFollowed,
}; };

View File

@@ -1,7 +1,9 @@
import 'package:PiliPlus/http/fan.dart'; import 'package:PiliPlus/http/fan.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models/fans/result.dart'; import 'package:PiliPlus/models/fans/result.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/storage.dart';
@@ -12,7 +14,7 @@ class FansController
late int? mid; late int? mid;
late String? name; late String? name;
dynamic userInfo; dynamic userInfo;
RxBool isOwner = false.obs; late bool isOwner = false;
@override @override
void onInit() { void onInit() {
@@ -21,7 +23,7 @@ class FansController
mid = Get.parameters['mid'] != null mid = Get.parameters['mid'] != null
? int.parse(Get.parameters['mid']!) ? int.parse(Get.parameters['mid']!)
: userInfo?.mid; : userInfo?.mid;
isOwner.value = mid == userInfo?.mid; isOwner = mid == userInfo?.mid;
name = Get.parameters['name'] ?? userInfo?.uname; name = Get.parameters['name'] ?? userInfo?.uname;
queryData(); queryData();
@@ -39,4 +41,20 @@ class FansController
ps: ps, ps: ps,
orderType: 'attention', orderType: 'attention',
); );
Future onRemoveFan(int index, int mid) async {
final res = await VideoHttp.relationMod(
mid: mid,
act: 7,
reSrc: 11,
);
if (res['status']) {
List<FansItemModel> list = (loadingState.value as Success).response;
list.removeAt(index);
loadingState.refresh();
SmartDialog.showToast('移除成功');
} else {
SmartDialog.showToast(res['msg']);
}
}
} }

View File

@@ -1,4 +1,6 @@
import 'package:PiliPlus/common/skeleton/msg_feed_top.dart'; import 'package:PiliPlus/common/skeleton/msg_feed_top.dart';
import 'package:PiliPlus/common/widgets/dialog.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/fans/result.dart'; import 'package:PiliPlus/models/fans/result.dart';
@@ -9,7 +11,6 @@ import 'package:PiliPlus/common/widgets/http_error.dart';
import '../../utils/grid.dart'; import '../../utils/grid.dart';
import 'controller.dart'; import 'controller.dart';
import 'widgets/fan_item.dart';
class FansPage extends StatefulWidget { class FansPage extends StatefulWidget {
const FansPage({super.key}); const FansPage({super.key});
@@ -34,7 +35,7 @@ class _FansPageState extends State<FansPage> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text( title: Text(
_fansController.isOwner.value ? '我的粉丝' : '${_fansController.name}的粉丝', _fansController.isOwner ? '我的粉丝' : '${_fansController.name}的粉丝',
), ),
), ),
body: SafeArea( body: SafeArea(
@@ -81,7 +82,47 @@ class _FansPageState extends State<FansPage> {
if (index == loadingState.response!.length - 1) { if (index == loadingState.response!.length - 1) {
_fansController.onLoadMore(); _fansController.onLoadMore();
} }
return fanItem(item: loadingState.response![index]); final item = loadingState.response![index];
String heroTag = Utils.makeHeroTag(item.mid);
return ListTile(
onTap: () {
Get.toNamed(
'/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag},
);
},
onLongPress: _fansController.isOwner
? () {
showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () {
_fansController.onRemoveFan(index, item.mid!);
},
);
}
: null,
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 45,
height: 45,
type: 'avatar',
src: item.face,
),
),
title: Text(
item.uname!,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
item.sign ?? '',
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
dense: true,
trailing: const SizedBox(width: 6),
);
}, },
childCount: loadingState.response!.length, childCount: loadingState.response!.length,
), ),

View File

@@ -1,32 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/network_img_layer.dart';
import 'package:PiliPlus/utils/utils.dart';
Widget fanItem({item}) {
String heroTag = Utils.makeHeroTag(item!.mid);
return ListTile(
onTap: () => Get.toNamed('/member?mid=${item.mid}',
arguments: {'face': item.face, 'heroTag': heroTag}),
leading: Hero(
tag: heroTag,
child: NetworkImgLayer(
width: 45,
height: 45,
type: 'avatar',
src: item.face,
),
),
title: Text(
item.uname,
style: const TextStyle(fontSize: 14),
),
subtitle: Text(
item.sign,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
dense: true,
trailing: const SizedBox(width: 6),
);
}

View File

@@ -25,31 +25,22 @@ class MemberControllerNew extends CommonDataController<Data, dynamic>
with GetTickerProviderStateMixin { with GetTickerProviderStateMixin {
MemberControllerNew({required this.mid}); MemberControllerNew({required this.mid});
int mid; int mid;
RxBool showUname = false.obs;
String? username;
int? ownerMid; int? ownerMid;
RxInt relation = 0.obs; String? username;
TabController? tabController; RxBool showUname = false.obs;
late List<Tab> tabs;
List<Tab2>? tab2;
RxInt contributeInitialIndex = 0.obs;
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
bool get isFollow => relation.value != 0 && relation.value != 128;
@override
void onInit() {
super.onInit();
ownerMid = Accounts.main.mid;
queryData();
}
dynamic live; dynamic live;
int? silence; int? silence;
String? endTime; String? endTime;
int? isFollowed; // 被关注
RxInt relation = 0.obs;
bool get isFollow => relation.value != 0 && relation.value != 128;
List<Tab2>? tab2;
late List<Tab> tabs;
TabController? tabController;
RxInt contributeInitialIndex = 0.obs;
late final implTabs = const [ late final implTabs = const [
'home', 'home',
'dynamic', 'dynamic',
@@ -58,10 +49,22 @@ class MemberControllerNew extends CommonDataController<Data, dynamic>
'bangumi', 'bangumi',
]; ];
bool? hasSeasonOrSeries;
final fromViewAid = Get.parameters['from_view_aid'];
@override
void onInit() {
super.onInit();
ownerMid = Accounts.main.mid;
queryData();
}
@override @override
bool customHandleResponse(bool isRefresh, Success<Data> response) { bool customHandleResponse(bool isRefresh, Success<Data> response) {
Data data = response.response; Data data = response.response;
username = data.card?.name ?? ''; username = data.card?.name ?? '';
isFollowed = data.card?.relation?.isFollowed;
if (data.relation == -1) { if (data.relation == -1) {
relation.value = 128; relation.value = 128;
} else { } else {
@@ -69,7 +72,7 @@ class MemberControllerNew extends CommonDataController<Data, dynamic>
? data.relSpecial == 1 ? data.relSpecial == 1
? -10 ? -10
: data.card?.relation?.status ?? 2 : data.card?.relation?.status ?? 2
: 0; : data.card?.relation?.status ?? 0;
} }
tab2 = data.tab2; tab2 = data.tab2;
live = data.live; live = data.live;
@@ -226,4 +229,21 @@ class MemberControllerNew extends CommonDataController<Data, dynamic>
tabController?.dispose(); tabController?.dispose();
super.onClose(); super.onClose();
} }
Future onRemoveFan() async {
final res = await VideoHttp.relationMod(
mid: mid,
act: 7,
reSrc: 11,
);
if (res['status']) {
isFollowed = null;
if (relation.value == 4) {
relation.value = 2;
}
SmartDialog.showToast('移除成功');
} else {
SmartDialog.showToast(res['msg']);
}
}
} }

View File

@@ -84,7 +84,8 @@ class _MemberPageNewState extends State<MemberPageNew> {
PopupMenuButton( PopupMenuButton(
icon: const Icon(Icons.more_vert), icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[ itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_userController.ownerMid != _mid) ...[ if (_userController.ownerMid != 0 &&
_userController.ownerMid != _mid) ...[
PopupMenuItem( PopupMenuItem(
onTap: () => _userController.blockUser(context), onTap: () => _userController.blockUser(context),
child: Row( child: Row(
@@ -97,7 +98,19 @@ class _MemberPageNewState extends State<MemberPageNew> {
: '移除黑名单'), : '移除黑名单'),
], ],
), ),
) ),
if (_userController.isFollowed == 1)
PopupMenuItem(
onTap: _userController.onRemoveFan,
child: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Icon(Icons.remove_circle_outline_outlined, size: 19),
SizedBox(width: 10),
Text('移除粉丝'),
],
),
),
], ],
PopupMenuItem( PopupMenuItem(
onTap: () => _userController.shareUser(), onTap: () => _userController.shareUser(),

View File

@@ -375,7 +375,7 @@ class UserInfoCard extends StatelessWidget {
child: FilledButton.tonal( child: FilledButton.tonal(
onPressed: onFollow, onPressed: onFollow,
style: FilledButton.styleFrom( style: FilledButton.styleFrom(
backgroundColor: relation != 0 backgroundColor: relation != 0 && relation != 3
? Theme.of(context).colorScheme.onInverseSurface ? Theme.of(context).colorScheme.onInverseSurface
: null, : null,
visualDensity: const VisualDensity( visualDensity: const VisualDensity(
@@ -385,13 +385,13 @@ class UserInfoCard extends StatelessWidget {
), ),
child: Text.rich( child: Text.rich(
style: TextStyle( style: TextStyle(
color: relation != 0 color: relation != 0 && relation != 3
? Theme.of(context).colorScheme.outline ? Theme.of(context).colorScheme.outline
: null, : null,
), ),
TextSpan( TextSpan(
children: [ children: [
if (relation != 0 && relation != 128) if (relation != 0 && relation != 128 && relation != 3)
WidgetSpan( WidgetSpan(
alignment: PlaceholderAlignment.top, alignment: PlaceholderAlignment.top,
child: Icon( child: Icon(
@@ -407,6 +407,7 @@ class UserInfoCard extends StatelessWidget {
0 => '关注', 0 => '关注',
1 => '悄悄关注', 1 => '悄悄关注',
2 => '已关注', 2 => '已关注',
3 => '回关',
4 || 6 => '已互关', 4 || 6 => '已互关',
128 => '移除黑名单', 128 => '移除黑名单',
-10 => '特别关注', // 该状态码并不是官方状态码 -10 => '特别关注', // 该状态码并不是官方状态码