opt member page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2025-08-13 10:29:26 +08:00
parent aea1992f5d
commit 086c93d24f
6 changed files with 299 additions and 512 deletions

View File

@@ -42,6 +42,7 @@ class _DynTopicPageState extends State<DynTopicPage> {
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final padding = MediaQuery.paddingOf(context);
return Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton.extended(
@@ -70,7 +71,13 @@ class _DynTopicPageState extends State<DynTopicPage> {
controller: _controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
Obx(() => _buildAppBar(theme, _controller.topState.value)),
Obx(
() => _buildAppBar(
theme,
padding.top,
_controller.topState.value,
),
),
Obx(() {
final allSortBy = _controller.topicSortByConf.value?.allSortBy;
if (allSortBy != null && allSortBy.isNotEmpty) {
@@ -133,9 +140,7 @@ class _DynTopicPageState extends State<DynTopicPage> {
return const SliverToBoxAdapter();
}),
SliverPadding(
padding: EdgeInsets.only(
bottom: MediaQuery.paddingOf(context).bottom + 80,
),
padding: EdgeInsets.only(bottom: padding.bottom + 80),
sliver: Obx(() => _buildBody(_controller.loadingState.value)),
),
],
@@ -145,15 +150,18 @@ class _DynTopicPageState extends State<DynTopicPage> {
);
}
Widget _buildAppBar(ThemeData theme, LoadingState<TopDetails?> topState) {
late final paddingTop = MediaQuery.paddingOf(context).top;
Widget _buildAppBar(
ThemeData theme,
double paddingTop,
LoadingState<TopDetails?> topState,
) {
return switch (topState) {
Loading() => const SliverAppBar(),
Success(:var response) when (topState.dataOrNull != null) =>
DynamicSliverAppBarMedium(
pinned: true,
callback: (value) => _controller.appbarOffset =
value - kToolbarHeight - paddingTop - 7,
callback: (value) =>
_controller.appbarOffset = value - kToolbarHeight - paddingTop,
title: IgnorePointer(child: Text(response!.topicItem!.name)),
flexibleSpace: Container(
decoration: BoxDecoration(

View File

@@ -47,7 +47,6 @@ class MemberController extends CommonDataController<SpaceData, SpaceData?>
final fromViewAid = Get.parameters['from_view_aid'];
final key = GlobalKey<ExtendedNestedScrollViewState>();
int offset = 120;
@override
void onInit() {

View File

@@ -1,5 +1,5 @@
import 'package:PiliPlus/common/widgets/dialog/report_member.dart';
import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar.dart';
import 'package:PiliPlus/common/widgets/dynamic_sliver_appbar_medium.dart';
import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart';
import 'package:PiliPlus/common/widgets/scroll_physics.dart';
import 'package:PiliPlus/http/loading_state.dart';
@@ -43,277 +43,236 @@ class _MemberPageState extends State<MemberPage> {
MemberController(mid: _mid),
tag: _heroTag,
);
_userController.scrollController.addListener(listener);
}
void listener() {
if (_userController.scrollController.hasClients) {
_userController.showUname.value =
_userController.scrollController.offset >= _userController.offset;
}
}
@override
void dispose() {
_userController.scrollController.removeListener(listener);
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Scaffold(
extendBody: true,
extendBodyBehindAppBar: true,
resizeToAvoidBottomInset: false,
appBar: AppBar(
forceMaterialTransparency: true,
title: IgnorePointer(
child: Obx(
() =>
_userController.showUname.value &&
_userController.username != null
? Text(_userController.username!)
: const SizedBox.shrink(),
),
),
actions: [
IconButton(
tooltip: '搜索',
onPressed: () => Get.toNamed(
'/memberSearch?mid=$_mid&uname=${_userController.username}',
),
icon: const Icon(Icons.search_outlined),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_userController.accountService.isLogin.value &&
_userController.accountService.mid != _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 != 128
? '加入黑名单'
: '移除黑名单',
),
],
),
return Material(
color: theme.colorScheme.surface,
child: Obx(() {
if (_userController.loadingState.value.isSuccess) {
return ExtendedNestedScrollView(
key: _userController.key,
controller: _userController.scrollController,
onlyOneScrollInBody: true,
pinnedHeaderSliverHeightBuilder: () =>
kToolbarHeight + MediaQuery.paddingOf(context).top,
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
_buildUserInfo(
theme,
_userController.loadingState.value,
),
if (_userController.isFollowed == 1)
PopupMenuItem(
onTap: _userController.onRemoveFan,
child: const Row(
mainAxisSize: MainAxisSize.min,
];
},
body: _userController.tab2?.isNotEmpty == true
? SafeArea(
top: false,
bottom: false,
child: Column(
children: [
Icon(Icons.remove_circle_outline_outlined, size: 19),
SizedBox(width: 10),
Text('移除粉丝'),
if ((_userController.tab2?.length ?? 0) > 1)
TabBar(
controller: _userController.tabController,
tabs: _userController.tabs,
onTap: _userController.onTapTab,
),
Expanded(child: _buildBody),
],
),
),
],
PopupMenuItem(
onTap: () => _userController.shareUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.share_outlined, size: 19),
const SizedBox(width: 10),
Text(
_userController.accountService.mid != _mid
? '分享UP主'
: '分享我的主页',
),
],
),
),
PopupMenuItem(
onTap: () => Get.toNamed(
'/upowerRank',
parameters: {
'mid': _userController.mid.toString(),
},
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.electric_bolt, size: 19),
SizedBox(width: 10),
Text('充电排行榜'),
],
),
),
if (_userController.accountService.isLogin.value)
if (_userController.mid ==
_userController.accountService.mid) ...[
if ((_userController
.loadingState
.value
.dataOrNull
?.card
?.vip
?.status ??
0) >
0)
PopupMenuItem(
onTap: _userController.vipExpAdd,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.upcoming_outlined, size: 19),
SizedBox(width: 10),
Text('大会员经验'),
],
),
),
PopupMenuItem(
onTap: () => Get.to(
const LogPage(),
arguments: LoginLogController(),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.login, size: 18),
SizedBox(width: 10),
Text('登录记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.to(
const LogPage(),
arguments: CoinLogController(),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(FontAwesomeIcons.b, size: 16),
SizedBox(width: 10),
Text('硬币记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.to(
const LogPage(),
arguments: ExpLogController(),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.linear_scale, size: 18),
SizedBox(width: 10),
Text('经验记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.toNamed('/spaceSetting'),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.settings_outlined, size: 19),
SizedBox(width: 10),
Text('空间设置'),
],
),
),
] else ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: MemberReportPanel(
name: _userController.username,
mid: _mid,
),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 19,
color: theme.colorScheme.error,
),
const SizedBox(width: 10),
Text(
'举报',
style: TextStyle(color: theme.colorScheme.error),
),
],
),
),
],
],
),
const SizedBox(width: 4),
],
),
body: Obx(
() => _userController.loadingState.value.isSuccess
? LayoutBuilder(
builder: (context, constraints) {
return ExtendedNestedScrollView(
key: _userController.key,
controller: _userController.scrollController,
onlyOneScrollInBody: true,
pinnedHeaderSliverHeightBuilder: () {
return kToolbarHeight +
MediaQuery.paddingOf(this.context).top.toInt();
},
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
_buildAppBar(
isV: constraints.maxHeight > constraints.maxWidth,
),
];
},
body: _userController.tab2?.isNotEmpty == true
? SafeArea(
top: false,
bottom: false,
child: Column(
children: [
if ((_userController.tab2?.length ?? 0) > 1)
_buildTab(theme),
Expanded(child: _buildBody),
],
),
)
: const Center(child: Text('EMPTY')),
);
},
)
: Center(
child: _buildUserInfo(_userController.loadingState.value),
),
),
)
: const Center(child: Text('EMPTY')),
);
}
return Center(
child: _buildUserInfo(theme, _userController.loadingState.value),
);
}),
);
}
Widget _buildTab(ThemeData theme) => Material(
color: theme.colorScheme.surface,
child: TabBar(
controller: _userController.tabController,
tabs: _userController.tabs,
onTap: _userController.onTapTab,
List<Widget> _actions(ThemeData theme) => [
IconButton(
tooltip: '搜索',
onPressed: () => Get.toNamed(
'/memberSearch?mid=$_mid&uname=${_userController.username}',
),
icon: const Icon(Icons.search_outlined),
),
);
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
if (_userController.accountService.isLogin.value &&
_userController.accountService.mid != _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 != 128 ? '加入黑名单' : '移除黑名单',
),
],
),
),
if (_userController.isFollowed == 1)
PopupMenuItem(
onTap: _userController.onRemoveFan,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.remove_circle_outline_outlined, size: 19),
SizedBox(width: 10),
Text('移除粉丝'),
],
),
),
],
PopupMenuItem(
onTap: () => _userController.shareUser(),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.share_outlined, size: 19),
const SizedBox(width: 10),
Text(
_userController.accountService.mid != _mid ? '分享UP主' : '分享我的主页',
),
],
),
),
PopupMenuItem(
onTap: () => Get.toNamed(
'/upowerRank',
parameters: {
'mid': _userController.mid.toString(),
},
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.electric_bolt, size: 19),
SizedBox(width: 10),
Text('充电排行榜'),
],
),
),
if (_userController.accountService.isLogin.value)
if (_userController.mid == _userController.accountService.mid) ...[
if ((_userController
.loadingState
.value
.dataOrNull
?.card
?.vip
?.status ??
0) >
0)
PopupMenuItem(
onTap: _userController.vipExpAdd,
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.upcoming_outlined, size: 19),
SizedBox(width: 10),
Text('大会员经验'),
],
),
),
PopupMenuItem(
onTap: () => Get.to(
const LogPage(),
arguments: LoginLogController(),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.login, size: 18),
SizedBox(width: 10),
Text('登录记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.to(
const LogPage(),
arguments: CoinLogController(),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(FontAwesomeIcons.b, size: 16),
SizedBox(width: 10),
Text('硬币记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.to(
const LogPage(),
arguments: ExpLogController(),
),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.linear_scale, size: 18),
SizedBox(width: 10),
Text('经验记录'),
],
),
),
PopupMenuItem(
onTap: () => Get.toNamed('/spaceSetting'),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.settings_outlined, size: 19),
SizedBox(width: 10),
Text('空间设置'),
],
),
),
] else ...[
const PopupMenuDivider(),
PopupMenuItem(
onTap: () => showDialog(
context: context,
builder: (context) => AlertDialog(
clipBehavior: Clip.hardEdge,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
content: MemberReportPanel(
name: _userController.username,
mid: _mid,
),
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
Icons.error_outline,
size: 19,
color: theme.colorScheme.error,
),
const SizedBox(width: 10),
Text(
'举报',
style: TextStyle(color: theme.colorScheme.error),
),
],
),
),
],
],
),
const SizedBox(width: 4),
];
Widget get _buildBody => tabBarView(
controller: _userController.tabController,
@@ -345,48 +304,44 @@ class _MemberPageState extends State<MemberPage> {
}).toList(),
);
Widget _buildAppBar({bool isV = true}) {
final top = MediaQuery.paddingOf(context).top;
return DynamicSliverAppBar(
pinned: true,
primary: false,
automaticallyImplyLeading: false,
toolbarHeight: kToolbarHeight + top,
flexibleSpace: _buildUserInfo(_userController.loadingState.value, isV),
callback: (value) {
_userController.offset = (value - 56 - top).toInt();
listener();
},
);
}
Widget _buildUserInfo(LoadingState<SpaceData?> userState, [bool isV = true]) {
return switch (userState) {
Loading() => const CircularProgressIndicator(),
Success(:var response) =>
response != null
? Obx(
() => UserInfoCard(
isV: isV,
isOwner:
_userController.mid == _userController.accountService.mid,
relation: _userController.relation.value,
card: response.card!,
images: response.images!,
onFollow: () => _userController.onFollow(context),
live: _userController.live,
silence: _userController.silence,
),
)
: GestureDetector(
onTap: _userController.onReload,
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 56, width: double.infinity),
Widget _buildUserInfo(ThemeData theme, LoadingState<SpaceData?> userState) {
switch (userState) {
case Loading():
return const CircularProgressIndicator();
case Success<SpaceData?>(:var response):
if (response != null) {
return DynamicSliverAppBarMedium(
pinned: true,
actions: _actions(theme),
title: Text(_userController.username ?? ''),
flexibleSpace: Obx(
() => UserInfoCard(
isOwner:
_userController.mid == _userController.accountService.mid,
relation: _userController.relation.value,
card: response.card!,
images: response.images!,
onFollow: () => _userController.onFollow(context),
live: _userController.live,
silence: _userController.silence,
),
Error(:var errMsg) => scrollErrorWidget(
errMsg: errMsg,
onReload: _userController.onReload,
),
};
),
);
}
return SliverAppBar(
pinned: true,
actions: _actions(theme),
title: GestureDetector(
onTap: _userController.onReload,
behavior: HitTestBehavior.opaque,
child: Text(_userController.username ?? ''),
),
);
case Error(:var errMsg):
return scrollErrorWidget(
errMsg: errMsg,
onReload: _userController.onReload,
);
}
}
}

