feat: Implement the onboarding flow with new screens, assets, and updated splash screen navigation logic.

This commit is contained in:
pradeepkumar
2026-03-10 23:10:23 +05:30
parent 792965f717
commit d1318d25b9
11 changed files with 439 additions and 7 deletions

View File

@@ -0,0 +1,5 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Container">
<path id="Icon" d="M12.175 9H0V7H12.175L6.575 1.4L8 0L16 8L8 16L6.575 14.6L12.175 9V9" fill="white"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 206 B

View File

@@ -0,0 +1,9 @@
<svg width="382" height="346" viewBox="0 0 382 346" fill="none" xmlns="http://www.w3.org/2000/svg">
<path opacity="0.2" d="M0 173C0 77.4547 77.4547 0 173 0H209C304.545 0 382 77.4547 382 173C382 268.545 304.545 346 209 346H173C77.4548 346 0 268.545 0 173Z" fill="url(#paint0_linear_0_11)"/>
<defs>
<linearGradient id="paint0_linear_0_11" x1="0" y1="0" x2="344.312" y2="380.136" gradientUnits="userSpaceOnUse">
<stop stop-color="#E68A2D"/>
<stop offset="1" stop-color="#E68A2D" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 528 B

View File

@@ -0,0 +1,5 @@
<svg viewBox="0 0 110.05 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Container">
<path id="Icon" d="M54.425 90C54.7583 90 55.0917 89.9167 55.425 89.75C55.7583 89.5833 56.0083 89.4167 56.175 89.25L97.175 48.25C98.175 47.25 98.9042 46.125 99.3625 44.875C99.8208 43.625 100.05 42.375 100.05 41.125C100.05 39.7917 99.8208 38.5208 99.3625 37.3125C98.9042 36.1042 98.175 35.0417 97.175 34.125L75.925 12.875C75.0083 11.875 73.9458 11.1458 72.7375 10.6875C71.5292 10.2292 70.2583 10 68.925 10C67.675 10 66.425 10.2292 65.175 10.6875C63.925 11.1458 62.8 11.875 61.8 12.875L60.425 14.25L69.675 23.625C70.925 24.7917 71.8417 26.125 72.425 27.625C73.0083 29.125 73.3 30.7083 73.3 32.375C73.3 35.875 72.1125 38.8125 69.7375 41.1875C67.3625 43.5625 64.425 44.75 60.925 44.75C59.2583 44.75 57.6542 44.4583 56.1125 43.875C54.5708 43.2917 53.2167 42.4167 52.05 41.25L42.675 32L20.8 53.875C20.55 54.125 20.3625 54.3958 20.2375 54.6875C20.1125 54.9792 20.05 55.2917 20.05 55.625C20.05 56.2917 20.3 56.8958 20.8 57.4375C21.3 57.9792 21.8833 58.25 22.55 58.25C22.8833 58.25 23.2167 58.1667 23.55 58C23.8833 57.8333 24.1333 57.6667 24.3 57.5L41.3 40.5L48.3 47.5L31.425 64.5C31.175 64.75 30.9875 65.0208 30.8625 65.3125C30.7375 65.6042 30.675 65.9167 30.675 66.25C30.675 66.9167 30.925 67.5 31.425 68C31.925 68.5 32.5083 68.75 33.175 68.75C33.5083 68.75 33.8417 68.6667 34.175 68.5C34.5083 68.3333 34.7583 68.1667 34.925 68L51.925 51.125L58.925 58.125L42.05 75.125C41.8 75.2917 41.6125 75.5417 41.4875 75.875C41.3625 76.2083 41.3 76.5417 41.3 76.875C41.3 77.5417 41.55 78.125 42.05 78.625C42.55 79.125 43.1333 79.375 43.8 79.375C44.1333 79.375 44.4458 79.3125 44.7375 79.1875C45.0292 79.0625 45.3 78.875 45.55 78.625L62.55 61.75L69.55 68.75L52.55 85.75C52.3 86 52.1125 86.2708 51.9875 86.5625C51.8625 86.8542 51.8 87.1667 51.8 87.5C51.8 88.1667 52.0708 88.75 52.6125 89.25C53.1542 89.75 53.7583 90 54.425 90V90M54.3 100C51.2167 100 48.4875 98.9792 46.1125 96.9375C43.7375 94.8958 42.3417 92.3333 41.925 89.25C39.0917 88.8333 36.7167 87.6667 34.8 85.75C32.8833 83.8333 31.7167 81.4583 31.3 78.625C28.4667 78.2083 26.1125 77.0208 24.2375 75.0625C22.3625 73.1042 21.2167 70.75 20.8 68C17.6333 67.5833 15.05 66.2083 13.05 63.875C11.05 61.5417 10.05 58.7917 10.05 55.625C10.05 53.9583 10.3625 52.3542 10.9875 50.8125C11.6125 49.2708 12.5083 47.9167 13.675 46.75L42.675 17.875L59.05 34.25C59.2167 34.5 59.4667 34.6875 59.8 34.8125C60.1333 34.9375 60.4667 35 60.8 35C61.55 35 62.175 34.7708 62.675 34.3125C63.175 33.8542 63.425 33.25 63.425 32.5C63.425 32.1667 63.3625 31.8333 63.2375 31.5C63.1125 31.1667 62.925 30.9167 62.675 30.75L44.8 12.875C43.8833 11.875 42.8208 11.1458 41.6125 10.6875C40.4042 10.2292 39.1333 10 37.8 10C36.55 10 35.3 10.2292 34.05 10.6875C32.8 11.1458 31.675 11.875 30.675 12.875L13.05 30.625C12.3 31.375 11.675 32.25 11.175 33.25C10.675 34.25 10.3417 35.25 10.175 36.25C10.0083 37.25 10.0083 38.2708 10.175 39.3125C10.3417 40.3542 10.675 41.3333 11.175 42.25L3.925 49.5C2.50833 47.5833 1.46667 45.4792 0.8 43.1875C0.133333 40.8958 -0.116667 38.5833 0.05 36.25C0.216667 33.9167 0.8 31.6458 1.8 29.4375C2.8 27.2292 4.175 25.25 5.925 23.5L23.55 5.875C25.55 3.95833 27.7792 2.5 30.2375 1.5C32.6958 0.5 35.2167 0 37.8 0C40.3833 0 42.9042 0.5 45.3625 1.5C47.8208 2.5 50.0083 3.95833 51.925 5.875L53.3 7.25L54.675 5.875C56.675 3.95833 58.9042 2.5 61.3625 1.5C63.8208 0.5 66.3417 0 68.925 0C71.5083 0 74.0292 0.5 76.4875 1.5C78.9458 2.5 81.1333 3.95833 83.05 5.875L104.175 27C106.092 28.9167 107.55 31.125 108.55 33.625C109.55 36.125 110.05 38.6667 110.05 41.25C110.05 43.8333 109.55 46.3542 108.55 48.8125C107.55 51.2708 106.092 53.4583 104.175 55.375L63.175 96.25C62.0083 97.4167 60.6542 98.3333 59.1125 99C57.5708 99.6667 55.9667 100 54.3 100V100M41.925 30V30V30V30V30V30V30V30V30V30V30V30V30V30V30V30V30V30V30V30V30" fill="#E68A2D"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -0,0 +1,9 @@
<svg width="382" height="346" viewBox="0 0 382 346" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Gradient" opacity="0.2" d="M0 173C0 77.4547 77.4547 0 173 0H209C304.545 0 382 77.4547 382 173C382 268.545 304.545 346 209 346H173C77.4548 346 0 268.545 0 173Z" fill="url(#paint0_linear_0_4)"/>
<defs>
<linearGradient id="paint0_linear_0_4" x1="0" y1="0" x2="344.312" y2="380.136" gradientUnits="userSpaceOnUse">
<stop stop-color="#E68A2D"/>
<stop offset="1" stop-color="#E68A2D" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 540 B

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 83.3333 75" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Vector" d="M41.6667 0C64.5833 0 83.3333 14.9167 83.3333 33.3333C83.3333 51.75 64.5833 66.6667 41.6667 66.6667C36.5 66.6667 31.5417 65.9167 26.9583 64.5833C14.7917 75 0 75 0 75C9.70833 65.2917 11.25 58.75 11.4583 56.25C4.375 50.2917 0 42.2083 0 33.3333C0 14.9167 18.75 0 41.6667 0Z" fill="#E58625"/>
</svg>

After

Width:  |  Height:  |  Size: 394 B

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 27 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Icon" d="M0 27V6H6V0H21V12H27V27H15V21H12V27H0V27M3 24H6V21H3V24V24M3 18H6V15H3V18V18M3 12H6V9H3V12V12M9 18H12V15H9V18V18M9 12H12V9H9V12V12M9 6H12V3H9V6V6M15 18H18V15H15V18V18M15 12H18V9H15V12V12M15 6H18V3H15V6V6M21 24H24V21H21V24V24M21 18H24V15H21V18V18" fill="#E68A2D" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 382 B

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 31.5 31.5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Icon" d="M27 18.75V9L19.5 3.75L12 9V11.25H9V7.5L19.5 0L30 7.5V18.75H27V18.75M19.5 3.75V3.75V3.75V3.75V3.75V3.75V3.75V3.75V3.75V3.75M20.25 9.75H21.75V8.25H20.25V9.75V9.75M17.25 9.75H18.75V8.25H17.25V9.75V9.75M20.25 12.75H21.75V11.25H20.25V12.75V12.75M17.25 12.75H18.75V11.25H17.25V12.75V12.75M9 25.5L19.425 28.35L28.35 25.575C28.225 25.35 28.0437 25.1562 27.8062 24.9937C27.5687 24.8312 27.3 24.75 27 24.75H19.425C18.75 24.75 18.2125 24.725 17.8125 24.675C17.4125 24.625 17 24.525 16.575 24.375L13.0875 23.2125L13.9125 20.2875L16.95 21.3C17.375 21.425 17.875 21.525 18.45 21.6C19.025 21.675 19.875 21.725 21 21.75V21.75V21.75V21.75C21 21.475 20.9188 21.2125 20.7563 20.9625C20.5938 20.7125 20.4 20.55 20.175 20.475L11.4 17.25V17.25V17.25H9V25.5V25.5M0 30.75V14.25H11.4C11.575 14.25 11.75 14.2687 11.925 14.3062C12.1 14.3437 12.2625 14.3875 12.4125 14.4375L21.225 17.7C22.05 18 22.7188 18.525 23.2313 19.275C23.7438 20.025 24 20.85 24 21.75H27C28.25 21.75 29.3125 22.1625 30.1875 22.9875C31.0625 23.8125 31.5 24.9 31.5 26.25V27.75L19.5 31.5L9 28.575V28.575V30.75H0V30.75M3 27.75H6V17.25H3V27.75V27.75" fill="#E68A2D" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -0,0 +1,3 @@
<svg viewBox="0 0 24 27" fill="none" xmlns="http://www.w3.org/2000/svg">
<path id="Icon" d="M3 24H7.5V15H16.5V24H21V10.5L12 3.75L3 10.5V24V24M0 27V9L12 0L24 9V27H13.5V18H10.5V27H0V27M12 13.875V13.875V13.875V13.875V13.875V13.875V13.875V13.875V13.875V13.875V13.875" fill="#E68A2D" fill-opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 307 B

View File

@@ -0,0 +1,375 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart';
class OnboardingScreen extends StatefulWidget {
const OnboardingScreen({super.key});
@override
State<OnboardingScreen> createState() => _OnboardingScreenState();
}
class _OnboardingScreenState extends State<OnboardingScreen> {
final PageController _pageController = PageController();
int _currentPage = 0;
static const _pages = [
_OnboardingData(
bgAsset: 'assets/images/onboarding_1_bg.svg',
iconAsset: 'assets/images/onboarding_1_icon.svg',
iconWidth: 110,
iconHeight: 100,
title: 'Connect with Top\nProfessionals',
subtitle:
'Agents can list properties and find qualified\nbuyers faster than ever.',
),
_OnboardingData(
bgAsset: 'assets/images/onboarding_2_bg.svg',
iconAsset: 'assets/images/onboarding_2_icon.svg',
iconWidth: 83,
iconHeight: 75,
title: 'Real help. Real agents.\nInstantly.',
subtitle:
'Chat with agents and get answers about\nproperties instantly.',
),
];
Future<void> _completeOnboarding() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('onboarding_completed', true);
if (mounted) {
context.go('/home');
}
}
void _nextPage() {
if (_currentPage < _pages.length - 1) {
_pageController.nextPage(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
);
} else {
_completeOnboarding();
}
}
@override
void dispose() {
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
width: double.infinity,
height: double.infinity,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.gradientStart, // #C4D9D4
AppColors.gradientEnd, // #F0F5FC
],
),
),
child: SafeArea(
child: Column(
children: [
// Skip button - top right
Align(
alignment: Alignment.centerRight,
child: GestureDetector(
onTap: _completeOnboarding,
child: const Padding(
padding: EdgeInsets.only(right: 24, top: 16),
child: Text(
'Skip',
style: TextStyle(
fontFamily: 'Fractul',
fontSize: 16,
fontWeight: FontWeight.w700,
color: AppColors.accentOrange,
letterSpacing: 0.24,
),
),
),
),
),
// Main content - PageView
Expanded(
child: PageView.builder(
controller: _pageController,
itemCount: _pages.length,
onPageChanged: (index) {
setState(() => _currentPage = index);
},
itemBuilder: (context, index) {
return _OnboardingPage(data: _pages[index]);
},
),
),
// Pagination dots
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_pages.length, (index) {
final isActive = index == _currentPage;
return Container(
margin: const EdgeInsets.symmetric(horizontal: 6),
width: isActive ? 32 : 8,
height: 8,
decoration: BoxDecoration(
color: isActive
? AppColors.accentOrange
: const Color(0xFFBCBCBC),
borderRadius: BorderRadius.circular(9999),
),
);
}),
),
const SizedBox(height: 40),
// Next / Get Started button
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 48),
child: SizedBox(
width: double.infinity,
height: 56,
child: DecoratedBox(
decoration: BoxDecoration(
color: AppColors.accentOrange,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: AppColors.accentOrange.withValues(alpha: 0.2),
blurRadius: 15,
offset: const Offset(0, 10),
spreadRadius: -3,
),
BoxShadow(
color: AppColors.accentOrange.withValues(alpha: 0.2),
blurRadius: 6,
offset: const Offset(0, 4),
spreadRadius: -4,
),
],
),
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: _nextPage,
borderRadius: BorderRadius.circular(12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_currentPage < _pages.length - 1
? 'Next'
: 'Get Started',
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
const SizedBox(width: 8),
SvgPicture.asset(
'assets/icons/arrow_right_white.svg',
width: 16,
height: 16,
),
],
),
),
),
),
),
),
],
),
),
),
);
}
}
class _OnboardingPage extends StatelessWidget {
final _OnboardingData data;
const _OnboardingPage({required this.data});
@override
Widget build(BuildContext context) {
return Column(
children: [
// Illustration section - takes upper portion
Expanded(
flex: 5,
child: Padding(
padding: const EdgeInsets.only(left: 0, top: 8),
child: Align(
alignment: Alignment.centerLeft,
child: SizedBox(
width: MediaQuery.of(context).size.width - 24,
child: Stack(
alignment: Alignment.center,
children: [
// Background oval/pill shape with orange gradient
SvgPicture.asset(
data.bgAsset,
width: 382,
height: 346,
fit: BoxFit.contain,
),
// Blurred decoration - bottom right
Positioned(
right: 20,
bottom: 20,
child: Container(
width: 160,
height: 160,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.accentOrange.withValues(alpha: 0.2),
boxShadow: [
BoxShadow(
color: AppColors.accentOrange
.withValues(alpha: 0.15),
blurRadius: 40,
spreadRadius: 0,
),
],
),
),
),
// Blurred decoration - top left
Positioned(
left: 0,
top: 0,
child: Container(
width: 160,
height: 160,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.accentOrange.withValues(alpha: 0.1),
boxShadow: [
BoxShadow(
color: AppColors.accentOrange
.withValues(alpha: 0.08),
blurRadius: 40,
spreadRadius: 0,
),
],
),
),
),
// Main icon + small icons
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
data.iconAsset,
width: data.iconWidth,
height: data.iconHeight,
),
const SizedBox(height: 16),
// Small property icons row
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
'assets/images/onboarding_small_icon_1.svg',
width: 27,
height: 27,
),
const SizedBox(width: 8),
SvgPicture.asset(
'assets/images/onboarding_small_icon_2.svg',
width: 31.5,
height: 31.5,
),
const SizedBox(width: 8),
SvgPicture.asset(
'assets/images/onboarding_small_icon_3.svg',
width: 24,
height: 27,
),
],
),
],
),
],
),
),
),
),
),
// Text content section
Expanded(
flex: 3,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Column(
children: [
const SizedBox(height: 8),
// Title
Text(
data.title,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'Fractul',
fontSize: 32,
fontWeight: FontWeight.w700,
color: Color(0xFF0F172A),
height: 1.25,
letterSpacing: -0.8,
),
),
const SizedBox(height: 16),
// Subtitle
Text(
data.subtitle,
textAlign: TextAlign.center,
style: const TextStyle(
fontFamily: 'SourceSerif4',
fontSize: 16,
fontWeight: FontWeight.w400,
color: Color(0xFF475569),
height: 1.625,
),
),
],
),
),
),
],
);
}
}
class _OnboardingData {
final String bgAsset;
final String iconAsset;
final double iconWidth;
final double iconHeight;
final String title;
final String subtitle;
const _OnboardingData({
required this.bgAsset,
required this.iconAsset,
required this.iconWidth,
required this.iconHeight,
required this.title,
required this.subtitle,
});
}

View File

@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:real_estate_mobile/core/constants/app_colors.dart'; import 'package:real_estate_mobile/core/constants/app_colors.dart';
class SplashScreen extends StatefulWidget { class SplashScreen extends StatefulWidget {
@@ -33,11 +34,20 @@ class _SplashScreenState extends State<SplashScreen>
_controller.forward(); _controller.forward();
_navigationTimer = Timer(const Duration(seconds: 3), () { _navigationTimer = Timer(const Duration(seconds: 3), _navigate);
if (mounted) { }
context.go('/home');
Future<void> _navigate() async {
if (!mounted) return;
final prefs = await SharedPreferences.getInstance();
final onboardingCompleted = prefs.getBool('onboarding_completed') ?? false;
if (!mounted) return;
if (onboardingCompleted) {
context.go('/home');
} else {
context.go('/onboarding');
} }
});
} }
@override @override

View File

@@ -21,6 +21,7 @@ import 'package:real_estate_mobile/features/notifications/presentation/screens/n
import 'package:real_estate_mobile/features/agents/presentation/screens/agent_edit_profile_screen.dart'; import 'package:real_estate_mobile/features/agents/presentation/screens/agent_edit_profile_screen.dart';
import 'package:real_estate_mobile/features/profile/presentation/screens/payment_success_screen.dart'; import 'package:real_estate_mobile/features/profile/presentation/screens/payment_success_screen.dart';
import 'package:real_estate_mobile/features/splash/presentation/screens/splash_screen.dart'; import 'package:real_estate_mobile/features/splash/presentation/screens/splash_screen.dart';
import 'package:real_estate_mobile/features/onboarding/presentation/screens/onboarding_screen.dart';
final routerProvider = Provider<GoRouter>((ref) { final routerProvider = Provider<GoRouter>((ref) {
final authRefreshNotifier = _AuthRefreshNotifier(); final authRefreshNotifier = _AuthRefreshNotifier();
@@ -51,8 +52,8 @@ final routerProvider = Provider<GoRouter>((ref) {
(route) => location == route || location.startsWith('$route/'), (route) => location == route || location.startsWith('$route/'),
); );
// Never redirect away from splash — it handles its own navigation // Never redirect away from splash or onboarding — they handle their own navigation
if (location == '/splash') return null; if (location == '/splash' || location == '/onboarding') return null;
// While checking auth status, don't redirect // While checking auth status, don't redirect
if (isLoading) return null; if (isLoading) return null;
@@ -75,6 +76,12 @@ final routerProvider = Provider<GoRouter>((ref) {
builder: (context, state) => const SplashScreen(), builder: (context, state) => const SplashScreen(),
), ),
// Onboarding (shown only once on first install)
GoRoute(
path: '/onboarding',
builder: (context, state) => const OnboardingScreen(),
),
// Auth routes (no shell — full screen) // Auth routes (no shell — full screen)
GoRoute( GoRoute(
path: '/login', path: '/login',