From 615e18d03aaab3c5cb19590660e7690bb2e0d3e8 Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Fri, 20 Feb 2026 22:50:04 -0800 Subject: [PATCH] Improve onboarding with title and progress bar - Added "Add Months" header with subtitle - Numbered step indicators (1, 2, 3) with progress lines - Checkmarks for completed steps - "Step X of 3" badge on each slide Co-Authored-By: Claude Opus 4.5 --- lib/screens/onboarding_screen.dart | 151 ++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 34 deletions(-) diff --git a/lib/screens/onboarding_screen.dart b/lib/screens/onboarding_screen.dart index 0a7fcbf..ef422a1 100644 --- a/lib/screens/onboarding_screen.dart +++ b/lib/screens/onboarding_screen.dart @@ -43,28 +43,46 @@ class _OnboardingScreenState extends State { body: SafeArea( child: Column( children: [ + // Header + Padding( + padding: const EdgeInsets.fromLTRB(32, 24, 32, 0), + child: Column( + children: [ + Text( + 'Add Months', + style: Theme.of(context).textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.w700, + ), + ), + const SizedBox(height: 4), + Text( + 'Evidence-based lifespan optimization', + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + const SizedBox(height: 16), + // Progress bar + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: _buildProgressBar(), + ), + // Slides Expanded( child: PageView.builder( controller: _controller, itemCount: _slides.length, onPageChanged: (index) => setState(() => _currentPage = index), - itemBuilder: (context, index) => _buildSlide(_slides[index]), + itemBuilder: (context, index) => + _buildSlide(index, _slides[index]), ), ), + // Bottom section Padding( - padding: const EdgeInsets.all(32), + padding: const EdgeInsets.fromLTRB(32, 0, 32, 32), child: Column( children: [ - // Page indicators - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate( - _slides.length, - (index) => _buildDot(index), - ), - ), - const SizedBox(height: 32), - // Button SizedBox( width: double.infinity, child: ElevatedButton( @@ -75,7 +93,6 @@ class _OnboardingScreenState extends State { ), ), const SizedBox(height: 12), - // Skip button (not on last page) if (_currentPage < _slides.length - 1) TextButton( onPressed: _skip, @@ -95,32 +112,112 @@ class _OnboardingScreenState extends State { ); } - Widget _buildSlide(_SlideData slide) { + Widget _buildProgressBar() { + return Row( + children: List.generate(_slides.length, (index) { + final isCompleted = index < _currentPage; + final isCurrent = index == _currentPage; + return Expanded( + child: Container( + margin: EdgeInsets.only(right: index < _slides.length - 1 ? 8 : 0), + child: Column( + children: [ + // Step number row + Row( + children: [ + Container( + width: 24, + height: 24, + decoration: BoxDecoration( + color: isCompleted || isCurrent + ? AppColors.primary + : AppColors.divider, + shape: BoxShape.circle, + ), + child: Center( + child: isCompleted + ? const Icon(Icons.check, + size: 14, color: Colors.white) + : Text( + '${index + 1}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: isCurrent + ? Colors.white + : AppColors.textTertiary, + ), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: Container( + height: 3, + decoration: BoxDecoration( + color: isCompleted + ? AppColors.primary + : AppColors.divider, + borderRadius: BorderRadius.circular(2), + ), + ), + ), + ], + ), + ], + ), + ), + ); + }), + ); + } + + Widget _buildSlide(int index, _SlideData slide) { return Padding( padding: const EdgeInsets.symmetric(horizontal: 40), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ + // Step indicator Container( - width: 120, - height: 120, + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: AppColors.primary.withAlpha(26), + borderRadius: BorderRadius.circular(12), + ), + child: Text( + 'Step ${index + 1} of ${_slides.length}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: AppColors.primary, + ), + ), + ), + const SizedBox(height: 32), + // Icon + Container( + width: 100, + height: 100, decoration: BoxDecoration( color: AppColors.primary.withAlpha(26), shape: BoxShape.circle, ), child: Icon( slide.icon, - size: 56, + size: 48, color: AppColors.primary, ), ), - const SizedBox(height: 48), + const SizedBox(height: 32), + // Title Text( slide.title, - style: Theme.of(context).textTheme.headlineLarge, + style: Theme.of(context).textTheme.headlineMedium, textAlign: TextAlign.center, ), - const SizedBox(height: 16), + const SizedBox(height: 12), + // Description Text( slide.description, style: Theme.of(context).textTheme.bodyLarge?.copyWith( @@ -133,20 +230,6 @@ class _OnboardingScreenState extends State { ); } - Widget _buildDot(int index) { - final isActive = index == _currentPage; - return AnimatedContainer( - duration: const Duration(milliseconds: 200), - margin: const EdgeInsets.symmetric(horizontal: 4), - width: isActive ? 24 : 8, - height: 8, - decoration: BoxDecoration( - color: isActive ? AppColors.primary : AppColors.divider, - borderRadius: BorderRadius.circular(4), - ), - ); - } - void _onButtonPressed() { if (_currentPage < _slides.length - 1) { _controller.nextPage(