View File

@@ -5,6 +5,7 @@ import 'package:PiliPlus/models_new/space/space/card.dart';
import 'package:PiliPlus/models_new/space/space/images.dart';
import 'package:PiliPlus/models_new/space/space/live.dart';
import 'package:PiliPlus/utils/accounts.dart';
import 'package:PiliPlus/utils/context_ext.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/image_util.dart';
import 'package:PiliPlus/utils/num_util.dart';
@@ -12,12 +13,11 @@ import 'package:PiliPlus/utils/page_utils.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:get/get.dart' hide ContextExtensionss;
class UserInfoCard extends StatelessWidget {
const UserInfoCard({
super.key,
required this.isV,
required this.isOwner,
required this.card,
required this.images,
@@ -27,7 +27,6 @@ class UserInfoCard extends StatelessWidget {
this.silence,
});
final bool isV;
final bool isOwner;
final int relation;
final SpaceCard card;
@@ -39,7 +38,9 @@ class UserInfoCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return isV ? _buildV(context, theme) : _buildH(context, theme);
return context.isPortrait
? _buildV(context, theme)
: _buildH(context, theme);
}
Widget _countWidget({
@@ -541,11 +542,8 @@ class UserInfoCard extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// _buildHeader(context),
SizedBox(
height: Get.mediaQuery.padding.top + 56,
),
const SizedBox(height: 56),
SafeArea(
top: false,
bottom: false,
child: Row(
children: [