|
|
|
import 'package:doublefeel_flutter/app/modules/watch_theme/models/watch_theme_models.dart';
|
|
|
|
import 'package:doublefeel_flutter/app/routes/app_pages.dart';
|
|
|
|
import 'package:doublefeel_flutter/core/services/user_state_service.dart';
|
|
|
|
import 'package:doublefeel_flutter/core/theme/app_colors.dart';
|
|
|
|
import 'package:doublefeel_flutter/core/util/size_extensions.dart';
|
|
|
|
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
|
|
|
|
import 'package:doublefeel_flutter/data/models/user/user_models.dart';
|
|
|
|
import 'package:doublefeel_flutter/r.dart';
|
|
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'package:get/get.dart';
|
|
|
|
|
|
|
|
class MyTab extends StatelessWidget {
|
|
|
|
const MyTab({super.key});
|
|
|
|
|
|
|
|
static const _background = Color(0xFFF5F2FF);
|
|
|
|
static const _proPurple = Color(0xFF916DF5);
|
|
|
|
static const _proGold = Color(0xFFFFDF51);
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
final userPrefs = Get.find<UserPreferencesStorage>();
|
|
|
|
return Container(
|
|
|
|
color: _background,
|
|
|
|
child: SafeArea(
|
|
|
|
bottom: false,
|
|
|
|
child: Obx(() {
|
|
|
|
final user = userPrefs.preferences.value.meUserInfo;
|
|
|
|
return ListView(
|
|
|
|
physics: const ClampingScrollPhysics(),
|
|
|
|
padding: EdgeInsets.fromLTRB(16.dp, 59.dp, 16.dp, 112.dp),
|
|
|
|
children: [
|
|
|
|
_ProfileHeader(
|
|
|
|
user: user,
|
|
|
|
onTap: () => Get.toNamed(Routes.ACCOUNT_SETTINGS),
|
|
|
|
),
|
|
|
|
SizedBox(height: 28.dp),
|
|
|
|
const _PremiumCard(),
|
|
|
|
SizedBox(height: 12.dp),
|
|
|
|
_WatchThemeCard(
|
|
|
|
onTap: () => Get.toNamed(
|
|
|
|
Routes.WATCH_THEME,
|
|
|
|
arguments: {'isPremium': false, 'hasCustomThemes': false},
|
|
|
|
),
|
|
|
|
),
|
|
|
|
SizedBox(height: 12.dp),
|
|
|
|
_MenuTile(
|
|
|
|
title: '账号信息',
|
|
|
|
onTap: () => Get.toNamed(Routes.ACCOUNT_SETTINGS),
|
|
|
|
),
|
|
|
|
SizedBox(height: 12.dp),
|
|
|
|
_MenuTile(title: '帮助', onTap: () {}),
|
|
|
|
],
|
|
|
|
);
|
|
|
|
}),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ProfileHeader extends StatelessWidget {
|
|
|
|
const _ProfileHeader({
|
|
|
|
required this.user,
|
|
|
|
required this.onTap,
|
|
|
|
});
|
|
|
|
|
|
|
|
final UserInfoResponse? user;
|
|
|
|
final VoidCallback onTap;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return GestureDetector(
|
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
|
onTap: onTap,
|
|
|
|
child: SizedBox(
|
|
|
|
height: 80.dp,
|
|
|
|
child: Row(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
_Avatar(avatarUrl: user?.avatar),
|
|
|
|
SizedBox(width: 12.dp),
|
|
|
|
Expanded(
|
|
|
|
child: Column(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
Flexible(
|
|
|
|
child: Text(
|
|
|
|
_displayName(user),
|
|
|
|
maxLines: 1,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
style: TextStyle(
|
|
|
|
color: AppColors.textPrimary,
|
|
|
|
fontSize: 20.dp,
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
SizedBox(width: 8.dp),
|
|
|
|
Icon(
|
|
|
|
Icons.edit_outlined,
|
|
|
|
color: const Color(0xFFA084EF),
|
|
|
|
size: 16.dp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
SizedBox(height: 6.dp),
|
|
|
|
Text(
|
|
|
|
'ID:${user?.id ?? 738293}',
|
|
|
|
maxLines: 1,
|
|
|
|
overflow: TextOverflow.ellipsis,
|
|
|
|
style: TextStyle(
|
|
|
|
color: AppColors.textSecondary,
|
|
|
|
fontSize: 14.dp,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
String _displayName(UserInfoResponse? user) {
|
|
|
|
final name = user?.nickname?.trim();
|
|
|
|
if (name != null && name.isNotEmpty) {
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
return '不爱吃热干面';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _Avatar extends StatelessWidget {
|
|
|
|
const _Avatar({this.avatarUrl});
|
|
|
|
|
|
|
|
final String? avatarUrl;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Center(
|
|
|
|
child: Column(
|
|
|
|
mainAxisSize: MainAxisSize.min,
|
|
|
|
final url = avatarUrl?.trim();
|
|
|
|
final imageProvider = url == null || url.isEmpty
|
|
|
|
? AssetImage(R.assetsImagesMyAvatarDefault) as ImageProvider
|
|
|
|
: NetworkImage(url);
|
|
|
|
|
|
|
|
return SizedBox(
|
|
|
|
width: 80.dp,
|
|
|
|
height: 80.dp,
|
|
|
|
child: Stack(
|
|
|
|
children: [
|
|
|
|
GestureDetector(
|
|
|
|
onTap: () async {
|
|
|
|
var userStateService = Get.find<UserStateService>();
|
|
|
|
await userStateService.onLogout();
|
|
|
|
Get.offAllNamed(AppRoutes.initial);
|
|
|
|
},
|
|
|
|
child: Container(
|
|
|
|
padding: const EdgeInsets.all(10),
|
|
|
|
margin: const EdgeInsets.only(top: 10),
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.red,
|
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
|
ClipOval(
|
|
|
|
child: Image(
|
|
|
|
image: imageProvider,
|
|
|
|
width: 80.dp,
|
|
|
|
height: 80.dp,
|
|
|
|
fit: BoxFit.cover,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Positioned(
|
|
|
|
right: 0,
|
|
|
|
bottom: 0,
|
|
|
|
child: Container(
|
|
|
|
width: 24.dp,
|
|
|
|
height: 24.dp,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: const Color(0xFFE8E0FF),
|
|
|
|
shape: BoxShape.circle,
|
|
|
|
border: Border.all(color: MyTab._background, width: 2.dp),
|
|
|
|
),
|
|
|
|
child: Icon(
|
|
|
|
Icons.photo_camera_outlined,
|
|
|
|
color: const Color(0xFFA084EF),
|
|
|
|
size: 14.dp,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _PremiumCard extends StatelessWidget {
|
|
|
|
const _PremiumCard();
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return GestureDetector(
|
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
|
onTap: () => Get.toNamed(Routes.PURCHASE),
|
|
|
|
child: Container(
|
|
|
|
height: 128.dp,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: MyTab._proPurple,
|
|
|
|
borderRadius: BorderRadius.circular(16.dp),
|
|
|
|
),
|
|
|
|
clipBehavior: Clip.antiAlias,
|
|
|
|
child: Stack(
|
|
|
|
children: [
|
|
|
|
Positioned(
|
|
|
|
right: 0,
|
|
|
|
bottom: 0,
|
|
|
|
child: SizedBox(
|
|
|
|
width: 151.dp,
|
|
|
|
height: 114.dp,
|
|
|
|
child: Stack(
|
|
|
|
alignment: Alignment.bottomRight,
|
|
|
|
children: [
|
|
|
|
Positioned(
|
|
|
|
left: 0,
|
|
|
|
bottom: -14.dp,
|
|
|
|
child: Container(
|
|
|
|
width: 86.dp,
|
|
|
|
height: 110.dp,
|
|
|
|
decoration: const BoxDecoration(
|
|
|
|
color: Color(0xFFD8CFF7),
|
|
|
|
borderRadius: BorderRadius.only(
|
|
|
|
topLeft: Radius.circular(12),
|
|
|
|
topRight: Radius.circular(12),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Container(
|
|
|
|
width: 80.dp,
|
|
|
|
height: 120.dp,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.white,
|
|
|
|
borderRadius: BorderRadius.only(
|
|
|
|
topLeft: Radius.circular(8.dp),
|
|
|
|
topRight: Radius.circular(8.dp),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
child: const Text('Logout'),
|
|
|
|
)),
|
|
|
|
GestureDetector(
|
|
|
|
onTap: () async {
|
|
|
|
Get.offAllNamed(AppRoutes.userOnboarding);
|
|
|
|
},
|
|
|
|
child: Container(
|
|
|
|
padding: const EdgeInsets.all(10),
|
|
|
|
margin: const EdgeInsets.only(top: 10),
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.blue,
|
|
|
|
borderRadius: BorderRadius.circular(10),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Padding(
|
|
|
|
padding: EdgeInsets.fromLTRB(20.dp, 20.dp, 20.dp, 20.dp),
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
ShaderMask(
|
|
|
|
blendMode: BlendMode.srcIn,
|
|
|
|
shaderCallback: (bounds) => const LinearGradient(
|
|
|
|
begin: Alignment.topCenter,
|
|
|
|
end: Alignment.bottomCenter,
|
|
|
|
colors: [
|
|
|
|
Color(0xFFFFE8AE),
|
|
|
|
Color(0xFFFFFAED),
|
|
|
|
Colors.white,
|
|
|
|
],
|
|
|
|
).createShader(bounds),
|
|
|
|
child: Text(
|
|
|
|
'解锁专业版',
|
|
|
|
style: TextStyle(
|
|
|
|
fontSize: 16.dp,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
SizedBox(height: 5.dp),
|
|
|
|
Text(
|
|
|
|
'开启压力预警与健康陪伴之旅',
|
|
|
|
style: TextStyle(
|
|
|
|
color: const Color(0xFFC6B3FF),
|
|
|
|
fontSize: 12.dp,
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const Spacer(),
|
|
|
|
Container(
|
|
|
|
height: 32.dp,
|
|
|
|
width: 129.dp,
|
|
|
|
alignment: Alignment.center,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: MyTab._proGold,
|
|
|
|
borderRadius: BorderRadius.circular(24.dp),
|
|
|
|
),
|
|
|
|
child: Row(
|
|
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
|
|
children: [
|
|
|
|
Icon(
|
|
|
|
Icons.workspace_premium,
|
|
|
|
color: AppColors.textPrimary,
|
|
|
|
size: 16.dp,
|
|
|
|
),
|
|
|
|
SizedBox(width: 4.dp),
|
|
|
|
Text(
|
|
|
|
'立即解锁',
|
|
|
|
style: TextStyle(
|
|
|
|
color: AppColors.textPrimary,
|
|
|
|
fontSize: 14.dp,
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _WatchThemeCard extends StatelessWidget {
|
|
|
|
const _WatchThemeCard({required this.onTap});
|
|
|
|
|
|
|
|
final VoidCallback onTap;
|
|
|
|
|
|
|
|
final double _itemSize = 64;
|
|
|
|
final double _overlap = 8;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return GestureDetector(
|
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
|
onTap: onTap,
|
|
|
|
child: Container(
|
|
|
|
padding: EdgeInsets.fromLTRB(20.dp, 20.dp, 20.dp, 18.dp),
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.white,
|
|
|
|
borderRadius: BorderRadius.circular(16.dp),
|
|
|
|
),
|
|
|
|
child: Column(
|
|
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
|
|
children: [
|
|
|
|
Row(
|
|
|
|
children: [
|
|
|
|
Text(
|
|
|
|
'Watch主题',
|
|
|
|
style: TextStyle(
|
|
|
|
color: AppColors.textPrimary,
|
|
|
|
fontSize: 16.dp,
|
|
|
|
fontWeight: FontWeight.w600,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
child: const Text('Onboarding'),
|
|
|
|
)),
|
|
|
|
],
|
|
|
|
const Spacer(),
|
|
|
|
Icon(
|
|
|
|
Icons.chevron_right,
|
|
|
|
color: AppColors.textTertiary,
|
|
|
|
size: 20.dp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
SizedBox(height: 17.dp),
|
|
|
|
SizedBox(
|
|
|
|
height: _itemSize,
|
|
|
|
child: ListView.builder(
|
|
|
|
scrollDirection: Axis.horizontal,
|
|
|
|
clipBehavior: Clip.none,
|
|
|
|
itemCount: officialThemes.length,
|
|
|
|
itemBuilder: (context, index) {
|
|
|
|
return Transform.translate(
|
|
|
|
offset: Offset(index == 0 ? 0 : -_overlap * index, 0),
|
|
|
|
child: _ThemeBubble(
|
|
|
|
size: _itemSize,
|
|
|
|
assetPath: officialThemes[index]
|
|
|
|
.infoList
|
|
|
|
.firstOrNull
|
|
|
|
?.assetPath ??
|
|
|
|
'',
|
|
|
|
));
|
|
|
|
},
|
|
|
|
|
|
|
|
),
|
|
|
|
),
|
|
|
|
SizedBox(height: 10.dp),
|
|
|
|
Text(
|
|
|
|
'支持自定义创作主题哦~',
|
|
|
|
style: TextStyle(
|
|
|
|
color: AppColors.primary,
|
|
|
|
fontSize: 12.dp,
|
|
|
|
fontWeight: FontWeight.w500,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _ThemeBubble extends StatelessWidget {
|
|
|
|
const _ThemeBubble({required this.size, required this.assetPath});
|
|
|
|
|
|
|
|
final double size;
|
|
|
|
final String assetPath;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return Container(
|
|
|
|
width: size,
|
|
|
|
height: size,
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: const Color(0xFFEDE8FF),
|
|
|
|
shape: BoxShape.circle,
|
|
|
|
border: Border.all(color: Colors.white, width: 4),
|
|
|
|
),
|
|
|
|
alignment: Alignment.center,
|
|
|
|
child: Image.asset(
|
|
|
|
assetPath,
|
|
|
|
width: 36,
|
|
|
|
height: 36,
|
|
|
|
fit: BoxFit.fill,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class _MenuTile extends StatelessWidget {
|
|
|
|
const _MenuTile({
|
|
|
|
required this.title,
|
|
|
|
required this.onTap,
|
|
|
|
});
|
|
|
|
|
|
|
|
final String title;
|
|
|
|
final VoidCallback onTap;
|
|
|
|
|
|
|
|
@override
|
|
|
|
Widget build(BuildContext context) {
|
|
|
|
return GestureDetector(
|
|
|
|
behavior: HitTestBehavior.opaque,
|
|
|
|
onTap: onTap,
|
|
|
|
child: Container(
|
|
|
|
height: 56.dp,
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 20.dp),
|
|
|
|
decoration: BoxDecoration(
|
|
|
|
color: Colors.white,
|
|
|
|
borderRadius: BorderRadius.circular(16.dp),
|
|
|
|
),
|
|
|
|
child: Row(
|
|
|
|
children: [
|
|
|
|
Text(
|
|
|
|
title,
|
|
|
|
style: TextStyle(
|
|
|
|
color: AppColors.textPrimary,
|
|
|
|
fontSize: 14.dp,
|
|
|
|
fontWeight: FontWeight.w400,
|
|
|
|
height: 1.25,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
const Spacer(),
|
|
|
|
Icon(
|
|
|
|
Icons.chevron_right,
|
|
|
|
color: AppColors.textTertiary,
|
|
|
|
size: 20.dp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
...
|
...
|
|