Files
PiliPlus/lib/pages/mine/view.dart
bggRGjQaUbCoE 61bc4ce5b1 opt msg item
Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
2025-09-25 10:58:26 +08:00

550 lines
19 KiB
Dart

import 'dart:async';
import 'package:PiliPlus/common/constants.dart';
import 'package:PiliPlus/common/widgets/image/network_img_layer.dart';
import 'package:PiliPlus/common/widgets/list_tile.dart';
import 'package:PiliPlus/common/widgets/refresh_indicator.dart';
import 'package:PiliPlus/http/loading_state.dart';
import 'package:PiliPlus/models/common/image_type.dart';
import 'package:PiliPlus/models/common/nav_bar_config.dart';
import 'package:PiliPlus/models/user/info.dart';
import 'package:PiliPlus/models_new/fav/fav_folder/list.dart';
import 'package:PiliPlus/pages/common/common_page.dart';
import 'package:PiliPlus/pages/home/view.dart';
import 'package:PiliPlus/pages/login/controller.dart';
import 'package:PiliPlus/pages/main/controller.dart';
import 'package:PiliPlus/pages/mine/controller.dart';
import 'package:PiliPlus/pages/mine/widgets/item.dart';
import 'package:PiliPlus/utils/extension.dart';
import 'package:PiliPlus/utils/utils.dart';
import 'package:flutter/material.dart' hide ListTile;
import 'package:get/get.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
class MinePage extends StatefulWidget {
const MinePage({super.key});
@override
State<MinePage> createState() => _MediaPageState();
}
class _MediaPageState extends CommonPageState<MinePage, MineController>
with AutomaticKeepAliveClientMixin {
@override
MineController controller = Get.put(MineController());
late final MainController _mainController = Get.find<MainController>();
@override
bool get wantKeepAlive => true;
@override
void listener() {
if (_mainController.navigationBars[0] != NavigationBarType.mine &&
_mainController.selectedIndex.value == 0) {
return;
}
super.listener();
}
@override
Widget build(BuildContext context) {
super.build(context);
final theme = Theme.of(context);
final secondary = theme.colorScheme.secondary;
return Column(
children: [
const SizedBox(height: 10),
_buildHeaderActions,
const SizedBox(height: 10),
Expanded(
child: Material(
type: MaterialType.transparency,
child: refreshIndicator(
onRefresh: controller.onRefresh,
child: ListView(
padding: const EdgeInsets.only(bottom: 100),
controller: controller.scrollController,
physics: const AlwaysScrollableScrollPhysics(),
children: [
_buildUserInfo(theme, secondary),
_buildActions(secondary),
Obx(
() => controller.loadingState.value is Loading
? const SizedBox.shrink()
: _buildFav(theme, secondary),
),
],
),
),
),
),
],
);
}
Widget _buildActions(Color primary) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: controller.list
.map(
(e) => Flexible(
child: InkWell(
onTap: e.onTap,
borderRadius: StyleString.mdRadius,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 80),
child: AspectRatio(
aspectRatio: 1,
child: Column(
spacing: 6,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
size: 22,
e.icon,
color: primary,
),
Text(
e.title,
style: const TextStyle(fontSize: 13),
),
],
),
),
),
),
),
)
.toList(),
);
}
Widget get _buildHeaderActions {
return Row(
spacing: 5,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (!_mainController.hasHome) ...[
IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '搜索',
onPressed: () => Get.toNamed('/search'),
icon: const Icon(Icons.search),
),
msgBadge(_mainController),
],
Obx(
() {
final anonymity = MineController.anonymity.value;
return IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: "${anonymity ? '退出' : '进入'}无痕模式",
onPressed: MineController.onChangeAnonymity,
icon: anonymity
? const Icon(MdiIcons.incognito)
: const Icon(MdiIcons.incognitoOff),
);
},
),
IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '设置账号模式',
onPressed: () => LoginPageController.switchAccountDialog(context),
icon: const Icon(Icons.switch_account_outlined),
),
Obx(
() {
return IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '切换至${controller.nextThemeType.desc}主题',
onPressed: controller.onChangeTheme,
icon: controller.themeType.value.icon,
);
},
),
IconButton(
iconSize: 22,
padding: const EdgeInsets.all(8),
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
tooltip: '设置',
onPressed: () => Get.toNamed('/setting', preventDuplicates: false),
icon: const Icon(Icons.settings_outlined),
),
const SizedBox(width: 16),
],
);
}
Widget _buildUserInfo(ThemeData theme, Color secondary) {
final style = TextStyle(
fontSize: theme.textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold,
);
final lebelStyle = theme.textTheme.labelMedium!.copyWith(
color: theme.colorScheme.outline,
);
final coinLabelStyle = TextStyle(
fontSize: theme.textTheme.labelMedium!.fontSize,
color: theme.colorScheme.outline,
);
final coinValStyle = TextStyle(
fontSize: theme.textTheme.labelMedium!.fontSize,
fontWeight: FontWeight.bold,
color: secondary,
);
return Obx(() {
final UserInfoData userInfo = controller.userInfo.value;
final LevelInfo? levelInfo = userInfo.levelInfo;
final hasLevel = levelInfo != null;
final isVip = userInfo.vipStatus != null && userInfo.vipStatus! > 0;
final userStat = controller.userStat.value;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: controller.onLogin,
onLongPress: () {
Feedback.forLongPress(context);
controller.onLogin(true);
},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 20),
userInfo.face != null
? Stack(
clipBehavior: Clip.none,
children: [
NetworkImgLayer(
src: userInfo.face,
semanticsLabel: '头像',
type: ImageType.avatar,
width: 55,
height: 55,
),
if (isVip)
Positioned(
right: -1,
bottom: -2,
child: Image.asset(
'assets/images/big-vip.png',
height: 19,
semanticLabel: "大会员",
),
),
],
)
: ClipOval(
child: Image.asset(
width: 55,
height: 55,
'assets/images/noface.jpeg',
semanticLabel: "默认头像",
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
spacing: 6,
children: [
Flexible(
child: Text(
userInfo.uname ?? '点击登录',
style: theme.textTheme.titleMedium!.copyWith(
height: 1,
color: isVip && userInfo.vipType == 2
? theme.colorScheme.vipColor
: null,
),
),
),
Image.asset(
'assets/images/lv/lv${levelInfo == null
? 0
: userInfo.isSeniorMember == 1
? '6_s'
: levelInfo.currentLevel}.png',
height: 10,
),
],
),
const SizedBox(height: 8),
Text.rich(
TextSpan(
children: [
TextSpan(
text: '硬币 ',
style: coinLabelStyle,
),
TextSpan(
text: userInfo.money?.toString() ?? '-',
style: coinValStyle,
),
TextSpan(
text: " 经验 ",
style: coinLabelStyle,
),
TextSpan(
text: levelInfo?.currentExp?.toString() ?? '-',
style: coinValStyle,
),
TextSpan(
text: "/${levelInfo?.nextExp ?? '-'}",
style: coinLabelStyle,
),
],
),
),
const SizedBox(height: 4),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 225),
child: LinearProgressIndicator(
minHeight: 2.25,
value: hasLevel
? levelInfo.currentExp! / levelInfo.nextExp!
: 0,
trackGap: hasLevel ? null : 0,
backgroundColor: theme.colorScheme.outline.withValues(
alpha: 0.4,
),
valueColor: AlwaysStoppedAnimation<Color>(secondary),
stopIndicatorColor: Colors.transparent,
),
),
],
),
),
const SizedBox(width: 20),
],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_btn(
count: userStat.dynamicCount,
countStyle: style,
name: '动态',
lebelStyle: lebelStyle,
onTap: () => controller.push('memberDynamics'),
),
_btn(
count: userStat.following,
countStyle: style,
name: '关注',
lebelStyle: lebelStyle,
onTap: () => controller.push('follow'),
),
_btn(
count: userStat.follower,
countStyle: style,
name: '粉丝',
lebelStyle: lebelStyle,
onTap: () => controller.push('fan'),
),
],
),
],
);
});
}
Widget _btn({
required int? count,
required TextStyle countStyle,
required String name,
required TextStyle? lebelStyle,
required VoidCallback onTap,
}) {
return Flexible(
child: InkWell(
onTap: onTap,
borderRadius: StyleString.mdRadius,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 80),
child: AspectRatio(
aspectRatio: 1,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
count?.toString() ?? '-',
style: countStyle,
),
const SizedBox(height: 4),
Text(
name,
style: lebelStyle,
),
],
),
),
),
),
);
}
Widget _buildFav(ThemeData theme, Color secondary) {
return Column(
children: [
Divider(
height: 20,
color: theme.dividerColor.withValues(alpha: 0.1),
),
ListTile(
onTap: () => Get.toNamed('/fav')?.whenComplete(
() => Future.delayed(
const Duration(milliseconds: 150),
controller.onRefresh,
),
),
dense: true,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: '我的收藏 ',
style: TextStyle(
fontSize: theme.textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold,
),
),
if (controller.favFoldercount != null)
TextSpan(
text: "${controller.favFoldercount} ",
style: TextStyle(
fontSize: theme.textTheme.titleSmall!.fontSize,
color: secondary,
),
),
WidgetSpan(
child: Icon(
Icons.arrow_forward_ios,
size: 18,
color: secondary,
),
),
],
),
),
),
trailing: IconButton(
tooltip: '刷新',
onPressed: controller.onRefresh,
icon: const Icon(Icons.refresh, size: 20),
),
),
_buildFavBody(theme, secondary, controller.loadingState.value),
],
);
}
Widget _buildFavBody(
ThemeData theme,
Color secondary,
LoadingState loadingState,
) {
return switch (loadingState) {
Loading() => const SizedBox.shrink(),
Success(:var response) => Builder(
builder: (context) {
List<FavFolderInfo>? favFolderList = response.list;
if (favFolderList == null || favFolderList.isEmpty) {
return const SizedBox.shrink();
}
bool flag = (controller.favFoldercount ?? 0) > favFolderList.length;
return SizedBox(
height: 200,
child: ListView.separated(
padding: const EdgeInsets.only(left: 20, top: 12, right: 20),
itemCount: response.list.length + (flag ? 1 : 0),
itemBuilder: (context, index) {
if (flag && index == favFolderList.length) {
return Padding(
padding: const EdgeInsets.only(bottom: 35),
child: Center(
child: IconButton(
tooltip: '查看更多',
style: ButtonStyle(
padding: const WidgetStatePropertyAll(
EdgeInsets.zero,
),
backgroundColor: WidgetStatePropertyAll(
theme.colorScheme.secondaryContainer.withValues(
alpha: 0.5,
),
),
),
onPressed: () => Get.toNamed('/fav')?.whenComplete(
() => Future.delayed(
const Duration(milliseconds: 150),
controller.onRefresh,
),
),
icon: Icon(
Icons.arrow_forward_ios,
size: 18,
color: secondary,
),
),
),
);
} else {
return FavFolderItem(
heroTag: Utils.generateRandomString(8),
item: response.list[index],
callback: () => Future.delayed(
const Duration(milliseconds: 150),
controller.onRefresh,
),
);
}
},
scrollDirection: Axis.horizontal,
separatorBuilder: (context, index) => const SizedBox(width: 14),
),
);
},
),
Error(:var errMsg) => SizedBox(
height: 160,
child: Center(
child: Text(
errMsg ?? '',
textAlign: TextAlign.center,
),
),
),
};
}
}