feat(mining-app): 资产页面优化及个人资料编辑功能
- 删除资产页面的"提现"按钮,将"划转"改为"C2C" - 删除积分值卡片上的"可提现"标签 - 简化资产页面和兑换页面的标题栏,移除左右图标 - 统一资产页面背景色与兑换页面一致 - 新增个人资料编辑页面,支持头像颜色选择和昵称修改 - 头像和昵称支持本地存储 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
854bb7a0ac
commit
3096297198
|
|
@ -11,6 +11,7 @@ import '../../presentation/pages/contribution/contribution_records_page.dart';
|
||||||
import '../../presentation/pages/trading/trading_page.dart';
|
import '../../presentation/pages/trading/trading_page.dart';
|
||||||
import '../../presentation/pages/asset/asset_page.dart';
|
import '../../presentation/pages/asset/asset_page.dart';
|
||||||
import '../../presentation/pages/profile/profile_page.dart';
|
import '../../presentation/pages/profile/profile_page.dart';
|
||||||
|
import '../../presentation/pages/profile/edit_profile_page.dart';
|
||||||
import '../../presentation/pages/profile/mining_records_page.dart';
|
import '../../presentation/pages/profile/mining_records_page.dart';
|
||||||
import '../../presentation/pages/profile/planting_records_page.dart';
|
import '../../presentation/pages/profile/planting_records_page.dart';
|
||||||
import '../../presentation/widgets/main_shell.dart';
|
import '../../presentation/widgets/main_shell.dart';
|
||||||
|
|
@ -112,6 +113,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
||||||
path: Routes.plantingRecords,
|
path: Routes.plantingRecords,
|
||||||
builder: (context, state) => const PlantingRecordsPage(),
|
builder: (context, state) => const PlantingRecordsPage(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: Routes.editProfile,
|
||||||
|
builder: (context, state) => const EditProfilePage(),
|
||||||
|
),
|
||||||
ShellRoute(
|
ShellRoute(
|
||||||
builder: (context, state, child) => MainShell(child: child),
|
builder: (context, state, child) => MainShell(child: child),
|
||||||
routes: [
|
routes: [
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ class Routes {
|
||||||
static const String trading = '/trading';
|
static const String trading = '/trading';
|
||||||
static const String asset = '/asset';
|
static const String asset = '/asset';
|
||||||
static const String profile = '/profile';
|
static const String profile = '/profile';
|
||||||
|
static const String editProfile = '/edit-profile';
|
||||||
static const String miningRecords = '/mining-records';
|
static const String miningRecords = '/mining-records';
|
||||||
static const String contributionRecords = '/contribution-records';
|
static const String contributionRecords = '/contribution-records';
|
||||||
static const String plantingRecords = '/planting-records';
|
static const String plantingRecords = '/planting-records';
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ class AssetPage extends ConsumerWidget {
|
||||||
final asset = assetAsync.valueOrNull;
|
final asset = assetAsync.valueOrNull;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: Colors.white,
|
backgroundColor: const Color(0xFFF5F5F5),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
bottom: false,
|
bottom: false,
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,381 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
|
import '../../providers/user_providers.dart';
|
||||||
|
|
||||||
|
class EditProfilePage extends ConsumerStatefulWidget {
|
||||||
|
const EditProfilePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<EditProfilePage> createState() => _EditProfilePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EditProfilePageState extends ConsumerState<EditProfilePage> {
|
||||||
|
static const Color _orange = Color(0xFFFF6B00);
|
||||||
|
static const Color _green = Color(0xFF10B981);
|
||||||
|
static const Color _darkText = Color(0xFF1F2937);
|
||||||
|
static const Color _grayText = Color(0xFF6B7280);
|
||||||
|
static const Color _bgGray = Color(0xFFF3F4F6);
|
||||||
|
|
||||||
|
late TextEditingController _nicknameController;
|
||||||
|
int _selectedAvatarIndex = 0;
|
||||||
|
bool _isLoading = false;
|
||||||
|
|
||||||
|
// 预设头像颜色列表
|
||||||
|
static const List<Color> _avatarColors = [
|
||||||
|
Color(0xFFFF6B00), // 橙色
|
||||||
|
Color(0xFF10B981), // 绿色
|
||||||
|
Color(0xFF3B82F6), // 蓝色
|
||||||
|
Color(0xFFEF4444), // 红色
|
||||||
|
Color(0xFF8B5CF6), // 紫色
|
||||||
|
Color(0xFFF59E0B), // 琥珀色
|
||||||
|
Color(0xFFEC4899), // 粉色
|
||||||
|
Color(0xFF06B6D4), // 青色
|
||||||
|
];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final user = ref.read(userNotifierProvider);
|
||||||
|
_nicknameController = TextEditingController(text: user.nickname ?? '');
|
||||||
|
_selectedAvatarIndex = user.avatarIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_nicknameController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final user = ref.watch(userNotifierProvider);
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
backgroundColor: _bgGray,
|
||||||
|
appBar: AppBar(
|
||||||
|
backgroundColor: Colors.white,
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back, color: _darkText),
|
||||||
|
onPressed: () => context.pop(),
|
||||||
|
),
|
||||||
|
title: const Text(
|
||||||
|
'编辑资料',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _darkText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
centerTitle: true,
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _isLoading ? null : _saveProfile,
|
||||||
|
child: Text(
|
||||||
|
'保存',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: _isLoading ? _grayText : _orange,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 头像选择区域
|
||||||
|
_buildAvatarSection(user),
|
||||||
|
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
|
||||||
|
// 昵称输入区域
|
||||||
|
_buildNicknameSection(),
|
||||||
|
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
|
// 用户信息展示(只读)
|
||||||
|
_buildInfoSection(user),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAvatarSection(UserState user) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'选择头像',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _darkText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
|
||||||
|
// 当前头像预览
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
_avatarColors[_selectedAvatarIndex].withValues(alpha: 0.8),
|
||||||
|
_avatarColors[_selectedAvatarIndex],
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: _avatarColors[_selectedAvatarIndex].withValues(alpha: 0.3),
|
||||||
|
blurRadius: 12,
|
||||||
|
offset: const Offset(0, 4),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
_getAvatarText(user),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 42,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
const SizedBox(height: 24),
|
||||||
|
|
||||||
|
// 头像颜色选择网格
|
||||||
|
GridView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
|
crossAxisCount: 4,
|
||||||
|
crossAxisSpacing: 16,
|
||||||
|
mainAxisSpacing: 16,
|
||||||
|
),
|
||||||
|
itemCount: _avatarColors.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final isSelected = _selectedAvatarIndex == index;
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
_selectedAvatarIndex = index;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
gradient: LinearGradient(
|
||||||
|
colors: [
|
||||||
|
_avatarColors[index].withValues(alpha: 0.8),
|
||||||
|
_avatarColors[index],
|
||||||
|
],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
|
border: isSelected
|
||||||
|
? Border.all(color: _darkText, width: 3)
|
||||||
|
: null,
|
||||||
|
boxShadow: isSelected
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: _avatarColors[index].withValues(alpha: 0.4),
|
||||||
|
blurRadius: 8,
|
||||||
|
offset: const Offset(0, 2),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: isSelected
|
||||||
|
? const Icon(Icons.check, color: Colors.white, size: 28)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildNicknameSection() {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'昵称',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _darkText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
TextField(
|
||||||
|
controller: _nicknameController,
|
||||||
|
maxLength: 20,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
hintText: '请输入昵称',
|
||||||
|
hintStyle: const TextStyle(color: _grayText),
|
||||||
|
filled: true,
|
||||||
|
fillColor: _bgGray,
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: BorderSide.none,
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(color: _orange, width: 2),
|
||||||
|
),
|
||||||
|
counterStyle: const TextStyle(color: _grayText),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoSection(UserState user) {
|
||||||
|
return Container(
|
||||||
|
margin: const EdgeInsets.symmetric(horizontal: 16),
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.white,
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const Text(
|
||||||
|
'账户信息',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: _darkText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
_buildInfoItem('手机号', user.phone ?? '--'),
|
||||||
|
const Divider(height: 24),
|
||||||
|
_buildInfoItem('实名状态', user.isKycVerified ? '已实名' : '未实名',
|
||||||
|
valueColor: user.isKycVerified ? _green : _grayText),
|
||||||
|
if (user.realName != null && user.realName!.isNotEmpty) ...[
|
||||||
|
const Divider(height: 24),
|
||||||
|
_buildInfoItem('真实姓名', _maskName(user.realName!)),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInfoItem(String label, String value, {Color? valueColor}) {
|
||||||
|
return Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
label,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: _grayText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
value,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: valueColor ?? _darkText,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _getAvatarText(UserState user) {
|
||||||
|
final nickname = _nicknameController.text;
|
||||||
|
if (nickname.isNotEmpty) {
|
||||||
|
return nickname.substring(0, 1).toUpperCase();
|
||||||
|
}
|
||||||
|
if (user.realName?.isNotEmpty == true) {
|
||||||
|
return user.realName!.substring(0, 1).toUpperCase();
|
||||||
|
}
|
||||||
|
return 'U';
|
||||||
|
}
|
||||||
|
|
||||||
|
String _maskName(String name) {
|
||||||
|
if (name.length <= 1) return name;
|
||||||
|
return '${name.substring(0, 1)}${'*' * (name.length - 1)}';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveProfile() async {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
final notifier = ref.read(userNotifierProvider.notifier);
|
||||||
|
|
||||||
|
// 保存头像
|
||||||
|
await notifier.updateAvatar(_selectedAvatarIndex);
|
||||||
|
|
||||||
|
// 保存昵称
|
||||||
|
final nickname = _nicknameController.text.trim();
|
||||||
|
if (nickname.isNotEmpty) {
|
||||||
|
await notifier.updateNickname(nickname);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('保存成功'),
|
||||||
|
backgroundColor: _green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
context.pop();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (mounted) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text('保存失败: $e'),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
_isLoading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,18 @@ class ProfilePage extends ConsumerWidget {
|
||||||
static const Color _bgGray = Color(0xFFF3F4F6);
|
static const Color _bgGray = Color(0xFFF3F4F6);
|
||||||
static const Color _red = Color(0xFFEF4444);
|
static const Color _red = Color(0xFFEF4444);
|
||||||
|
|
||||||
|
// 预设头像颜色列表(与编辑页面保持一致)
|
||||||
|
static const List<Color> _avatarColors = [
|
||||||
|
Color(0xFFFF6B00), // 橙色
|
||||||
|
Color(0xFF10B981), // 绿色
|
||||||
|
Color(0xFF3B82F6), // 蓝色
|
||||||
|
Color(0xFFEF4444), // 红色
|
||||||
|
Color(0xFF8B5CF6), // 紫色
|
||||||
|
Color(0xFFF59E0B), // 琥珀色
|
||||||
|
Color(0xFFEC4899), // 粉色
|
||||||
|
Color(0xFF06B6D4), // 青色
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final user = ref.watch(userNotifierProvider);
|
final user = ref.watch(userNotifierProvider);
|
||||||
|
|
@ -94,34 +106,39 @@ class ProfilePage extends ConsumerWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUserHeader(BuildContext context, UserState user) {
|
Widget _buildUserHeader(BuildContext context, UserState user) {
|
||||||
|
final avatarColor = _avatarColors[user.avatarIndex % _avatarColors.length];
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(20),
|
padding: const EdgeInsets.all(20),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
// 头像
|
// 头像(可点击)
|
||||||
Container(
|
GestureDetector(
|
||||||
width: 80,
|
onTap: () => context.push(Routes.editProfile),
|
||||||
height: 80,
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
width: 80,
|
||||||
shape: BoxShape.circle,
|
height: 80,
|
||||||
gradient: LinearGradient(
|
decoration: BoxDecoration(
|
||||||
colors: [_orange.withValues(alpha: 0.8), _orange],
|
shape: BoxShape.circle,
|
||||||
begin: Alignment.topLeft,
|
gradient: LinearGradient(
|
||||||
end: Alignment.bottomRight,
|
colors: [avatarColor.withValues(alpha: 0.8), avatarColor],
|
||||||
|
begin: Alignment.topLeft,
|
||||||
|
end: Alignment.bottomRight,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
child: Center(
|
||||||
child: Center(
|
child: Text(
|
||||||
child: Text(
|
user.nickname?.isNotEmpty == true
|
||||||
user.nickname?.isNotEmpty == true
|
? user.nickname!.substring(0, 1).toUpperCase()
|
||||||
? user.nickname!.substring(0, 1).toUpperCase()
|
: (user.realName?.isNotEmpty == true
|
||||||
: (user.realName?.isNotEmpty == true
|
? user.realName!.substring(0, 1).toUpperCase()
|
||||||
? user.realName!.substring(0, 1).toUpperCase()
|
: 'U'),
|
||||||
: 'U'),
|
style: const TextStyle(
|
||||||
style: const TextStyle(
|
fontSize: 36,
|
||||||
fontSize: 36,
|
fontWeight: FontWeight.bold,
|
||||||
fontWeight: FontWeight.bold,
|
color: Colors.white,
|
||||||
color: Colors.white,
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -137,7 +154,9 @@ class ProfilePage extends ConsumerWidget {
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
user.realName ?? user.nickname ?? '榴莲用户',
|
user.nickname?.isNotEmpty == true
|
||||||
|
? user.nickname!
|
||||||
|
: (user.realName ?? '榴莲用户'),
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
|
@ -190,9 +209,7 @@ class ProfilePage extends ConsumerWidget {
|
||||||
|
|
||||||
// 编辑按钮
|
// 编辑按钮
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () {
|
onPressed: () => context.push(Routes.editProfile),
|
||||||
// TODO: 编辑个人资料
|
|
||||||
},
|
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.edit_outlined,
|
Icons.edit_outlined,
|
||||||
color: _grayText,
|
color: _grayText,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ class UserState {
|
||||||
final DateTime? lastLoginAt;
|
final DateTime? lastLoginAt;
|
||||||
final String? accessToken;
|
final String? accessToken;
|
||||||
final String? refreshToken;
|
final String? refreshToken;
|
||||||
|
final int avatarIndex;
|
||||||
final bool isLoggedIn;
|
final bool isLoggedIn;
|
||||||
final bool isLoading;
|
final bool isLoading;
|
||||||
final String? error;
|
final String? error;
|
||||||
|
|
@ -31,6 +32,7 @@ class UserState {
|
||||||
this.lastLoginAt,
|
this.lastLoginAt,
|
||||||
this.accessToken,
|
this.accessToken,
|
||||||
this.refreshToken,
|
this.refreshToken,
|
||||||
|
this.avatarIndex = 0,
|
||||||
this.isLoggedIn = false,
|
this.isLoggedIn = false,
|
||||||
this.isLoading = false,
|
this.isLoading = false,
|
||||||
this.error,
|
this.error,
|
||||||
|
|
@ -50,6 +52,7 @@ class UserState {
|
||||||
DateTime? lastLoginAt,
|
DateTime? lastLoginAt,
|
||||||
String? accessToken,
|
String? accessToken,
|
||||||
String? refreshToken,
|
String? refreshToken,
|
||||||
|
int? avatarIndex,
|
||||||
bool? isLoggedIn,
|
bool? isLoggedIn,
|
||||||
bool? isLoading,
|
bool? isLoading,
|
||||||
String? error,
|
String? error,
|
||||||
|
|
@ -66,6 +69,7 @@ class UserState {
|
||||||
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
|
lastLoginAt: lastLoginAt ?? this.lastLoginAt,
|
||||||
accessToken: accessToken ?? this.accessToken,
|
accessToken: accessToken ?? this.accessToken,
|
||||||
refreshToken: refreshToken ?? this.refreshToken,
|
refreshToken: refreshToken ?? this.refreshToken,
|
||||||
|
avatarIndex: avatarIndex ?? this.avatarIndex,
|
||||||
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
|
isLoggedIn: isLoggedIn ?? this.isLoggedIn,
|
||||||
isLoading: isLoading ?? this.isLoading,
|
isLoading: isLoading ?? this.isLoading,
|
||||||
error: error,
|
error: error,
|
||||||
|
|
@ -86,6 +90,8 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
final refreshToken = prefs.getString('refresh_token');
|
final refreshToken = prefs.getString('refresh_token');
|
||||||
final accountSequence = prefs.getString('account_sequence');
|
final accountSequence = prefs.getString('account_sequence');
|
||||||
final phone = prefs.getString('phone');
|
final phone = prefs.getString('phone');
|
||||||
|
final avatarIndex = prefs.getInt('avatar_index') ?? 0;
|
||||||
|
final nickname = prefs.getString('nickname');
|
||||||
|
|
||||||
if (accessToken != null && refreshToken != null && accountSequence != null) {
|
if (accessToken != null && refreshToken != null && accountSequence != null) {
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
|
@ -93,6 +99,8 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
refreshToken: refreshToken,
|
refreshToken: refreshToken,
|
||||||
accountSequence: accountSequence,
|
accountSequence: accountSequence,
|
||||||
phone: phone,
|
phone: phone,
|
||||||
|
avatarIndex: avatarIndex,
|
||||||
|
nickname: nickname,
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
);
|
);
|
||||||
// 登录后自动获取用户详情
|
// 登录后自动获取用户详情
|
||||||
|
|
@ -114,6 +122,8 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
await prefs.remove('refresh_token');
|
await prefs.remove('refresh_token');
|
||||||
await prefs.remove('account_sequence');
|
await prefs.remove('account_sequence');
|
||||||
await prefs.remove('phone');
|
await prefs.remove('phone');
|
||||||
|
await prefs.remove('avatar_index');
|
||||||
|
await prefs.remove('nickname');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sendSmsCode(String phone, String type) async {
|
Future<void> sendSmsCode(String phone, String type) async {
|
||||||
|
|
@ -261,6 +271,20 @@ class UserNotifier extends StateNotifier<UserState> {
|
||||||
// 静默失败,不影响用户体验
|
// 静默失败,不影响用户体验
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 更新头像索引(本地存储)
|
||||||
|
Future<void> updateAvatar(int avatarIndex) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setInt('avatar_index', avatarIndex);
|
||||||
|
state = state.copyWith(avatarIndex: avatarIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 更新昵称(本地存储)
|
||||||
|
Future<void> updateNickname(String nickname) async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
await prefs.setString('nickname', nickname);
|
||||||
|
state = state.copyWith(nickname: nickname);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final userNotifierProvider = StateNotifierProvider<UserNotifier, UserState>(
|
final userNotifierProvider = StateNotifierProvider<UserNotifier, UserState>(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue