feat: onboarding page

This commit is contained in:
efrilm 2025-08-27 15:07:49 +07:00
parent 30007415c7
commit 3c24fff82e
16 changed files with 413 additions and 18 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 722 KiB

View File

@ -1,11 +1,24 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../presentation/components/assets/fonts.gen.dart';
part 'app_color.dart'; part 'app_color.dart';
part 'app_style.dart'; part 'app_style.dart';
part 'app_value.dart'; part 'app_value.dart';
class ThemeApp { class ThemeApp {
static ThemeData get theme => ThemeData( static ThemeData get theme => ThemeData(
useMaterial3: true, useMaterial3: true,
); fontFamily: FontFamily.quicksand,
primaryColor: AppColor.primary,
scaffoldBackgroundColor: AppColor.white,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: AppColor.primary,
foregroundColor: Colors.white,
elevation: 0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
),
),
);
} }

View File

@ -11,8 +11,9 @@ void main() async {
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle( const SystemUiOverlayStyle(
statusBarColor: Colors.transparent, statusBarColor: Colors.white, // background putih
statusBarIconBrightness: Brightness.dark, statusBarIconBrightness: Brightness.dark, // ikon/tulisan hitam
statusBarBrightness: Brightness.light, // khusus iOS biar teksnya gelap
), ),
); );

View File

@ -21,8 +21,26 @@ class $AssetsImagesGen {
/// File path: assets/images/logo.png /// File path: assets/images/logo.png
AssetGenImage get logo => const AssetGenImage('assets/images/logo.png'); AssetGenImage get logo => const AssetGenImage('assets/images/logo.png');
/// File path: assets/images/onboarding1.png
AssetGenImage get onboarding1 =>
const AssetGenImage('assets/images/onboarding1.png');
/// File path: assets/images/onboarding2.png
AssetGenImage get onboarding2 =>
const AssetGenImage('assets/images/onboarding2.png');
/// File path: assets/images/onboarding3.png
AssetGenImage get onboarding3 =>
const AssetGenImage('assets/images/onboarding3.png');
/// List of all assets /// List of all assets
List<AssetGenImage> get values => [launcher, logo]; List<AssetGenImage> get values => [
launcher,
logo,
onboarding1,
onboarding2,
onboarding3,
];
} }
class Assets { class Assets {

View File

@ -0,0 +1,5 @@
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
part 'elevated_button.dart';

View File

@ -0,0 +1,34 @@
part of 'button.dart';
class AppElevatedButton extends StatelessWidget {
const AppElevatedButton({
super.key,
required this.onPressed,
required this.title,
this.width = double.infinity,
this.height = 48.0,
});
final Function()? onPressed;
final String title;
final double width;
final double height;
@override
Widget build(BuildContext context) {
return SizedBox(
width: width,
height: height,
child: ElevatedButton(
onPressed: onPressed,
child: Text(
title,
style: AppStyle.lg.copyWith(
color: AppColor.white,
fontWeight: FontWeight.w700,
),
),
),
);
}
}

View File

@ -0,0 +1,6 @@
import 'package:flutter/material.dart';
import '../../../common/theme/theme.dart';
import '../assets/assets.gen.dart';
part 'image_placeholder.dart';

View File

@ -0,0 +1,83 @@
part of 'image.dart';
class ImagePlaceholder extends StatelessWidget {
const ImagePlaceholder({super.key});
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
decoration: const BoxDecoration(
color: Color(0x4DD9D9D9), // Light gray with opacity
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Hand holding coffee illustration
Container(
width: 120,
height: 160,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(60),
),
child: Stack(
children: [
// Hand
Positioned(
bottom: 20,
left: 30,
child: Container(
width: 60,
height: 80,
decoration: BoxDecoration(
color: const Color(0xFFFFDBB3),
borderRadius: BorderRadius.circular(30),
),
),
),
// Coffee cup
Positioned(
top: 30,
left: 25,
child: Container(
width: 70,
height: 90,
decoration: BoxDecoration(
color: const Color(0xFFF4E4BC),
borderRadius: BorderRadius.circular(10),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Fore logo
Assets.images.logo.image(
width: 40,
height: 40,
fit: BoxFit.contain,
),
const SizedBox(height: 8),
Text(
'Enaklo',
style: TextStyle(
color: AppColor.primary,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
],
),
),
],
),
);
}
}

View File

@ -0,0 +1,204 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart';
import '../../../common/theme/theme.dart';
import '../../components/assets/assets.gen.dart';
import '../../components/button/button.dart';
import '../../components/image/image.dart';
@RoutePage()
class OnboardingPage extends StatefulWidget {
const OnboardingPage({super.key});
@override
State<OnboardingPage> createState() => _OnboardingPageState();
}
class _OnboardingPageState extends State<OnboardingPage> {
int currentIndex = 0;
final CarouselSliderController _carouselController =
CarouselSliderController();
final List<OnboardingData> onboardingData = [
OnboardingData(
image: Assets.images.onboarding1.path,
title: 'Nikmati Hidangan Kapanpun, Dimanapun',
subtitle:
'Bebas pilih cara penyajian, bisa dine-in di restoran atau pesan antar langsung ke lokasimu',
),
OnboardingData(
image: Assets.images.onboarding2.path,
title: 'Bahan Berkualitas Premium',
subtitle:
'Kami hanya menggunakan bahan segar pilihan untuk menghadirkan cita rasa terbaik di setiap hidangan',
),
OnboardingData(
image: Assets.images.onboarding3.path,
title: 'Pesan dengan Praktis',
subtitle:
'Aplikasi dengan tampilan sederhana memudahkanmu memesan makanan favorit kapan saja',
),
];
@override
Widget build(BuildContext context) {
final screenHeight = MediaQuery.of(context).size.height;
final imageHeight = screenHeight * 0.6; // 60% dari tinggi layar
return Scaffold(
backgroundColor: Colors.white, // Changed to white background
body: Column(
children: [
// Image carousel - 60% of screen height
SizedBox(height: imageHeight, child: _buildImageCarousel()),
// Bottom content - remaining space
Expanded(child: _buildBottomContent()),
],
),
);
}
Widget _buildImageCarousel() {
return Stack(
children: [
// Carousel
CarouselSlider.builder(
carouselController: _carouselController,
itemCount: onboardingData.length,
itemBuilder: (context, index, realIndex) {
return SizedBox(
width: double.infinity, // Full width
child: ClipRRect(
child: Image.asset(
onboardingData[index].image,
fit: BoxFit.fill,
width: double.infinity,
errorBuilder: (context, error, stackTrace) {
return ImagePlaceholder();
},
),
),
);
},
options: CarouselOptions(
height: double.infinity,
viewportFraction: 1.0,
enableInfiniteScroll: true,
autoPlay: true,
onPageChanged: (index, reason) {
setState(() {
currentIndex = index;
});
},
),
),
// White overlay gradient at bottom
Positioned(
bottom: 0,
left: 0,
right: 0,
child: Container(
height: 100,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Colors.white.withOpacity(0.1),
Color(0x80FFFFFF), // White with 50% opacity
Colors.white, // Pure white
],
),
),
),
),
// Dots indicator positioned over the white overlay
Positioned(
bottom: 30,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
onboardingData.length,
(index) => Container(
margin: const EdgeInsets.symmetric(horizontal: 4),
height: 8,
width: currentIndex == index ? 24 : 8,
decoration: BoxDecoration(
color: currentIndex == index
? AppColor.primary
: AppColor.borderLight,
borderRadius: BorderRadius.circular(4),
),
),
),
),
),
],
);
}
Widget _buildBottomContent() {
return Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
const SizedBox(height: 16),
// Title
Text(
onboardingData[currentIndex].title,
textAlign: TextAlign.center,
style: AppStyle.xl.copyWith(fontWeight: FontWeight.w800),
),
const SizedBox(height: 12),
// Subtitle
Text(
onboardingData[currentIndex].subtitle,
textAlign: TextAlign.center,
style: AppStyle.md.copyWith(
fontWeight: FontWeight.w500,
color: AppColor.textSecondary,
),
),
const Spacer(),
AppElevatedButton(onPressed: () {}, title: 'Masuk'),
const SizedBox(height: 12),
TextButton(
onPressed: () {},
child: Text(
'Lewati tahap ini',
style: AppStyle.md.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
}
class OnboardingData {
final String image;
final String title;
final String subtitle;
OnboardingData({
required this.image,
required this.title,
required this.subtitle,
});
}

View File

@ -1,9 +1,12 @@
import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async'; import 'dart:async';
import '../../../common/theme/theme.dart'; import '../../../common/theme/theme.dart';
import '../../components/assets/assets.gen.dart'; import '../../components/assets/assets.gen.dart';
import '../../router/app_router.gr.dart';
@RoutePage()
class SplashPage extends StatefulWidget { class SplashPage extends StatefulWidget {
const SplashPage({super.key}); const SplashPage({super.key});
@ -51,9 +54,7 @@ class _SplashPageState extends State<SplashPage>
void _navigateToHome() { void _navigateToHome() {
Timer(const Duration(milliseconds: 2500), () { Timer(const Duration(milliseconds: 2500), () {
// Navigate to your main screen here context.router.push(OnboardingRoute());
// Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomeScreen()));
print("Navigate to Home Screen");
}); });
} }

View File

@ -5,7 +5,10 @@ import 'app_router.gr.dart';
class AppRouter extends RootStackRouter { class AppRouter extends RootStackRouter {
@override @override
List<AutoRoute> get routes => [ List<AutoRoute> get routes => [
// Splash // Splash
AutoRoute(page: SplashRoute.page, initial: true), AutoRoute(page: SplashRoute.page, initial: true),
];
// Onboarding
AutoRoute(page: OnboardingRoute.page),
];
} }

View File

@ -9,21 +9,39 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: no_leading_underscores_for_library_prefixes // ignore_for_file: no_leading_underscores_for_library_prefixes
import 'package:auto_route/auto_route.dart' as _i2; import 'package:auto_route/auto_route.dart' as _i3;
import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i1; import 'package:enaklo/presentation/pages/onboarding/onboarding_page.dart'
as _i1;
import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i2;
/// generated route for /// generated route for
/// [_i1.SplashPage] /// [_i1.OnboardingPage]
class SplashRoute extends _i2.PageRouteInfo<void> { class OnboardingRoute extends _i3.PageRouteInfo<void> {
const SplashRoute({List<_i2.PageRouteInfo>? children}) const OnboardingRoute({List<_i3.PageRouteInfo>? children})
: super(OnboardingRoute.name, initialChildren: children);
static const String name = 'OnboardingRoute';
static _i3.PageInfo page = _i3.PageInfo(
name,
builder: (data) {
return const _i1.OnboardingPage();
},
);
}
/// generated route for
/// [_i2.SplashPage]
class SplashRoute extends _i3.PageRouteInfo<void> {
const SplashRoute({List<_i3.PageRouteInfo>? children})
: super(SplashRoute.name, initialChildren: children); : super(SplashRoute.name, initialChildren: children);
static const String name = 'SplashRoute'; static const String name = 'SplashRoute';
static _i2.PageInfo page = _i2.PageInfo( static _i3.PageInfo page = _i3.PageInfo(
name, name,
builder: (data) { builder: (data) {
return const _i1.SplashPage(); return const _i2.SplashPage();
}, },
); );
} }

View File

@ -137,6 +137,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.11.1" version: "8.11.1"
carousel_slider:
dependency: "direct main"
description:
name: carousel_slider
sha256: bcc61735345c9ab5cb81073896579e735f81e35fd588907a393143ea986be8ff
url: "https://pub.dev"
source: hosted
version: "5.1.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:

View File

@ -27,6 +27,7 @@ dependencies:
freezed_annotation: ^2.4.1 freezed_annotation: ^2.4.1
json_annotation: ^4.9.0 json_annotation: ^4.9.0
shared_preferences: ^2.5.3 shared_preferences: ^2.5.3
carousel_slider: ^5.1.1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: