feat(mobile): redesign guide pages with fullscreen background images

向导页重新设计:

1. 页面1-4 改为全屏背景图片布局
   - 移除 SafeArea,图片延伸到状态栏
   - 移除文字标题和副标题(文字设计在图片中)
   - 仅保留底部页面指示器(白色圆点)

2. 页面5(欢迎加入页)也改为全屏背景图片
   - 添加渐变遮罩(30%~60%黑色)提高可读性
   - 表单区域改为透明背景
   - 所有文字、输入框、单选按钮改为白色系
   - 退出按钮文字从"退出 Exit"改为"退出"

3. 添加5张向导页背景图片
   - guide_1.jpg ~ guide_4.jpg: 介绍页背景
   - guide_5.png: 欢迎加入页背景

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-09 17:44:38 -08:00
parent 53320df220
commit 07448b381b
6 changed files with 223 additions and 171 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 377 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 303 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 259 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

View File

@ -38,28 +38,34 @@ class _GuidePageState extends ConsumerState<GuidePage> {
final PageController _pageController = PageController();
int _currentPage = 0;
// 1-4
// 1-5 (5)
// pngjpgwebp
final List<GuidePageData> _guidePages = const [
GuidePageData(
imagePath: 'assets/images/guide_1.png',
imagePath: 'assets/images/guide_1.jpg',
title: '认种一棵榴莲树\n拥有真实RWA资产',
subtitle: '绑定真实果园20年收益让区块链与农业完美结合',
),
GuidePageData(
imagePath: 'assets/images/guide_2.png',
imagePath: 'assets/images/guide_2.jpg',
title: '认种即可开启算力\n自动挖矿持续收益',
subtitle: '每一棵树都对应真实资产注入,为算力提供真实价值支撑',
),
GuidePageData(
imagePath: 'assets/images/guide_3.png',
imagePath: 'assets/images/guide_3.jpg',
title: '分享链接\n获得团队算力与收益',
subtitle: '真实认种数据透明可信 · 团队越大算力越强',
),
GuidePageData(
imagePath: 'assets/images/guide_4.png',
imagePath: 'assets/images/guide_4.jpg',
title: 'MPC多方安全\n所有地址与收益可审计',
subtitle: '你的资产 · 安全透明 · 不可被篡改',
),
GuidePageData(
imagePath: 'assets/images/guide_5.png',
title: '欢迎加入',
subtitle: '创建账号前的最后一步 · 请选择是否有推荐人',
),
];
@override
@ -99,85 +105,53 @@ class _GuidePageState extends ConsumerState<GuidePage> {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: PageView.builder(
controller: _pageController,
onPageChanged: _onPageChanged,
itemCount: 5, // 4 + 1
itemBuilder: (context, index) {
if (index < 4) {
return _buildGuidePage(_guidePages[index], index);
} else {
return _buildWelcomePage();
}
},
),
body: PageView.builder(
controller: _pageController,
onPageChanged: _onPageChanged,
itemCount: 5, // 4 + 1
itemBuilder: (context, index) {
if (index < 4) {
return _buildGuidePage(_guidePages[index], index);
} else {
return _buildWelcomePage();
}
},
),
);
}
/// (1-4)
/// (1-4) -
Widget _buildGuidePage(GuidePageData data, int index) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
children: [
SizedBox(height: 64.h),
//
Expanded(
flex: 5,
child: Container(
width: 312.w,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
return Stack(
fit: StackFit.expand,
children: [
//
if (data.imagePath != null)
Image.asset(
data.imagePath!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (context, error, stackTrace) {
return Container(
color: const Color(0xFFFFF8E7),
),
child: data.imagePath != null
? ClipRRect(
borderRadius: BorderRadius.circular(12.r),
child: Image.asset(
data.imagePath!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return _buildPlaceholderImage(index);
},
),
)
: _buildPlaceholderImage(index),
),
child: _buildPlaceholderImage(index),
);
},
)
else
Container(
color: const Color(0xFFFFF8E7),
child: _buildPlaceholderImage(index),
),
SizedBox(height: 48.h),
//
Text(
data.title,
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w700,
height: 1.33,
color: const Color(0xFF292524),
),
textAlign: TextAlign.center,
),
SizedBox(height: 16.h),
//
Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Text(
data.subtitle,
style: TextStyle(
fontSize: 14.sp,
height: 1.43,
color: const Color(0xFF57534E),
),
textAlign: TextAlign.center,
),
),
SizedBox(height: 48.h),
//
_buildPageIndicator(),
SizedBox(height: 80.h),
],
),
//
Positioned(
left: 0,
right: 0,
bottom: 80.h,
child: _buildPageIndicator(),
),
],
);
}
@ -223,10 +197,12 @@ class _GuidePageState extends ConsumerState<GuidePage> {
return _WelcomePageContent(
onNext: _goToOnboarding,
onExit: _exitApp, // 退
backgroundImage: _guidePages[4].imagePath,
pageIndicator: _buildPageIndicator(),
);
}
///
/// (使)
Widget _buildPageIndicator() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
@ -239,8 +215,8 @@ class _GuidePageState extends ConsumerState<GuidePage> {
decoration: BoxDecoration(
shape: BoxShape.circle,
color: isActive
? const Color(0xFF8E794A)
: const Color(0xFFEAE0CD),
? Colors.white
: Colors.white.withValues(alpha: 0.4),
),
);
}),
@ -252,10 +228,14 @@ class _GuidePageState extends ConsumerState<GuidePage> {
class _WelcomePageContent extends ConsumerStatefulWidget {
final VoidCallback onNext;
final VoidCallback onExit;
final String? backgroundImage;
final Widget pageIndicator;
const _WelcomePageContent({
required this.onNext,
required this.onExit,
this.backgroundImage,
required this.pageIndicator,
});
@override
@ -405,93 +385,152 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
children: [
// 退
Align(
alignment: Alignment.topRight,
child: Padding(
padding: EdgeInsets.only(top: 32.h),
child: GestureDetector(
onTap: widget.onExit,
child: Text(
'退出 Exit',
style: TextStyle(
fontSize: 14.sp,
color: const Color(0xFFA99F93),
),
),
),
),
),
SizedBox(height: 80.h),
//
Text(
'欢迎加入',
style: TextStyle(
fontSize: 24.sp,
fontWeight: FontWeight.w700,
height: 1.33,
color: const Color(0xFF6F6354),
),
),
SizedBox(height: 12.h),
//
Text(
'创建账号前的最后一步 · 请选择是否有推荐人',
style: TextStyle(
fontSize: 14.sp,
height: 1.43,
color: const Color(0xFFA99F93),
),
),
SizedBox(height: 48.h),
//
_buildReferrerOptions(),
const Spacer(),
//
GestureDetector(
onTap: _canProceed ? _saveReferralCodeAndProceed : null,
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 16.h),
child: Text(
'下一步 (创建账号)',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
height: 1.5,
color: _canProceed
? const Color(0xFFD4A84B) // -
: const Color(0xFFCCC5B9), // -
),
textAlign: TextAlign.right,
),
),
),
SizedBox(height: 48.h),
],
),
),
child: Stack(
fit: StackFit.expand,
children: [
//
if (widget.backgroundImage != null)
Image.asset(
widget.backgroundImage!,
fit: BoxFit.cover,
width: double.infinity,
height: double.infinity,
errorBuilder: (context, error, stackTrace) {
return Container(
color: const Color(0xFFFFF8E7),
);
},
)
else
Container(
color: const Color(0xFFFFF8E7),
),
//
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.black.withValues(alpha: 0.3),
Colors.black.withValues(alpha: 0.6),
],
),
),
);
},
),
//
SafeArea(
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight,
),
child: IntrinsicHeight(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 24.w),
child: Column(
children: [
// 退
Align(
alignment: Alignment.topRight,
child: Padding(
padding: EdgeInsets.only(top: 16.h),
child: GestureDetector(
onTap: widget.onExit,
child: Text(
'退出',
style: TextStyle(
fontSize: 14.sp,
color: Colors.white.withValues(alpha: 0.8),
),
),
),
),
),
SizedBox(height: 60.h),
//
Text(
'欢迎加入',
style: TextStyle(
fontSize: 28.sp,
fontWeight: FontWeight.w700,
height: 1.33,
color: Colors.white,
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 4,
),
],
),
),
SizedBox(height: 12.h),
//
Text(
'创建账号前的最后一步 · 请选择是否有推荐人',
style: TextStyle(
fontSize: 14.sp,
height: 1.43,
color: Colors.white.withValues(alpha: 0.9),
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 4,
),
],
),
),
SizedBox(height: 48.h),
// ()
_buildReferrerOptions(),
const Spacer(),
//
widget.pageIndicator,
SizedBox(height: 24.h),
//
GestureDetector(
onTap: _canProceed ? _saveReferralCodeAndProceed : null,
child: Container(
width: double.infinity,
padding: EdgeInsets.symmetric(vertical: 16.h),
decoration: BoxDecoration(
color: _canProceed
? const Color(0xFFD4A84B)
: Colors.white.withValues(alpha: 0.3),
borderRadius: BorderRadius.circular(12.r),
),
child: Text(
'下一步 (创建账号)',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
height: 1.5,
color: _canProceed
? Colors.white
: Colors.white.withValues(alpha: 0.6),
),
textAlign: TextAlign.center,
),
),
),
SizedBox(height: 32.h),
],
),
),
),
),
);
},
),
),
],
),
);
}
///
/// ()
Widget _buildReferrerOptions() {
return Column(
children: [
@ -511,7 +550,13 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
style: TextStyle(
fontSize: 16.sp,
height: 1.5,
color: const Color(0xFF6F6354),
color: Colors.white,
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 4,
),
],
),
),
],
@ -522,11 +567,11 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
if (_hasReferrer)
Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 4.w),
decoration: const BoxDecoration(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 1,
color: Color(0xFFEAE1D2),
color: Colors.white.withValues(alpha: 0.5),
),
),
),
@ -539,7 +584,7 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
hintText: '请输入推荐码 / 序列号',
hintStyle: TextStyle(
fontSize: 16.sp,
color: const Color(0xFFA99F93),
color: Colors.white.withValues(alpha: 0.6),
),
border: InputBorder.none,
isDense: true,
@ -547,8 +592,9 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
),
style: TextStyle(
fontSize: 16.sp,
color: const Color(0xFF6F6354),
color: Colors.white,
),
cursorColor: Colors.white,
),
),
//
@ -559,7 +605,7 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
child: Icon(
Icons.camera_alt_outlined,
size: 20.sp,
color: const Color(0xFFA99F93),
color: Colors.white.withValues(alpha: 0.8),
),
),
),
@ -583,7 +629,13 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
style: TextStyle(
fontSize: 16.sp,
height: 1.5,
color: const Color(0xFF6F6354),
color: Colors.white,
shadows: [
Shadow(
color: Colors.black.withValues(alpha: 0.5),
blurRadius: 4,
),
],
),
),
],
@ -593,7 +645,7 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
);
}
///
/// ()
Widget _buildRadio(bool isSelected) {
return Container(
width: 20.w,
@ -603,8 +655,8 @@ class _WelcomePageContentState extends ConsumerState<_WelcomePageContent> {
border: Border.all(
width: isSelected ? 6.w : 2.w,
color: isSelected
? const Color(0xFF2563EB)
: const Color(0xFFA99F93),
? Colors.white
: Colors.white.withValues(alpha: 0.5),
),
),
);