opt: member page

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
bggRGjQaUbCoE
2024-11-20 15:58:11 +08:00
parent 5b02ca44f8
commit 67fc1b7d08
5 changed files with 432 additions and 358 deletions

View File

@@ -95,6 +95,7 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
// As long as the height is 0 instead of the sliver app bar a sliver to box adapter will be used // As long as the height is 0 instead of the sliver app bar a sliver to box adapter will be used
// to calculate dynamically the size for the sliver app bar // to calculate dynamically the size for the sliver app bar
double _height = 0; double _height = 0;
Orientation? _orientation;
@override @override
void initState() { void initState() {
@@ -105,6 +106,7 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
@override @override
void didUpdateWidget(covariant DynamicSliverAppBar oldWidget) { void didUpdateWidget(covariant DynamicSliverAppBar oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
_updateHeight(); _updateHeight();
} }
@@ -124,6 +126,11 @@ class _DynamicSliverAppBarState extends State<DynamicSliverAppBar> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
//Needed to lay out the flexibleSpace the first time, so we can calculate its intrinsic height //Needed to lay out the flexibleSpace the first time, so we can calculate its intrinsic height
Orientation orientation = MediaQuery.orientationOf(context);
if (_orientation != orientation) {
_orientation = orientation;
_height = 0;
}
if (_height == 0) { if (_height == 0) {
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Stack( child: Stack(

View File

@@ -50,7 +50,8 @@ class VideoCardH extends StatelessWidget {
excludeSemantics: true, excludeSemantics: true,
customSemanticsActions: <CustomSemanticsAction, void Function()>{ customSemanticsActions: <CustomSemanticsAction, void Function()>{
for (var item in actions) for (var item in actions)
CustomSemanticsAction(label: item.title): item.onTap!, CustomSemanticsAction(
label: item.title.isEmpty ? 'label' : item.title): item.onTap!,
}, },
child: InkWell( child: InkWell(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),

View File

@@ -24,19 +24,20 @@ class VideoCustomActions {
late List<VideoCustomAction> actions; late List<VideoCustomAction> actions;
VideoCustomActions(this.videoItem, this.context) { VideoCustomActions(this.videoItem, this.context) {
actions = [ actions = [
VideoCustomAction( if ((videoItem.bvid as String?)?.isNotEmpty == true)
videoItem.bvid, VideoCustomAction(
'copy', videoItem.bvid,
Stack( 'copy',
children: [ Stack(
Icon(MdiIcons.identifier, size: 16), children: [
Icon(MdiIcons.circleOutline, size: 16), Icon(MdiIcons.identifier, size: 16),
], Icon(MdiIcons.circleOutline, size: 16),
],
),
() {
Utils.copyText(videoItem.bvid);
},
), ),
() {
Utils.copyText(videoItem.bvid);
},
),
VideoCustomAction( VideoCustomAction(
'稍后再看', '稍后再看',
'pause', 'pause',

View File

@@ -40,7 +40,7 @@ class _MemberPageNewState extends State<MemberPageNew>
); );
_userController.scrollController.addListener(() { _userController.scrollController.addListener(() {
_userController.scrollRatio.value = _userController.scrollRatio.value =
min(1.0, _userController.scrollController.offset.round() / 150); min(1.0, _userController.scrollController.offset.round() / 120);
}); });
} }
@@ -53,71 +53,73 @@ class _MemberPageNewState extends State<MemberPageNew>
() => _userController.loadingState.value is Success () => _userController.loadingState.value is Success
? LayoutBuilder( ? LayoutBuilder(
builder: (_, constraints) { builder: (_, constraints) {
if (constraints.maxHeight > constraints.maxWidth) { // if (constraints.maxHeight > constraints.maxWidth) {
return ExtendedNestedScrollView( return ExtendedNestedScrollView(
controller: _userController.scrollController, controller: _userController.scrollController,
onlyOneScrollInBody: true, onlyOneScrollInBody: true,
headerSliverBuilder: (context, innerBoxIsScrolled) { headerSliverBuilder: (context, innerBoxIsScrolled) {
return [ return [
SliverOverlapAbsorber( SliverOverlapAbsorber(
handle: ExtendedNestedScrollView handle: ExtendedNestedScrollView
.sliverOverlapAbsorberHandleFor(context), .sliverOverlapAbsorberHandleFor(context),
sliver: _buildAppBar(), 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')),
);
} else {
return Row(
children: [
Expanded(
child: CustomScrollView(
slivers: [
_buildAppBar(false),
],
), ),
), ),
Expanded( ];
child: SafeArea( },
top: false, body: _userController.tab2?.isNotEmpty == true
left: false, ? LayoutBuilder(
bottom: false, builder: (context, _) {
child: Column( return Padding(
children: [ padding: EdgeInsets.only(
SizedBox(height: _userController.top), top: ExtendedNestedScrollView
if ((_userController.tab2?.length ?? -1) > 1) .sliverOverlapAbsorberHandleFor(
_buildTab, context)
Expanded( .layoutExtent ??
child: 0,
_userController.tab2?.isNotEmpty == true
? _buildBody
: Center(
child: const Text('EMPTY'),
),
), ),
], child: _buildBody,
), );
), },
), )
], : Center(child: const Text('EMPTY')),
); );
} // } else {
// return Row(
// children: [
// Expanded(
// child: CustomScrollView(
// slivers: [
// _buildAppBar(false),
// ],
// ),
// ),
// Expanded(
// child: SafeArea(
// top: false,
// left: false,
// bottom: false,
// child: Column(
// children: [
// SizedBox(height: _userController.top),
// if ((_userController.tab2?.length ?? -1) > 1)
// _buildTab,
// Expanded(
// child:
// _userController.tab2?.isNotEmpty == true
// ? _buildBody
// : Center(
// child: const Text('EMPTY'),
// ),
// ),
// ],
// ),
// ),
// ),
// ],
// );
// }
}, },
) )
: Center( : Center(
@@ -132,37 +134,44 @@ class _MemberPageNewState extends State<MemberPageNew>
tabs: _userController.tabs, tabs: _userController.tabs,
); );
Widget get _buildBody => TabBarView( Widget get _buildBody => Padding(
controller: _userController.tabController, padding: EdgeInsets.only(
children: _userController.tab2!.map((item) { left: MediaQuery.paddingOf(context).left,
return switch (item.param!) { right: MediaQuery.paddingOf(context).right,
'home' => MemberHome(heroTag: _heroTag), ),
// 'dynamic' => MemberDynamic(mid: _mid ?? -1), child: TabBarView(
'dynamic' => MemberDynamicsPage(mid: _mid), controller: _userController.tabController,
'contribute' => Obx( children: _userController.tab2!.map((item) {
() => MemberContribute( 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 ?? -1,
),
),
'bangumi' => MemberBangumi(
heroTag: _heroTag, heroTag: _heroTag,
initialIndex: _userController.contributeInitialIndex.value,
mid: _mid ?? -1, mid: _mid ?? -1,
), ),
), 'favorite' => MemberFavorite(
'bangumi' => MemberBangumi( heroTag: _heroTag,
heroTag: _heroTag, mid: _mid ?? -1,
mid: _mid ?? -1, ),
), _ => Center(child: Text(item.title ?? '')),
'favorite' => MemberFavorite( };
heroTag: _heroTag, }).toList(),
mid: _mid ?? -1, ),
),
_ => Center(child: Text(item.title ?? '')),
};
}).toList(),
); );
Widget _buildAppBar([bool needTab = true]) => MediaQuery.removePadding( Widget _buildAppBar({bool needTab = true, bool isV = true}) =>
MediaQuery.removePadding(
context: context, context: context,
removeTop: true, removeTop: true,
removeRight: true, // removeRight: true,
child: DynamicSliverAppBar( child: DynamicSliverAppBar(
leading: Padding( leading: Padding(
padding: EdgeInsets.only(top: _userController.top ?? 0), padding: EdgeInsets.only(top: _userController.top ?? 0),
@@ -178,7 +187,8 @@ class _MemberPageNewState extends State<MemberPageNew>
pinned: true, pinned: true,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
flexibleSpace: _buildUserInfo(_userController.loadingState.value), flexibleSpace:
_buildUserInfo(_userController.loadingState.value, isV),
bottom: needTab && (_userController.tab2?.length ?? -1) > 1 bottom: needTab && (_userController.tab2?.length ?? -1) > 1
? PreferredSize( ? PreferredSize(
preferredSize: Size.fromHeight(48), preferredSize: Size.fromHeight(48),
@@ -291,7 +301,7 @@ class _MemberPageNewState extends State<MemberPageNew>
); );
} }
Widget _buildUserInfo(LoadingState userState) { Widget _buildUserInfo(LoadingState userState, [bool isV = true]) {
switch (userState) { switch (userState) {
case Empty(): case Empty():
return _errorWidget('EMPTY'); return _errorWidget('EMPTY');
@@ -303,6 +313,7 @@ class _MemberPageNewState extends State<MemberPageNew>
padding: EdgeInsets.only( padding: EdgeInsets.only(
bottom: (_userController.tab2?.length ?? 0) > 1 ? 48 : 0), bottom: (_userController.tab2?.length ?? 0) > 1 ? 48 : 0),
child: UserInfoCard( child: UserInfoCard(
isV: isV,
isOwner: _userController.mid == _userController.ownerMid, isOwner: _userController.mid == _userController.ownerMid,
relation: _userController.relation.value, relation: _userController.relation.value,
isFollow: _userController.isFollow.value, isFollow: _userController.isFollow.value,

View File

@@ -12,6 +12,7 @@ import 'package:get/get.dart';
class UserInfoCard extends StatelessWidget { class UserInfoCard extends StatelessWidget {
const UserInfoCard({ const UserInfoCard({
super.key, super.key,
required this.isV,
required this.isOwner, required this.isOwner,
required this.card, required this.card,
required this.images, required this.images,
@@ -20,6 +21,7 @@ class UserInfoCard extends StatelessWidget {
required this.onFollow, required this.onFollow,
}); });
final bool isV;
final bool isOwner; final bool isOwner;
final int relation; final int relation;
final bool isFollow; final bool isFollow;
@@ -29,245 +31,79 @@ class UserInfoCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return isV ? _buildV(context) : _buildH(context);
}
Widget _countWidget({
required String title,
required int count,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
Utils.numFormat(count),
style: TextStyle(
fontSize: 14,
),
),
Text(
title,
style: TextStyle(
height: 1,
fontSize: 11,
color: Theme.of(Get.context!).colorScheme.outline,
),
),
],
),
);
}
_buildHeader(BuildContext context) {
bool darken = GStorage.brightness == Brightness.dark; bool darken = GStorage.brightness == Brightness.dark;
String? imgUrl = darken String? imgUrl = darken
? (images.nightImgurl?.isEmpty == true ? (images.nightImgurl?.isEmpty == true
? images.imgUrl?.http2https ? images.imgUrl?.http2https
: images.nightImgurl?.http2https) : images.nightImgurl?.http2https)
: images.imgUrl?.http2https; : images.imgUrl?.http2https;
return Column( return GestureDetector(
mainAxisAlignment: MainAxisAlignment.start, onTap: () {
crossAxisAlignment: CrossAxisAlignment.start, showDialog(
children: [ useSafeArea: false,
Stack( context: context,
children: [ builder: (context) {
Column( return ImagePreview(
mainAxisSize: MainAxisSize.min, initialPage: 0,
children: [ imgList: [imgUrl ?? ''],
GestureDetector( );
onTap: () { },
showDialog( );
useSafeArea: false, },
context: context, child: CachedNetworkImage(
builder: (context) { imageUrl: imgUrl ?? '',
return ImagePreview( width: double.infinity,
initialPage: 0, height: 135,
imgList: [imgUrl ?? ''], imageBuilder: (context, imageProvider) => Container(
); decoration: BoxDecoration(
}, image: DecorationImage(
); image: imageProvider,
}, fit: BoxFit.cover,
child: CachedNetworkImage( colorFilter: ColorFilter.mode(
imageUrl: imgUrl ?? '', darken ? const Color(0x8D000000) : const Color(0x5DFFFFFF),
width: double.infinity, darken ? BlendMode.darken : BlendMode.lighten,
height: 135,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
colorFilter: ColorFilter.mode(
darken
? const Color(0x8D000000)
: const Color(0x5DFFFFFF),
darken ? BlendMode.darken : BlendMode.lighten,
),
),
),
),
),
),
const SizedBox(width: double.infinity, height: 85)
],
),
Positioned(
top: 110,
left: 20,
child: GestureDetector(
onTap: () {
showDialog(
useSafeArea: false,
context: context,
builder: (context) {
return ImagePreview(
initialPage: 0,
imgList: [card.face ?? ''],
);
},
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 2.5,
color: Theme.of(context).colorScheme.surface,
),
shape: BoxShape.circle,
),
child: NetworkImgLayer(
src: card.face,
type: 'avatar',
width: 80,
height: 80,
),
),
), ),
), ),
if (card.officialVerify?.icon?.isNotEmpty == true || ),
(card.vip?.vipStatus ?? -1) > 0)
Positioned(
top: 170,
left: 80,
child: Container(
padding: const EdgeInsets.all(0.01),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
),
child: card.officialVerify?.icon?.isNotEmpty == true
? NetworkImgLayer(
src: card.officialVerify?.icon,
radius: null,
width: 24,
height: 24,
quality: 100,
)
: Image.asset(
'assets/images/big-vip.png',
width: 24,
height: 24,
),
),
),
Positioned(
top: 140,
right: 20,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(
5,
(index) => index % 2 == 0
? Padding(
padding:
const EdgeInsets.symmetric(horizontal: 20),
child: _countWidget(
title: ['粉丝', '关注', '获赞'][index ~/ 2],
count: index == 0
? card.fans
: index == 2
? card.attention
: card.likes?.likeNum ?? 0,
onTap: () {
if (index == 0) {
Get.toNamed(
'/fan?mid=${card.mid}&name=${card.name}');
} else if (index == 2) {
Get.toNamed(
'/follow?mid=${card.mid}&name=${card.name}');
}
},
),
)
: SizedBox(
height: 15,
width: 1,
child: VerticalDivider(),
),
),
),
const SizedBox(height: 5),
Row(
mainAxisSize: MainAxisSize.min,
children: [
if (!isOwner)
IconButton.outlined(
onPressed: () {
if (GStorage.userInfo.get('userInfoCache') !=
null) {
Get.toNamed(
'/whisperDetail',
parameters: {
'talkerId': card.mid ?? '',
'name': card.name ?? '',
'face': card.face ?? '',
'mid': card.mid ?? '',
},
);
}
},
icon: const Icon(Icons.mail_outline, size: 21),
style: IconButton.styleFrom(
side: BorderSide(
width: 1.0,
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.5),
),
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(
horizontal: -2,
vertical: -2,
),
),
),
const SizedBox(width: 10),
FilledButton.tonal(
onPressed: onFollow,
style: FilledButton.styleFrom(
backgroundColor: relation == -1 || isFollow
? Theme.of(context).colorScheme.onInverseSurface
: null,
padding: const EdgeInsets.symmetric(horizontal: 50),
visualDensity: const VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: Text.rich(
style: TextStyle(
color: relation == -1 || isFollow
? Theme.of(context).colorScheme.outline
: null,
),
TextSpan(
children: [
if (isFollow)
WidgetSpan(
alignment: PlaceholderAlignment.top,
child: Icon(
Icons.sort,
size: 16,
color:
Theme.of(context).colorScheme.outline,
),
),
TextSpan(
text: isOwner
? '编辑资料'
: relation == -1
? '移除黑名单'
: relation == 2
? ' 特别关注'
: isFollow
? ' 已关注'
: '关注',
)
],
),
),
),
],
),
],
),
),
],
), ),
),
);
}
_buildLeft(BuildContext context) => [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 20), padding: const EdgeInsets.symmetric(horizontal: 20),
child: Wrap( child: Wrap(
@@ -434,37 +270,255 @@ class UserInfoCard extends StatelessWidget {
// .toList(), // .toList(),
// ), // ),
// ), // ),
const SizedBox(height: 5), ];
],
);
}
Widget _countWidget({ _buildRight(BuildContext context) => Column(
required String title,
required int count,
required VoidCallback onTap,
}) {
return GestureDetector(
onTap: onTap,
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Row(
Utils.numFormat(count), mainAxisAlignment: MainAxisAlignment.spaceEvenly,
style: TextStyle( children: List.generate(
fontSize: 14, 5,
(index) => index % 2 == 0
? Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: _countWidget(
title: ['粉丝', '关注', '获赞'][index ~/ 2],
count: index == 0
? card.fans
: index == 2
? card.attention
: card.likes?.likeNum ?? 0,
onTap: () {
if (index == 0) {
Get.toNamed(
'/fan?mid=${card.mid}&name=${card.name}');
} else if (index == 2) {
Get.toNamed(
'/follow?mid=${card.mid}&name=${card.name}');
}
},
),
)
: SizedBox(
height: 15,
width: 1,
child: VerticalDivider(),
),
), ),
), ),
Text( const SizedBox(height: 5),
title, Row(
style: TextStyle( mainAxisSize: MainAxisSize.min,
height: 1, children: [
fontSize: 11, if (!isOwner)
color: Theme.of(Get.context!).colorScheme.outline, IconButton.outlined(
), onPressed: () {
if (GStorage.userInfo.get('userInfoCache') != null) {
Get.toNamed(
'/whisperDetail',
parameters: {
'talkerId': card.mid ?? '',
'name': card.name ?? '',
'face': card.face ?? '',
'mid': card.mid ?? '',
},
);
}
},
icon: const Icon(Icons.mail_outline, size: 21),
style: IconButton.styleFrom(
side: BorderSide(
width: 1.0,
color: Theme.of(context)
.colorScheme
.outline
.withOpacity(0.5),
),
padding: EdgeInsets.zero,
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: const VisualDensity(
horizontal: -2,
vertical: -2,
),
),
),
const SizedBox(width: 10),
FilledButton.tonal(
onPressed: onFollow,
style: FilledButton.styleFrom(
backgroundColor: relation == -1 || isFollow
? Theme.of(context).colorScheme.onInverseSurface
: null,
padding: const EdgeInsets.symmetric(horizontal: 50),
visualDensity: const VisualDensity(
horizontal: -2,
vertical: -2,
),
),
child: Text.rich(
style: TextStyle(
color: relation == -1 || isFollow
? Theme.of(context).colorScheme.outline
: null,
),
TextSpan(
children: [
if (isFollow)
WidgetSpan(
alignment: PlaceholderAlignment.top,
child: Icon(
Icons.sort,
size: 16,
color: Theme.of(context).colorScheme.outline,
),
),
TextSpan(
text: isOwner
? '编辑资料'
: relation == -1
? '移除黑名单'
: relation == 2
? ' 特别关注'
: isFollow
? ' 已关注'
: '关注',
)
],
),
),
),
],
), ),
], ],
), );
);
} _buildBadge(BuildContext context) => Container(
padding: const EdgeInsets.all(0.01),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface,
),
child: card.officialVerify?.icon?.isNotEmpty == true
? NetworkImgLayer(
src: card.officialVerify?.icon,
radius: null,
width: 24,
height: 24,
quality: 100,
)
: Image.asset(
'assets/images/big-vip.png',
width: 24,
height: 24,
),
);
_buildAvatar(BuildContext context) => GestureDetector(
onTap: () {
showDialog(
useSafeArea: false,
context: context,
builder: (context) {
return ImagePreview(
initialPage: 0,
imgList: [card.face ?? ''],
);
},
);
},
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 2.5,
color: Theme.of(context).colorScheme.surface,
),
shape: BoxShape.circle,
),
child: NetworkImgLayer(
src: card.face,
type: 'avatar',
width: 80,
height: 80,
),
),
);
_buildV(BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Stack(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildHeader(context),
const SizedBox(width: double.infinity, height: 85)
],
),
Positioned(
top: 110,
left: 20,
child: _buildAvatar(context),
),
if (card.officialVerify?.icon?.isNotEmpty == true ||
(card.vip?.vipStatus ?? -1) > 0)
Positioned(
top: 170,
left: 80,
child: _buildBadge(context),
),
Positioned(
top: 140,
right: 20,
child: _buildRight(context),
),
],
),
..._buildLeft(context),
const SizedBox(height: 5),
],
);
_buildH(BuildContext context) => Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildHeader(context),
Row(
children: [
SizedBox(width: MediaQuery.paddingOf(context).left),
const SizedBox(width: 20),
Stack(
children: [
_buildAvatar(context),
if (card.officialVerify?.icon?.isNotEmpty == true ||
(card.vip?.vipStatus ?? -1) > 0)
Positioned(
right: 0,
bottom: 0,
child: _buildBadge(context),
),
],
),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10),
..._buildLeft(context),
const SizedBox(height: 5),
],
),
),
Expanded(child: _buildRight(context)),
SizedBox(width: MediaQuery.paddingOf(context).right),
],
),
],
);
} }