Files
PiliPlus/lib/pages/member/member_page.dart
bggRGjQaUbCoE 4972e64cad opt: item
chore: clean up widgets

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-04-16 21:47:14 +08:00

424 lines
15 KiB
Dart

import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar.dart';
import 'package:PiliPlus/common/widgets/loading_widget.dart';
import 'package:PiliPlus/common/widgets/radio_widget.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/http/member.dart';
import 'package:PiliPlus/models/space/data.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/bangumi/member_bangumi.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/content/favorite/member_favorite.dart';
import 'package:PiliPlus/pages/member/content/member_contribute/member_contribute.dart';
import 'package:PiliPlus/pages/member/content/member_home/member_home.dart';
import 'package:PiliPlus/pages/member/controller.dart';
import 'package:PiliPlus/pages/member/widget/user_info_card.dart';
import 'package:PiliPlus/pages/member_dynamics/view.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
class MemberPageNew extends StatefulWidget {
const MemberPageNew({super.key});
@override
State<MemberPageNew> createState() => _MemberPageNewState();
}
class _MemberPageNewState extends State<MemberPageNew> {
late final int _mid;
late final String _heroTag;
late final MemberControllerNew _userController;
final _key = GlobalKey<ExtendedNestedScrollViewState>();
@override
void initState() {
super.initState();
_mid = int.tryParse(Get.parameters['mid']!) ?? -1;
_heroTag = Utils.makeHeroTag(_mid);
_userController = Get.put(
MemberControllerNew(mid: _mid),
tag: _heroTag,
);
_userController.scrollController.addListener(listener);
}
void listener() {
_userController.showUname.value =
_userController.scrollController.offset >= 120;
}
@override
void dispose() {
_userController.scrollController.removeListener(listener);
super.dispose();
}
@override
Widget build(BuildContext context) {
if (_userController.top == 0) {
_userController.top = MediaQuery.of(context).padding.top;
}
return Scaffold(
resizeToAvoidBottomInset: false,
body: Obx(
() => _userController.loadingState.value is Success
? LayoutBuilder(
builder: (context, constraints) {
return ExtendedNestedScrollView(
key: _key,
controller: _userController.scrollController,
onlyOneScrollInBody: true,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverOverlapAbsorber(
handle: ExtendedNestedScrollView
.sliverOverlapAbsorberHandleFor(context),
sliver: _buildAppBar(
isV: constraints.maxHeight > constraints.maxWidth,
),
),
];
},
body: _userController.tab2?.isNotEmpty == true
? LayoutBuilder(
builder: (context, _) {
return Padding(
padding: EdgeInsets.only(
top: ExtendedNestedScrollView
.sliverOverlapAbsorberHandleFor(
context)
.layoutExtent ??
0,
),
child: _buildBody,
);
},
)
: Center(child: const Text('EMPTY')),
);
},
)
: Center(
child: _buildUserInfo(_userController.loadingState.value),
),
),
);
}
Widget get _buildTab => Material(
color: Theme.of(context).colorScheme.surface,
child: TabBar(
controller: _userController.tabController,
tabs: _userController.tabs,
onTap: (value) {
if (_userController.tabController?.indexIsChanging == false) {
_key.currentState?.outerController.animToTop();
}
},
),
);
Widget get _buildBody => SafeArea(
top: false,
bottom: false,
child: tabBarView(
controller: _userController.tabController,
children: _userController.tab2!.map((item) {
return switch (item.param!) {
'home' => MemberHome(heroTag: _heroTag),
// 'dynamic' => MemberDynamic(mid: _mid ?? -1),
'dynamic' => MemberDynamicsPage(mid: _mid),
'contribute' => Obx(
() => MemberContribute(
heroTag: _heroTag,
initialIndex: _userController.contributeInitialIndex.value,
mid: _mid,
),
),
'bangumi' => MemberBangumi(
heroTag: _heroTag,
mid: _mid,
),
'favorite' => MemberFavorite(
heroTag: _heroTag,
mid: _mid,
),
_ => Center(child: Text(item.title ?? '')),
};
}).toList(),
),
);
Widget _buildAppBar({bool needTab = true, bool isV = true}) =>
DynamicSliverAppBar(
primary: false,
leading: Padding(
padding: EdgeInsets.only(top: _userController.top),
child: const BackButton(),
),
hasTabBar: (_userController.tab2?.length ?? 0) > 1,
toolbarHeight: kToolbarHeight + _userController.top,
title: IgnorePointer(
child: Obx(() => _userController.showUname.value &&
_userController.username != null
? Padding(
padding: EdgeInsets.only(top: _userController.top),
child: Text(_userController.username!),
)
: const SizedBox.shrink()),
),
pinned: true,
flexibleSpace: _buildUserInfo(_userController.loadingState.value, isV),
bottom: needTab && (_userController.tab2?.length ?? -1) > 1
? PreferredSize(
preferredSize: Size.fromHeight(48),
child: _buildTab,
)
: null,
actions: [
Padding(
padding: EdgeInsets.only(top: _userController.top),
child: IconButton(
tooltip: '搜索',
onPressed: () => Get.toNamed(
'/memberSearch?mid=$_mid&uname=${_userController.username}'),
icon: const Icon(Icons.search_outlined),
),
),
Padding(
padding: EdgeInsets.only(top: _userController.top),
child: PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_userController.ownerMid != _mid) ...[
PopupMenuItem(
onTap: () => _userController.blockUser(context),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.block, size: 19),
const SizedBox(width: 10),
Text(_userController.relation.value != -1
? '加入黑名单'
: '移除黑名单'),
],
),
)
],
PopupMenuItem(
onTap: () => _userController.shareUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.share_outlined, size: 19),
const SizedBox(width: 10),
Text(_userController.ownerMid != _mid
? '分享UP主'
: '分享我的主页'),
],
),
),
if (_userController.ownerMid != null &&
_userController.mid != _userController.ownerMid) ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () {
showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: ReportPanel(
name: _userController.username,
mid: _mid,
),
),
);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 19,
color: Theme.of(context).colorScheme.error,
),
const SizedBox(width: 10),
Text(
'举报',
style: TextStyle(
color: Theme.of(context).colorScheme.error),
),
],
),
),
],
],
),
),
const SizedBox(width: 4),
],
);
Widget _errorWidget(msg) {
return errorWidget(
errMsg: msg,
callback: _userController.onReload,
);
}
Widget _buildUserInfo(LoadingState userState, [bool isV = true]) {
return switch (userState) {
Loading() => const CircularProgressIndicator(),
Success() => userState.response is Data
? Obx(
() => UserInfoCard(
isV: isV,
isOwner: _userController.mid == _userController.ownerMid,
relation: _userController.relation.value,
isFollow: _userController.isFollow.value,
card: userState.response.card,
images: userState.response.images,
onFollow: () => _userController.onFollow(context),
live: _userController.live,
silence: _userController.silence,
endTime: _userController.endTime,
),
)
: GestureDetector(
onTap: _userController.onReload,
behavior: HitTestBehavior.opaque,
child: SizedBox(height: 56, width: double.infinity),
),
Error() => _errorWidget(userState.errMsg),
LoadingState() => throw UnimplementedError(),
};
}
}
class ReportPanel extends StatefulWidget {
const ReportPanel({
super.key,
required this.name,
required this.mid,
});
final dynamic name;
final dynamic mid;
@override
State<ReportPanel> createState() => _ReportPanelState();
}
class _ReportPanelState extends State<ReportPanel> {
final List<bool> _reasonList = List.generate(3, (_) => false).toList();
final Set<int> _reason = {};
int? _reasonV2;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'举报: ${widget.name}',
style: const TextStyle(fontSize: 18),
),
const SizedBox(height: 4),
Text('uid: ${widget.mid}'),
const SizedBox(height: 10),
const Text('举报内容(必选,可多选)'),
...List.generate(
3,
(index) => _checkBoxWidget(
_reasonList[index],
(value) {
setState(() => _reasonList[index] = value);
if (value) {
_reason.add(index + 1);
} else {
_reason.remove(index + 1);
}
},
['头像违规', '昵称违规', '签名违规'][index],
),
),
const Text('举报理由(单选,非必选)'),
...List.generate(
5,
(index) => RadioWidget<int>(
value: index,
groupValue: _reasonV2,
onChanged: (value) {
setState(() => _reasonV2 = value);
},
title: const ['色情低俗', '不实信息', '违禁', '人身攻击', '赌博诈骗'][index],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: Get.back,
child: Text(
'取消',
style:
TextStyle(color: Theme.of(context).colorScheme.outline),
),
),
TextButton(
onPressed: () async {
if (_reason.isEmpty) {
SmartDialog.showToast('至少选择一项作为举报内容');
} else {
Get.back();
dynamic result = await MemberHttp.reportMember(
widget.mid,
reason: _reason.join(','),
reasonV2: _reasonV2 != null ? _reasonV2! + 1 : null,
);
if (result['msg'] is String && result['msg'].isNotEmpty) {
SmartDialog.showToast(result['msg']);
} else {
SmartDialog.showToast('举报失败');
}
}
},
child: const Text('确定'),
),
],
),
],
),
);
}
}
Widget _checkBoxWidget(
bool defValue,
ValueChanged onChanged,
String title,
) {
return InkWell(
onTap: () => onChanged(!defValue),
child: Row(
children: [
Checkbox(
value: defValue,
onChanged: onChanged,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
Text(title),
],
),
);
}