diff --git a/assets/images/onboarding1.png b/assets/images/onboarding1.png new file mode 100644 index 0000000..915d15c Binary files /dev/null and b/assets/images/onboarding1.png differ diff --git a/assets/images/onboarding2.png b/assets/images/onboarding2.png new file mode 100644 index 0000000..3241ae5 Binary files /dev/null and b/assets/images/onboarding2.png differ diff --git a/assets/images/onboarding3.png b/assets/images/onboarding3.png new file mode 100644 index 0000000..efeb1ea Binary files /dev/null and b/assets/images/onboarding3.png differ diff --git a/lib/common/theme/theme.dart b/lib/common/theme/theme.dart index 4d69f35..1f4cef9 100644 --- a/lib/common/theme/theme.dart +++ b/lib/common/theme/theme.dart @@ -1,11 +1,24 @@ import 'package:flutter/material.dart'; +import '../../presentation/components/assets/fonts.gen.dart'; + part 'app_color.dart'; part 'app_style.dart'; part 'app_value.dart'; class ThemeApp { 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)), + ), + ), + ); } diff --git a/lib/main.dart b/lib/main.dart index 826ecd4..b9daa12 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,8 +11,9 @@ void main() async { SystemChrome.setSystemUIOverlayStyle( const SystemUiOverlayStyle( - statusBarColor: Colors.transparent, - statusBarIconBrightness: Brightness.dark, + statusBarColor: Colors.white, // background putih + statusBarIconBrightness: Brightness.dark, // ikon/tulisan hitam + statusBarBrightness: Brightness.light, // khusus iOS biar teksnya gelap ), ); diff --git a/lib/presentation/components/assets/assets.gen.dart b/lib/presentation/components/assets/assets.gen.dart index dae6668..16673c3 100644 --- a/lib/presentation/components/assets/assets.gen.dart +++ b/lib/presentation/components/assets/assets.gen.dart @@ -21,8 +21,26 @@ class $AssetsImagesGen { /// File path: 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 get values => [launcher, logo]; + List get values => [ + launcher, + logo, + onboarding1, + onboarding2, + onboarding3, + ]; } class Assets { diff --git a/lib/presentation/components/button/button.dart b/lib/presentation/components/button/button.dart new file mode 100644 index 0000000..739165b --- /dev/null +++ b/lib/presentation/components/button/button.dart @@ -0,0 +1,5 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; + +part 'elevated_button.dart'; diff --git a/lib/presentation/components/button/elevated_button.dart b/lib/presentation/components/button/elevated_button.dart new file mode 100644 index 0000000..40ca12d --- /dev/null +++ b/lib/presentation/components/button/elevated_button.dart @@ -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, + ), + ), + ), + ); + } +} diff --git a/lib/presentation/components/image/image.dart b/lib/presentation/components/image/image.dart new file mode 100644 index 0000000..c355993 --- /dev/null +++ b/lib/presentation/components/image/image.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +import '../../../common/theme/theme.dart'; +import '../assets/assets.gen.dart'; + +part 'image_placeholder.dart'; diff --git a/lib/presentation/components/image/image_placeholder.dart b/lib/presentation/components/image/image_placeholder.dart new file mode 100644 index 0000000..11bab48 --- /dev/null +++ b/lib/presentation/components/image/image_placeholder.dart @@ -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, + ), + ), + ], + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/pages/onboarding/onboarding_page.dart b/lib/presentation/pages/onboarding/onboarding_page.dart new file mode 100644 index 0000000..5c34dfa --- /dev/null +++ b/lib/presentation/pages/onboarding/onboarding_page.dart @@ -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 createState() => _OnboardingPageState(); +} + +class _OnboardingPageState extends State { + int currentIndex = 0; + final CarouselSliderController _carouselController = + CarouselSliderController(); + + final List 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, + }); +} diff --git a/lib/presentation/pages/splash/splash_page.dart b/lib/presentation/pages/splash/splash_page.dart index eda1666..59ca6cc 100644 --- a/lib/presentation/pages/splash/splash_page.dart +++ b/lib/presentation/pages/splash/splash_page.dart @@ -1,9 +1,12 @@ +import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'dart:async'; import '../../../common/theme/theme.dart'; import '../../components/assets/assets.gen.dart'; +import '../../router/app_router.gr.dart'; +@RoutePage() class SplashPage extends StatefulWidget { const SplashPage({super.key}); @@ -51,9 +54,7 @@ class _SplashPageState extends State void _navigateToHome() { Timer(const Duration(milliseconds: 2500), () { - // Navigate to your main screen here - // Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => HomeScreen())); - print("Navigate to Home Screen"); + context.router.push(OnboardingRoute()); }); } diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 4c75faf..c878605 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -5,7 +5,10 @@ import 'app_router.gr.dart'; class AppRouter extends RootStackRouter { @override List get routes => [ - // Splash - AutoRoute(page: SplashRoute.page, initial: true), - ]; + // Splash + AutoRoute(page: SplashRoute.page, initial: true), + + // Onboarding + AutoRoute(page: OnboardingRoute.page), + ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index c58a1d5..ba8a93c 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -9,21 +9,39 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i2; -import 'package:enaklo/presentation/pages/splash/splash_page.dart' as _i1; +import 'package:auto_route/auto_route.dart' as _i3; +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 -/// [_i1.SplashPage] -class SplashRoute extends _i2.PageRouteInfo { - const SplashRoute({List<_i2.PageRouteInfo>? children}) +/// [_i1.OnboardingPage] +class OnboardingRoute extends _i3.PageRouteInfo { + 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 { + const SplashRoute({List<_i3.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i2.PageInfo page = _i2.PageInfo( + static _i3.PageInfo page = _i3.PageInfo( name, builder: (data) { - return const _i1.SplashPage(); + return const _i2.SplashPage(); }, ); } diff --git a/pubspec.lock b/pubspec.lock index b8f5014..efe4f0c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -137,6 +137,14 @@ packages: url: "https://pub.dev" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5e5145f..2881c9f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: freezed_annotation: ^2.4.1 json_annotation: ^4.9.0 shared_preferences: ^2.5.3 + carousel_slider: ^5.1.1 dev_dependencies: flutter_test: