handle relation url

Closes #1566

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-10-15 18:25:15 +08:00
parent 32ce2b87db
commit c9de79532a
31 changed files with 634 additions and 393 deletions

View File

@@ -1,29 +1,35 @@
import 'package:PiliPlus/http/fan.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/video.dart';
import 'package:PiliPlus/models_new/fans/data.dart';
import 'package:PiliPlus/models_new/fans/list.dart';
import 'package:PiliPlus/pages/common/common_list_controller.dart';
import 'package:PiliPlus/models_new/follow/data.dart';
import 'package:PiliPlus/pages/follow_type/controller.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
class FansController extends CommonListController<FansData, FansItemModel> {
FansController(this.mid);
int total = 0;
int mid;
class FansController extends FollowTypeController {
FansController(this.showName);
final bool showName;
late final bool isOwner;
@override
void onInit() {
super.onInit();
void init() {
final ownerMid = Accounts.main.mid;
final mid = Get.parameters['mid'];
this.mid = mid != null ? int.parse(mid) : ownerMid;
isOwner = ownerMid == this.mid;
if (showName && !isOwner) {
final name = Get.parameters['name'];
this.name = RxnString(name);
if (name == null) {
queryUserName();
}
}
queryData();
}
@override
List<FansItemModel>? getDataList(FansData response) {
return response.list;
}
@override
Future<LoadingState<FansData>> customGetData() => FanHttp.fans(
Future<LoadingState<FollowData>> customGetData() => FanHttp.fans(
vmid: mid,
pn: page,
orderType: 'attention',

View File

@@ -1,16 +1,9 @@
import 'package:PiliPlus/common/skeleton/msg_feed_top.dart';
import 'package:PiliPlus/common/widgets/dialog/dialog.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/common/widgets/view_sliver_safe_area.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models_new/fans/list.dart';
import 'package:PiliPlus/models_new/follow/list.dart';
import 'package:PiliPlus/pages/fan/controller.dart';
import 'package:PiliPlus/pages/follow_type/view.dart';
import 'package:PiliPlus/pages/follow_type/widgets/item.dart';
import 'package:PiliPlus/pages/share/view.dart' show UserModel;
import 'package:PiliPlus/services/account_service.dart';
import 'package:PiliPlus/utils/grid.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -18,160 +11,63 @@ import 'package:get/get.dart';
class FansPage extends StatefulWidget {
const FansPage({
super.key,
this.mid,
this.showName,
this.onSelect,
});
final int? mid;
final bool? showName;
final ValueChanged<UserModel>? onSelect;
@override
State<FansPage> createState() => _FansPageState();
}
class _FansPageState extends State<FansPage> {
late int mid;
String? name;
late bool isOwner;
late FansController _fansController;
class _FansPageState extends FollowTypePageState<FansPage> {
@override
void initState() {
super.initState();
AccountService accountService = Get.find<AccountService>();
late final mid = Get.parameters['mid'];
this.mid =
widget.mid ?? (mid != null ? int.parse(mid) : accountService.mid);
isOwner = this.mid == accountService.mid;
name = Get.parameters['name'] ?? accountService.name.value;
_fansController = Get.put(
FansController(this.mid),
tag: Utils.makeHeroTag(this.mid),
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context).colorScheme;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: widget.mid != null
? null
: AppBar(title: Text(isOwner ? '我的粉丝' : '$name的粉丝')),
body: refreshIndicator(
onRefresh: _fansController.onRefresh,
child: CustomScrollView(
physics: const AlwaysScrollableScrollPhysics(),
controller: _fansController.scrollController,
slivers: [
ViewSliverSafeArea(
sliver: Obx(
() => _buildBody(theme, _fansController.loadingState.value),
),
),
],
),
),
);
}
late final gridDelegate = SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: Grid.smallCardWidth * 2,
mainAxisExtent: 66,
late final FansController controller = Get.put(
FansController(widget.showName ?? true),
tag: Utils.generateRandomString(8),
);
late final flag = widget.onSelect == null && controller.isOwner;
Widget _buildBody(
ColorScheme theme,
LoadingState<List<FansItemModel>?> loadingState,
) {
return switch (loadingState) {
Loading() => SliverGrid.builder(
gridDelegate: gridDelegate,
itemBuilder: (context, index) => const MsgFeedTopSkeleton(),
itemCount: 16,
),
Success(:var response) =>
response?.isNotEmpty == true
? SliverGrid.builder(
gridDelegate: gridDelegate,
itemBuilder: (context, index) {
if (index == response.length - 1) {
_fansController.onLoadMore();
}
return _buildItem(theme, index, response[index]);
},
itemCount: response!.length,
)
: HttpError(onReload: _fansController.onReload),
Error(:var errMsg) => HttpError(
errMsg: errMsg,
onReload: _fansController.onReload,
),
};
}
@override
PreferredSizeWidget? get appBar => widget.showName == false
? null
: AppBar(
title: controller.isOwner
? const Text('我的粉丝')
: Obx(() {
final name = controller.name.value;
if (name != null) return Text('$name的粉丝');
return const SizedBox.shrink();
}),
);
Widget _buildItem(ColorScheme theme, int index, FansItemModel item) {
final isSelect = widget.onSelect != null;
@override
Widget buildItem(int index, FollowItemModel item) {
void onRemove() => showConfirmDialog(
context: context,
title: '确定移除 ${item.uname} ',
onConfirm: () => _fansController.onRemoveFan(index, item.mid!),
onConfirm: () => controller.onRemoveFan(index, item.mid),
);
final flag = !isSelect && isOwner;
return SizedBox(
height: 66,
child: InkWell(
onTap: () {
if (widget.onSelect != null) {
widget.onSelect!(
UserModel(
mid: item.mid!,
name: item.uname!,
avatar: item.face!,
),
);
return;
}
Get.toNamed('/member?mid=${item.mid}');
},
onLongPress: flag ? onRemove : null,
onSecondaryTap: flag && !Utils.isMobile ? onRemove : null,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 10,
),
child: Row(
spacing: 10,
children: [
NetworkImgLayer(
width: 45,
height: 45,
type: ImageType.avatar,
src: item.face,
),
Column(
spacing: 3,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
item.uname!,
style: const TextStyle(fontSize: 14),
),
if (item.sign != null)
Text(
item.sign!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(fontSize: 13, color: theme.outline),
),
],
),
],
),
),
),
return FollowTypeItem(
item: item,
onTap: () {
if (widget.onSelect != null) {
widget.onSelect!(
UserModel(
mid: item.mid,
name: item.uname!,
avatar: item.face!,
),
);
return;
}
Get.toNamed('/member?mid=${item.mid}');
},
onLongPress: flag ? onRemove : null,
onSecondaryTap: flag && !Utils.isMobile ? onRemove : null,
);
}
}