diff --git a/.metadata b/.metadata index e8f7bf9..878648b 100644 --- a/.metadata +++ b/.metadata @@ -18,21 +18,6 @@ migration: - platform: android create_revision: ea121f8859e4b13e47a8f845e4586164519588bc base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - - platform: ios - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - - platform: linux - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - - platform: macos - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - - platform: web - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc - - platform: windows - create_revision: ea121f8859e4b13e47a8f845e4586164519588bc - base_revision: ea121f8859e4b13e47a8f845e4586164519588bc # User provided section diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 69228fc..477529a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,8 @@ + + + + + + diff --git a/assets/icons/Line 4.svg b/assets/icons/Line 4.svg new file mode 100644 index 0000000..c3cd5f5 --- /dev/null +++ b/assets/icons/Line 4.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/MDS-Public-TW-Tag.svg b/assets/icons/MDS-Public-TW-Tag.svg new file mode 100644 index 0000000..0b4dcd4 --- /dev/null +++ b/assets/icons/MDS-Public-TW-Tag.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/icons/Rectangle off.svg b/assets/icons/Rectangle off.svg new file mode 100644 index 0000000..a5427a6 --- /dev/null +++ b/assets/icons/Rectangle off.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/Shape.svg b/assets/icons/Shape.svg new file mode 100644 index 0000000..b1c75ab --- /dev/null +++ b/assets/icons/Shape.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/Shop.svg b/assets/icons/Shop.svg new file mode 100644 index 0000000..78c352d --- /dev/null +++ b/assets/icons/Shop.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/Shop2.svg b/assets/icons/Shop2.svg new file mode 100644 index 0000000..78c352d --- /dev/null +++ b/assets/icons/Shop2.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/arrow-down-black.svg b/assets/icons/arrow-down-black.svg new file mode 100644 index 0000000..f602dd7 --- /dev/null +++ b/assets/icons/arrow-down-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/card-pos.svg b/assets/icons/card-pos.svg new file mode 100644 index 0000000..8abb308 --- /dev/null +++ b/assets/icons/card-pos.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/category-2.svg b/assets/icons/category-2.svg new file mode 100644 index 0000000..c4d0def --- /dev/null +++ b/assets/icons/category-2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/coin.svg b/assets/icons/coin.svg new file mode 100644 index 0000000..1c3713f --- /dev/null +++ b/assets/icons/coin.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/down.svg b/assets/icons/down.svg new file mode 100644 index 0000000..652c75b --- /dev/null +++ b/assets/icons/down.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/element-equal.svg b/assets/icons/element-equal.svg new file mode 100644 index 0000000..2fa0922 --- /dev/null +++ b/assets/icons/element-equal.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/fluent-color_location-ripple-16.svg b/assets/icons/fluent-color_location-ripple-16.svg new file mode 100644 index 0000000..c07f1a0 --- /dev/null +++ b/assets/icons/fluent-color_location-ripple-16.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/ic_round-local-offer.svg b/assets/icons/ic_round-local-offer.svg new file mode 100644 index 0000000..966695e --- /dev/null +++ b/assets/icons/ic_round-local-offer.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/infoPic.svg b/assets/icons/infoPic.svg new file mode 100644 index 0000000..2c78e4b --- /dev/null +++ b/assets/icons/infoPic.svg @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/list.svg b/assets/icons/list.svg new file mode 100644 index 0000000..2fa0922 --- /dev/null +++ b/assets/icons/list.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/map selected.svg b/assets/icons/map selected.svg new file mode 100644 index 0000000..7ef1b18 --- /dev/null +++ b/assets/icons/map selected.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/map.svg b/assets/icons/map.svg new file mode 100644 index 0000000..f29e012 --- /dev/null +++ b/assets/icons/map.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/material-symbols_location-air.svg b/assets/icons/material-symbols_location-air.svg new file mode 100644 index 0000000..0a47b7c --- /dev/null +++ b/assets/icons/material-symbols_location-air.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/material-symbols_location-ent.svg b/assets/icons/material-symbols_location-ent.svg new file mode 100644 index 0000000..1ceff47 --- /dev/null +++ b/assets/icons/material-symbols_location-ent.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/material-symbols_location-family.svg b/assets/icons/material-symbols_location-family.svg new file mode 100644 index 0000000..6bd0733 --- /dev/null +++ b/assets/icons/material-symbols_location-family.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/material-symbols_location-health.svg b/assets/icons/material-symbols_location-health.svg new file mode 100644 index 0000000..138bd10 --- /dev/null +++ b/assets/icons/material-symbols_location-health.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/material-symbols_location-on.svg b/assets/icons/material-symbols_location-on.svg new file mode 100644 index 0000000..4c3489c --- /dev/null +++ b/assets/icons/material-symbols_location-on.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/material-symbols_location-onn.svg b/assets/icons/material-symbols_location-onn.svg new file mode 100644 index 0000000..4c3489c --- /dev/null +++ b/assets/icons/material-symbols_location-onn.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/material-symbols_location-work.svg b/assets/icons/material-symbols_location-work.svg new file mode 100644 index 0000000..bed63a9 --- /dev/null +++ b/assets/icons/material-symbols_location-work.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/ph_cheese.svg b/assets/icons/ph_cheese.svg new file mode 100644 index 0000000..237b764 --- /dev/null +++ b/assets/icons/ph_cheese.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/recenter.svg b/assets/icons/recenter.svg new file mode 100644 index 0000000..16bd701 --- /dev/null +++ b/assets/icons/recenter.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/routing.svg b/assets/icons/routing.svg new file mode 100644 index 0000000..f2ec483 --- /dev/null +++ b/assets/icons/routing.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/selected list.svg b/assets/icons/selected list.svg new file mode 100644 index 0000000..9fc2817 --- /dev/null +++ b/assets/icons/selected list.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/icons/star.svg b/assets/icons/star.svg new file mode 100644 index 0000000..981d528 --- /dev/null +++ b/assets/icons/star.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/timer-pause.svg b/assets/icons/timer-pause.svg new file mode 100644 index 0000000..7d37c7a --- /dev/null +++ b/assets/icons/timer-pause.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/timer-start.svg b/assets/icons/timer-start.svg new file mode 100644 index 0000000..fa356fc --- /dev/null +++ b/assets/icons/timer-start.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/icons/timer.svg b/assets/icons/timer.svg new file mode 100644 index 0000000..4d36bc4 --- /dev/null +++ b/assets/icons/timer.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/Image.png b/assets/images/Image.png new file mode 100644 index 0000000..fc154d4 Binary files /dev/null and b/assets/images/Image.png differ diff --git a/assets/images/Media.png b/assets/images/Media.png new file mode 100644 index 0000000..55aa927 Binary files /dev/null and b/assets/images/Media.png differ diff --git a/assets/images/top deals and stores.png b/assets/images/top deals and stores.png new file mode 100644 index 0000000..b640605 Binary files /dev/null and b/assets/images/top deals and stores.png differ diff --git a/assets/images/userinfo.svg b/assets/images/userinfo.svg new file mode 100644 index 0000000..2c78e4b --- /dev/null +++ b/assets/images/userinfo.svg @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/extension/screenSize.dart b/lib/extension/screenSize.dart new file mode 100644 index 0000000..108df24 --- /dev/null +++ b/lib/extension/screenSize.dart @@ -0,0 +1,7 @@ +import 'package:flutter/material.dart'; + +extension ScreenSize on BuildContext { + Size get screenSize => MediaQuery.of(this).size; + double get screenWidth => screenSize.width; + double get screenHeight => screenSize.height; +} \ No newline at end of file diff --git a/lib/gen/assets.gen.dart b/lib/gen/assets.gen.dart index 7e05641..a3338aa 100644 --- a/lib/gen/assets.gen.dart +++ b/lib/gen/assets.gen.dart @@ -15,10 +15,37 @@ import 'package:vector_graphics/vector_graphics.dart' as _vg; class $AssetsIconsGen { const $AssetsIconsGen(); + /// File path: assets/icons/Line 1.svg + SvgGenImage get line1 => const SvgGenImage('assets/icons/Line 1.svg'); + + /// File path: assets/icons/Line 4.svg + SvgGenImage get line4 => const SvgGenImage('assets/icons/Line 4.svg'); + + /// File path: assets/icons/MDS-Public-TW-Tag.svg + SvgGenImage get mDSPublicTWTag => + const SvgGenImage('assets/icons/MDS-Public-TW-Tag.svg'); + /// File path: assets/icons/Next Button.svg SvgGenImage get nextButton => const SvgGenImage('assets/icons/Next Button.svg'); + /// File path: assets/icons/Rectangle off.svg + SvgGenImage get rectangleOff => + const SvgGenImage('assets/icons/Rectangle off.svg'); + + /// File path: assets/icons/Shape.svg + SvgGenImage get shape => const SvgGenImage('assets/icons/Shape.svg'); + + /// File path: assets/icons/Shop.svg + SvgGenImage get shop => const SvgGenImage('assets/icons/Shop.svg'); + + /// File path: assets/icons/Shop2.svg + SvgGenImage get shop2 => const SvgGenImage('assets/icons/Shop2.svg'); + + /// File path: assets/icons/arrow-down-black.svg + SvgGenImage get arrowDownBlack => + const SvgGenImage('assets/icons/arrow-down-black.svg'); + /// File path: assets/icons/arrow-down.svg SvgGenImage get arrowDown => const SvgGenImage('assets/icons/arrow-down.svg'); @@ -28,19 +55,97 @@ class $AssetsIconsGen { /// File path: assets/icons/back.svg SvgGenImage get back => const SvgGenImage('assets/icons/back.svg'); + /// File path: assets/icons/card-pos.svg + SvgGenImage get cardPos => const SvgGenImage('assets/icons/card-pos.svg'); + + /// File path: assets/icons/category-2.svg + SvgGenImage get category2 => const SvgGenImage('assets/icons/category-2.svg'); + /// File path: assets/icons/clander.svg SvgGenImage get clander => const SvgGenImage('assets/icons/clander.svg'); + /// File path: assets/icons/coin.svg + SvgGenImage get coin => const SvgGenImage('assets/icons/coin.svg'); + + /// File path: assets/icons/down.svg + SvgGenImage get down => const SvgGenImage('assets/icons/down.svg'); + + /// File path: assets/icons/element-equal.svg + SvgGenImage get elementEqual => + const SvgGenImage('assets/icons/element-equal.svg'); + + /// File path: assets/icons/fluent-color_location-ripple-16.svg + SvgGenImage get fluentColorLocationRipple16 => + const SvgGenImage('assets/icons/fluent-color_location-ripple-16.svg'); + + /// File path: assets/icons/ic_round-local-offer.svg + SvgGenImage get icRoundLocalOffer => + const SvgGenImage('assets/icons/ic_round-local-offer.svg'); + + /// File path: assets/icons/infoPic.svg + SvgGenImage get infoPic => const SvgGenImage('assets/icons/infoPic.svg'); + + /// File path: assets/icons/list.svg + SvgGenImage get list => const SvgGenImage('assets/icons/list.svg'); + /// File path: assets/icons/location.svg SvgGenImage get location => const SvgGenImage('assets/icons/location.svg'); + /// File path: assets/icons/map selected.svg + SvgGenImage get mapSelected => + const SvgGenImage('assets/icons/map selected.svg'); + + /// File path: assets/icons/map.svg + SvgGenImage get map => const SvgGenImage('assets/icons/map.svg'); + + /// File path: assets/icons/material-symbols_location-air.svg + SvgGenImage get materialSymbolsLocationAir => + const SvgGenImage('assets/icons/material-symbols_location-air.svg'); + + /// File path: assets/icons/material-symbols_location-ent.svg + SvgGenImage get materialSymbolsLocationEnt => + const SvgGenImage('assets/icons/material-symbols_location-ent.svg'); + + /// File path: assets/icons/material-symbols_location-family.svg + SvgGenImage get materialSymbolsLocationFamily => + const SvgGenImage('assets/icons/material-symbols_location-family.svg'); + + /// File path: assets/icons/material-symbols_location-health.svg + SvgGenImage get materialSymbolsLocationHealth => + const SvgGenImage('assets/icons/material-symbols_location-health.svg'); + + /// File path: assets/icons/material-symbols_location-on.svg + SvgGenImage get materialSymbolsLocationOn => + const SvgGenImage('assets/icons/material-symbols_location-on.svg'); + + /// File path: assets/icons/material-symbols_location-onn.svg + SvgGenImage get materialSymbolsLocationOnn => + const SvgGenImage('assets/icons/material-symbols_location-onn.svg'); + + /// File path: assets/icons/material-symbols_location-work.svg + SvgGenImage get materialSymbolsLocationWork => + const SvgGenImage('assets/icons/material-symbols_location-work.svg'); + /// File path: assets/icons/next.svg SvgGenImage get next => const SvgGenImage('assets/icons/next.svg'); + /// File path: assets/icons/ph_cheese.svg + SvgGenImage get phCheese => const SvgGenImage('assets/icons/ph_cheese.svg'); + + /// File path: assets/icons/recenter.svg + SvgGenImage get recenter => const SvgGenImage('assets/icons/recenter.svg'); + /// File path: assets/icons/ri_search-2-line.svg SvgGenImage get riSearch2Line => const SvgGenImage('assets/icons/ri_search-2-line.svg'); + /// File path: assets/icons/routing.svg + SvgGenImage get routing => const SvgGenImage('assets/icons/routing.svg'); + + /// File path: assets/icons/selected list.svg + SvgGenImage get selectedList => + const SvgGenImage('assets/icons/selected list.svg'); + /// File path: assets/icons/shopping-cart.svg SvgGenImage get shoppingCart => const SvgGenImage('assets/icons/shopping-cart.svg'); @@ -57,21 +162,69 @@ class $AssetsIconsGen { /// File path: assets/icons/sort.svg SvgGenImage get sort => const SvgGenImage('assets/icons/sort.svg'); + /// File path: assets/icons/star.svg + SvgGenImage get star => const SvgGenImage('assets/icons/star.svg'); + + /// File path: assets/icons/timer-pause.svg + SvgGenImage get timerPause => + const SvgGenImage('assets/icons/timer-pause.svg'); + + /// File path: assets/icons/timer-start.svg + SvgGenImage get timerStart => + const SvgGenImage('assets/icons/timer-start.svg'); + + /// File path: assets/icons/timer.svg + SvgGenImage get timer => const SvgGenImage('assets/icons/timer.svg'); + /// List of all assets List get values => [ + line1, + line4, + mDSPublicTWTag, nextButton, + rectangleOff, + shape, + shop, + shop2, + arrowDownBlack, arrowDown, arrowLeft, back, + cardPos, + category2, clander, + coin, + down, + elementEqual, + fluentColorLocationRipple16, + icRoundLocalOffer, + infoPic, + list, location, + mapSelected, + map, + materialSymbolsLocationAir, + materialSymbolsLocationEnt, + materialSymbolsLocationFamily, + materialSymbolsLocationHealth, + materialSymbolsLocationOn, + materialSymbolsLocationOnn, + materialSymbolsLocationWork, next, + phCheese, + recenter, riSearch2Line, + routing, + selectedList, shoppingCart, slide2, slide3, slides1, sort, + star, + timerPause, + timerStart, + timer, ]; } @@ -85,6 +238,12 @@ class $AssetsImagesGen { SvgGenImage get googleSvg => const SvgGenImage('assets/images/Google svg.svg'); + /// File path: assets/images/Image.png + AssetGenImage get image => const AssetGenImage('assets/images/Image.png'); + + /// File path: assets/images/Media.png + AssetGenImage get media => const AssetGenImage('assets/images/Media.png'); + /// File path: assets/images/logo.svg SvgGenImage get logo => const SvgGenImage('assets/images/logo.svg'); @@ -96,17 +255,28 @@ class $AssetsImagesGen { SvgGenImage get ounboarding1 => const SvgGenImage('assets/images/ounboarding 1.svg'); + /// File path: assets/images/top deals and stores.png + AssetGenImage get topDealsAndStores => + const AssetGenImage('assets/images/top deals and stores.png'); + /// File path: assets/images/usa.png AssetGenImage get usa => const AssetGenImage('assets/images/usa.png'); + /// File path: assets/images/userinfo.svg + SvgGenImage get userinfo => const SvgGenImage('assets/images/userinfo.svg'); + /// List of all assets List get values => [ frame, googleSvg, + image, + media, logo, onboarding3, ounboarding1, + topDealsAndStores, usa, + userinfo, ]; } diff --git a/lib/gen/fonts.gen.dart b/lib/gen/fonts.gen.dart new file mode 100644 index 0000000..7298f8c --- /dev/null +++ b/lib/gen/fonts.gen.dart @@ -0,0 +1,15 @@ +/// GENERATED CODE - DO NOT MODIFY BY HAND +/// ***************************************************** +/// FlutterGen +/// ***************************************************** + +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use + +class FontFamily { + FontFamily._(); + + /// Font family: Roboto + static const String roboto = 'Roboto'; +} diff --git a/lib/main.dart b/lib/main.dart index e3f5566..d99099c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -20,6 +20,8 @@ class MyApp extends StatelessWidget { return MaterialApp( title: 'LBA', theme: ThemeData( + fontFamily: 'Roboto', + scaffoldBackgroundColor: Colors.white, primaryColor: const Color.fromARGB(255, 14, 63, 102), buttonTheme: const ButtonThemeData( buttonColor: Color.fromARGB(255, 14, 63, 102), @@ -27,6 +29,19 @@ class MyApp extends StatelessWidget { appBarTheme: const AppBarTheme( backgroundColor: Color.fromARGB(255, 14, 63, 102), ), + dialogTheme: DialogTheme( + backgroundColor: Colors.white, + ), + inputDecorationTheme: const InputDecorationTheme( + labelStyle: TextStyle(color: Colors.black), + hintStyle: TextStyle(color: Colors.grey), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide(color: Colors.grey), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide(color: Color.fromARGB(255, 14, 63, 102), width: 2), + ), + ), ), home: const OnboardingScreen(), ); diff --git a/lib/res/colors.dart b/lib/res/colors.dart new file mode 100644 index 0000000..6e20551 --- /dev/null +++ b/lib/res/colors.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class LightAppColors{ + LightAppColors._(); + + static const hint = Color.fromARGB(255, 88, 88, 88); + static const inputBorder = Color.fromARGB(255, 14, 63, 102); + static const primary = Color.fromARGB(255, 33, 150, 243); + static const slectedTitle = Color.fromARGB(255, 153, 207, 249); + static const slectedText = Color.fromARGB(255, 153, 207, 249); + static const confirmButton = Color.fromARGB(255, 76, 175, 80); + static const popupText = Color.fromARGB(255, 157, 157, 155); + static const confirmPopup = Color.fromARGB(255, 23, 107,173); + static const nearbyPopup = Color.fromARGB(255, 233, 245,254); + static const nearbyPopuphint = Color.fromARGB(255, 112, 112, 110); +} \ No newline at end of file diff --git a/lib/screens/auth/cubit/auth_cubit.dart b/lib/screens/auth/cubit/auth_cubit.dart index 8fb9dc2..4a87173 100644 --- a/lib/screens/auth/cubit/auth_cubit.dart +++ b/lib/screens/auth/cubit/auth_cubit.dart @@ -6,12 +6,18 @@ part 'auth_state.dart'; class AuthCubit extends Cubit { AuthCubit() : super(AuthInitial()); - + + String _timeStamp = ""; + String _timeDue = ""; + final Dio _dio = Dio(BaseOptions( connectTimeout: const Duration(seconds: 30), receiveTimeout: const Duration(seconds: 30), )); + String get timeStamp => _timeStamp; + String get timeDue => _timeDue; + Future sendOTP(String phoneNumber) async { emit(AuthLoading()); try { @@ -20,21 +26,25 @@ class AuthCubit extends Cubit { data: {'userId': phoneNumber}, ); print(response.toString()); - + if (response.statusCode == 200 || response.statusCode == 201) { if (response.data['success'] == true) { + _timeStamp = response.data['timestamp'].toString(); + _timeDue = response.data['due'].toString(); emit(AuthSuccess(phoneNumber: phoneNumber)); print(response.data.toString()); } else { emit(AuthError('Failed to send OTP')); } } else { - emit(AuthError('Server error: ${response.statusCode}')); + emit(AuthError('Server error: \${response.statusCode}')); } } on DioException catch (e) { - emit(AuthError('Connection error: ${e.message}')); + emit(AuthError('Connection error: \${e.message}')); } catch (e) { emit(AuthError('Unexpected error occurred')); } } + + void verifyOTP(String otpCode) {} } \ No newline at end of file diff --git a/lib/screens/auth/cubit/auth_state.dart b/lib/screens/auth/cubit/auth_state.dart index 0837854..454efcc 100644 --- a/lib/screens/auth/cubit/auth_state.dart +++ b/lib/screens/auth/cubit/auth_state.dart @@ -1,6 +1,6 @@ part of 'auth_cubit.dart'; -@immutable + sealed class AuthState {} final class AuthInitial extends AuthState {} diff --git a/lib/screens/auth/login.dart b/lib/screens/auth/login.dart index a31035e..da1d890 100644 --- a/lib/screens/auth/login.dart +++ b/lib/screens/auth/login.dart @@ -1,10 +1,13 @@ +import 'package:country_pickers/country.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:country_pickers/country_pickers.dart'; +import 'package:lba/extension/screenSize.dart'; import 'package:lba/gen/assets.gen.dart'; import 'package:lba/screens/auth/cubit/auth_cubit.dart'; import 'package:lba/screens/auth/otpVerifcation.dart'; -import 'package:lba/widgets/loginButton.dart'; +import 'package:lba/widgets/button.dart'; class Login extends StatefulWidget { const Login({super.key}); @@ -15,18 +18,20 @@ class Login extends StatefulWidget { class _LoginState extends State { final TextEditingController phoneController = TextEditingController(); - - final List> flagList = [ - {"image": "assets/images/usa.png", "code": "+1"}, - {"image": "assets/images/usa.png", "code": "+98"}, - {"image": "assets/images/usa.png", "code": "+44"}, - ]; - - int selectedIndex = 0; + final TextEditingController countryController = TextEditingController(); + Country _selectedCountry = CountryPickerUtils.getCountryByPhoneCode('971'); bool keepSignedIn = false; + @override + void initState() { + super.initState(); + countryController.text = _selectedCountry.name; + } + @override Widget build(BuildContext context) { + final height = context.screenHeight; + return Scaffold( body: SingleChildScrollView( child: Padding( @@ -37,92 +42,112 @@ class _LoginState extends State { Center( child: Padding( padding: const EdgeInsets.only(top: 50), - child: SvgPicture.asset(Assets.images.logo.path), + child: SvgPicture.asset( + Assets.images.logo.path, + height: height / 5.2, + ), ), ), - const SizedBox(height: 24), + SizedBox(height: height / 25), const Text( "Login", - style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold), + style: TextStyle(fontSize: 35, fontWeight: FontWeight.bold), + ), + SizedBox(height: height / 100), + const Text( + "Please enter your phone number", + style: TextStyle(fontSize: 17), ), - const SizedBox(height: 8), - const Text("Please enter your phone number"), const SizedBox(height: 25), + TextField( - controller: phoneController, - keyboardType: TextInputType.phone, + controller: countryController, + readOnly: true, + onTap: () => _openCountryPicker(context), decoration: InputDecoration( - labelText: "Phone Number", - labelStyle: const TextStyle( - color: Color.fromARGB(255, 61, 61, 61), + labelText: "Country", + prefixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12,vertical: 15), + child: CountryPickerUtils.getDefaultFlagImage(_selectedCountry), + ), + suffixIcon: Padding( + padding: const EdgeInsets.all(12.0), + child: SvgPicture.asset(Assets.icons.arrowDownBlack.path), ), - hintText: "Phone Number", border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - ), + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color.fromARGB(255, 14, 63, 102), + ),), focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + borderRadius: BorderRadius.circular(5), borderSide: const BorderSide( color: Color.fromARGB(255, 14, 63, 102), width: 2, ), ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Color.fromARGB(255, 14, 63, 102), + ), + ), + + const SizedBox(height: 16), + + TextField( + controller: phoneController, + keyboardType: TextInputType.phone, + decoration: InputDecoration( + labelText: "Phone Number", + hintText: "_ _ _ _ _ _ _ _ _ _ _", + labelStyle: TextStyle( + color: Colors.black + ), + prefix: Text( + "+${_selectedCountry.phoneCode} ", + style: const TextStyle( + fontSize: 16, + color: Colors.black, + fontWeight: FontWeight.bold, ), ), - prefixIcon: Padding( - padding: const EdgeInsets.only(left: 10, right: 5), - child: DropdownButtonHideUnderline( - child: DropdownButton( - value: selectedIndex, - items: List.generate(flagList.length, (index) { - return DropdownMenuItem( - value: index, - child: Row( - children: [ - Image.asset( - flagList[index]["image"]!, - width: 24, - height: 24, - ), - const SizedBox(width: 6), - Text( - flagList[index]["code"]!, - style: const TextStyle(fontSize: 14), - ), - ], - ), - ); - }), - onChanged: (value) { - setState(() { - selectedIndex = value!; - }); - }, - ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(5), + borderSide: const BorderSide( + color: Color.fromARGB(255, 14, 63, 102), + width: 2, ), ), ), ), + const SizedBox(height: 16), - Row( - children: [ - Checkbox( - value: keepSignedIn, - activeColor: const Color.fromARGB(255, 14, 63, 102), - onChanged: (bool? value) { - setState(() { - keepSignedIn = value ?? false; - }); - }, - ), - const Text("Keep me signed in"), - ], + InkWell( + onTap: () { + setState(() { + keepSignedIn=!keepSignedIn; + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Checkbox( + value: keepSignedIn, + activeColor: const Color.fromARGB(255, 14, 63, 102), + onChanged: (bool? value) { + setState(() { + keepSignedIn = value ?? false; + }); + }, + ), + const Text( + "Keep me signed in", + style: TextStyle(fontSize: 15), + ), + ], + ), ), - const SizedBox(height: 16), + SizedBox(height: height / 50), BlocConsumer( listener: (context, state) { if (state is AuthSuccess) { @@ -130,15 +155,16 @@ class _LoginState extends State { context, MaterialPageRoute( builder: (context) => - OTPverifaction(phoneNumber: phoneController.text), + OTPVerification(phoneNumber: phoneController.text), ), ); - } else if (state is AuthError) { + } + if (state is AuthError) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - duration: Duration(seconds: 2), + duration: const Duration(seconds: 2), backgroundColor: Colors.red, - content: Text(state.message ?? "An error occurred"), + content: Text(state.message), ), ); } @@ -152,26 +178,28 @@ class _LoginState extends State { SizedBox( width: double.infinity, height: 48, - child: LogInButton( + child: Button( + color: const Color.fromARGB(255, 30, 137, 221), text: "Get OTP", onPressed: () { context.read().sendOTP( - "${flagList[selectedIndex]["code"]}${phoneController.text}", + "+${_selectedCountry.phoneCode}${phoneController.text}", ); }, ), ), - const SizedBox(height: 32), + SizedBox(height: height / 50), Row( children: [ const Expanded( child: Divider(thickness: 1, color: Colors.grey), ), - const Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: Text( + Padding( + padding: EdgeInsets.symmetric(horizontal: context.screenWidth / 15), + child: const Text( "Or continue with", - style: TextStyle(fontWeight: FontWeight.bold), + style: TextStyle( + fontWeight: FontWeight.normal, fontSize: 17), ), ), const Expanded( @@ -179,7 +207,7 @@ class _LoginState extends State { ), ], ), - const SizedBox(height: 32), + SizedBox(height: height / 30), SizedBox( width: double.infinity, height: 48, @@ -187,7 +215,10 @@ class _LoginState extends State { icon: SvgPicture.asset(Assets.images.googleSvg.path), label: const Text( "Login with google", - style: TextStyle(color: Colors.black), + style: TextStyle( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 17), ), style: OutlinedButton.styleFrom( side: const BorderSide(color: Colors.grey), @@ -208,4 +239,41 @@ class _LoginState extends State { ), ); } + + void _openCountryPicker(BuildContext context) { + showDialog( + context: context, + builder: (context) => Theme( + data: Theme.of(context).copyWith( + primaryColor: const Color.fromARGB(255, 14, 63, 102), + ), + child: CountryPickerDialog( + title: const Text('Select Country'), + searchCursorColor: const Color.fromARGB(255, 14, 63, 102), + searchInputDecoration: const InputDecoration( + hintText: 'Search...', + prefixIcon: Icon(Icons.search), + ), + isSearchable: true, + onValuePicked: (Country country) { + setState(() { + _selectedCountry = country; + countryController.text = country.name; + }); + }, + itemBuilder: _buildDialogItem, + ), + ), + ); + } + + Widget _buildDialogItem(Country country) => Row( + children: [ + CountryPickerUtils.getDefaultFlagImage(country), + const SizedBox(width: 8), + Text("+${country.phoneCode}"), + const SizedBox(width: 8), + Flexible(child: Text(country.name)), + ], + ); } \ No newline at end of file diff --git a/lib/screens/auth/onboarding.dart b/lib/screens/auth/onboarding.dart index 0938762..b5d4694 100644 --- a/lib/screens/auth/onboarding.dart +++ b/lib/screens/auth/onboarding.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:lba/extension/screenSize.dart'; import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/res/colors.dart'; import 'package:lba/screens/auth/login.dart'; class OnboardingScreen extends StatefulWidget { @@ -12,6 +14,7 @@ class OnboardingScreen extends StatefulWidget { class _OnboardingScreenState extends State { int currentIndex = 0; + final PageController _pageController = PageController(); final List imageAssets = [ Assets.images.ounboarding1.path, @@ -25,63 +28,83 @@ class _OnboardingScreenState extends State { Assets.icons.slide3.path, ]; - void _next() { if (currentIndex < imageAssets.length - 1) { - setState(() { - currentIndex++; - }); + _pageController.nextPage(duration: Duration(milliseconds: 300), curve: Curves.easeInOut); } else { - Navigator.push(context, MaterialPageRoute(builder: (context) => Login(),)); + Navigator.push(context, MaterialPageRoute(builder: (context) => Login())); } } void _back() { if (currentIndex > 0) { - setState(() { - currentIndex--; - }); + _pageController.previousPage(duration: Duration(milliseconds: 300), curve: Curves.easeInOut); } } @override Widget build(BuildContext context) { - - var sizeScreen = MediaQuery.of(context).size; + final width = context.screenWidth; + final height = context.screenHeight; return Scaffold( body: Column( children: [ - Padding( - padding: const EdgeInsets.fromLTRB(50, 150, 50, 100), - child: SvgPicture.asset(imageAssets[currentIndex]), + Expanded( + child: PageView.builder( + controller: _pageController, + itemCount: imageAssets.length, + onPageChanged: (index) { + setState(() { + currentIndex = index; + }); + }, + itemBuilder: (context, index) { + return Padding( + padding: EdgeInsets.fromLTRB(width / 15, height / 40, width / 15, height / 30), + child: SvgPicture.asset( + imageAssets[index], + key: ValueKey(imageAssets[index]), + height: height / 2.5, + width: width, + ), + ); + }, + ), ), Padding( - padding: EdgeInsets.fromLTRB(sizeScreen.width/15, 10, sizeScreen.width/15, 5), + padding: EdgeInsets.fromLTRB(width / 15, 10, width / 15, 5), child: Row( children: [ Text( "Proxibuy Geofencing marketing", - style: TextStyle(fontWeight: FontWeight.w900), + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), textDirection: TextDirection.ltr, ), ], ), ), Padding( - padding: EdgeInsets.fromLTRB(sizeScreen.width/15, 10, sizeScreen.width/15, sizeScreen.height/40), + padding: EdgeInsets.fromLTRB(width / 15, 0, width / 15, width/30), child: Text( '"Join the app to discover exclusive discounts and special offers in specific areas around you for a smarter shopping experience!"', - style: TextStyle(fontWeight: FontWeight.w500,color: const Color.fromARGB(255, 88, 88, 88)), + style: TextStyle(fontWeight: FontWeight.w500, color: LightAppColors.hint, fontSize: 15), ), ), - const SizedBox(height: 50), + SizedBox(height: height / 30), Padding( - padding: EdgeInsets.fromLTRB(sizeScreen.width/15, 10, sizeScreen.width/15, sizeScreen.height/40), + padding: EdgeInsets.fromLTRB(width / 15, 0, width / 15, height / 10), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - SvgPicture.asset(slides[currentIndex]), + AnimatedSwitcher( + duration: Duration(milliseconds: 300), + transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child), + child: SvgPicture.asset( + slides[currentIndex], + key: ValueKey(slides[currentIndex]), + ), + ), Row( children: [ if (currentIndex > 0) @@ -92,7 +115,10 @@ class _OnboardingScreenState extends State { const SizedBox(width: 8), GestureDetector( onTap: _next, - child: SvgPicture.asset(Assets.icons.next.path), + child: SvgPicture.asset( + Assets.icons.next.path, + width: width / 5.5, + ), ), ], ) @@ -103,4 +129,10 @@ class _OnboardingScreenState extends State { ), ); } + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } } diff --git a/lib/screens/auth/otpVerifcation.dart b/lib/screens/auth/otpVerifcation.dart index 38632dd..a7fc5d7 100644 --- a/lib/screens/auth/otpVerifcation.dart +++ b/lib/screens/auth/otpVerifcation.dart @@ -1,54 +1,43 @@ -import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/screens/auth/cubit/auth_cubit.dart'; import 'package:lba/screens/auth/userInfo.dart'; -import 'package:lba/widgets/loginButton.dart'; +import 'package:lba/widgets/button.dart'; +import 'package:lba/widgets/remainingTime.dart'; -class OTPverifaction extends StatefulWidget { +class OTPVerification extends StatefulWidget { final String phoneNumber; - const OTPverifaction({super.key, required this.phoneNumber}); + const OTPVerification({super.key, required this.phoneNumber}); @override - State createState() => _LoginState(); + State createState() => _OTPVerificationState(); } -class _LoginState extends State { +class _OTPVerificationState extends State { final List _focusNodes = List.generate(5, (_) => FocusNode()); final List _controllers = List.generate(5, (_) => TextEditingController()); - - Timer? _timer; - int _remainingSeconds = 600; - bool _canResend = false; + late RemainingTime _otpTimer; @override void initState() { super.initState(); - _startTimer(); + _otpTimer = RemainingTime(); + _initializeTimer(); } - void _startTimer() { - _timer?.cancel(); - _remainingSeconds = 600; - _canResend = false; - _timer = Timer.periodic(const Duration(seconds: 1), (timer) { - if (_remainingSeconds == 0) { - setState(() { - _canResend = true; - timer.cancel(); - }); - } else { - setState(() { - _remainingSeconds--; - }); - } - }); + void _initializeTimer() { + final authCubit = context.read(); + _otpTimer.initializeFromExpiry(expiryTimeString: authCubit.timeDue); } - String _formatTime(int seconds) { - final minutes = (seconds ~/ 60).toString().padLeft(2, '0'); - final secs = (seconds % 60).toString().padLeft(2, '0'); - return "$minutes:$secs"; + void _resendOTP() { + if (_otpTimer.canResend.value) { + context.read().sendOTP(widget.phoneNumber); + _otpTimer.remainingSeconds.value = 60; + _otpTimer.startTimer(); + } } @override @@ -59,7 +48,7 @@ class _LoginState extends State { for (final controller in _controllers) { controller.dispose(); } - _timer?.cancel(); + _otpTimer.dispose(); super.dispose(); } @@ -68,7 +57,7 @@ class _LoginState extends State { mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: List.generate(5, (index) { return SizedBox( - width: 50, + width: 60, child: TextField( controller: _controllers[index], focusNode: _focusNodes[index], @@ -78,6 +67,7 @@ class _LoginState extends State { style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold), decoration: InputDecoration( counterText: '', + hintText: "0", enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: const BorderSide(color: Colors.grey), @@ -100,76 +90,108 @@ class _LoginState extends State { @override Widget build(BuildContext context) { + final width = MediaQuery.of(context).size.width; + final height = MediaQuery.of(context).size.height; + return Scaffold( body: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - IconButton( - icon: SvgPicture.asset(Assets.icons.back.path,), - onPressed: () => Navigator.pop(context), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - const SizedBox(height: 20), - Center(child: SvgPicture.asset(Assets.images.logo.path)), - const SizedBox(height: 24), - const Text("OTP Verification", style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold)), - const SizedBox(height: 8), - const Text("Enter the verification code we just sent to your device."), - const SizedBox(height: 25), - _buildOTPFields(), - const SizedBox(height: 70), - SizedBox( - width: double.infinity, - height: 48, - child: LogInButton(text: "Verify",onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => UserInfo(),)); - },) - ), - const SizedBox(height: 40), - Center( - child: Column( - children: [ - Row( - children: [ - const Expanded( - child: Divider(thickness: 1, color: Colors.grey), - ), - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.0), - child: Text("Resend OTP in ${_formatTime(_remainingSeconds)}",style: TextStyle(fontWeight: FontWeight.bold),), - ), - const Expanded( - child: Divider(thickness: 1, color: Colors.grey), - ), - ], - ), - const SizedBox(height: 8), - GestureDetector( - onTap: _canResend ? () => _startTimer() : null, - child: Text( - "Resend Code", - style: TextStyle( - fontSize: 16, - color: _canResend ? const Color.fromARGB(255, 0, 0, 0) : Colors.grey, - ), - ), - ), - ], - ), + IconButton( + icon: SvgPicture.asset(Assets.icons.back.path), + onPressed: () => Navigator.pop(context), ), ], ), - ), - ], + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Center( + child: Padding( + padding: const EdgeInsets.only(top: 0), + child: SvgPicture.asset(Assets.images.logo.path, height: height / 5.2), + ), + ), + SizedBox(height: height / 20), + const Text("OTP Verification", style: TextStyle(fontSize: 33, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Text("Enter the verification code we just sent to your device.", + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500), + ), + const SizedBox(height: 25), + _buildOTPFields(), + SizedBox(height: height / 7), + SizedBox( + width: double.infinity, + height: 48, + child: Button( + text: "Verify", + onPressed: () { + final otpCode = _controllers.map((c) => c.text).join(); + if (otpCode.length == 5) { + context.read().verifyOTP(otpCode); + Navigator.push(context, MaterialPageRoute(builder: (context) => UserInfo())); + } + }, + color: const Color.fromARGB(255, 30, 137, 221), + ), + ), + SizedBox(height: height / 25), + Center( + child: Column( + children: [ + Row( + children: [ + const Expanded( + child: Divider(thickness: 1, color: Colors.grey), + ), + ValueListenableBuilder( + valueListenable: _otpTimer.remainingSeconds, + builder: (context, seconds, _) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + "Resend OTP in ${_otpTimer.formatTime()}", + style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 18), + ), + ); + }, + ), + const Expanded( + child: Divider(thickness: 1, color: Colors.grey), + ), + ], + ), + const SizedBox(height: 8), + ValueListenableBuilder( + valueListenable: _otpTimer.canResend, + builder: (context, canResend, _) { + return GestureDetector( + onTap: canResend ? _resendOTP : null, + child: Text( + "Resend OTP", + style: TextStyle( + fontSize: 16, + color: canResend ? const Color.fromARGB(255, 0, 0, 0) : Colors.grey, + ), + ), + ); + }, + ), + ], + ), + ), + ], + ), + ), + ], + ), ), ), ); diff --git a/lib/screens/auth/userInfo.dart b/lib/screens/auth/userInfo.dart index 30fd8d5..4c296c9 100644 --- a/lib/screens/auth/userInfo.dart +++ b/lib/screens/auth/userInfo.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:lba/extension/screenSize.dart'; import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/res/colors.dart'; import 'package:lba/screens/nearby/nearby.dart'; -import 'package:lba/widgets/interestsUserInfo.dart'; -import 'package:lba/widgets/loginButton.dart'; +import 'package:lba/widgets/button.dart'; +import 'package:lba/widgets/datePicker.dart'; class UserInfo extends StatefulWidget { const UserInfo({super.key}); @@ -18,359 +20,251 @@ class _UserInfoState extends State { @override Widget build(BuildContext context) { + final width = context.screenWidth; + final height = context.screenHeight; return Scaffold( body: SafeArea( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - IconButton( - icon: SvgPicture.asset(Assets.icons.back.path), - onPressed: () => Navigator.pop(context), - ), - ], - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 24), - child: Center(child: SvgPicture.asset(Assets.images.logo.path)), - ), - const SizedBox(height: 24), - const Padding( - padding: EdgeInsets.fromLTRB(25, 0, 25, 20), - child: Text( - "what do you like to be called in this application?", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 117, 117, 117), - ), - ), - ), - const Padding( - padding: EdgeInsets.fromLTRB(25, 0, 25, 0), - child: TextField( - decoration: InputDecoration( - counterText: '', - hintText: "Enter here...", - hintStyle: TextStyle(fontWeight: FontWeight.w500, color: Colors.grey), - filled: true, - fillColor: Color.fromARGB(133, 250, 250, 250), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: Color.fromARGB(255, 14, 63, 102)), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: Colors.grey, width: 2), - ), - ), - ), - ), - const SizedBox(height: 24), - const Padding( - padding: EdgeInsets.fromLTRB(25, 0, 25, 10), - child: Text( - "Gender", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 117, 117, 117), - ), - ), - ), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 25), - child: Wrap( - spacing: 10, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( children: [ - ChoiceChip( - label: const Text("Male"), - selected: selectedGender == "Male", - onSelected: (_) => setState(() => selectedGender = "Male"), - ), - ChoiceChip( - label: const Text("Female"), - selected: selectedGender == "Female", - onSelected: (_) => setState(() => selectedGender = "Female"), - ), - ChoiceChip( - label: const Text("Prefer not to say"), - selected: selectedGender == "Prefer not to say", - onSelected: (_) => setState(() => selectedGender = "Prefer not to say"), + IconButton( + icon: SvgPicture.asset(Assets.icons.back.path), + onPressed: () => Navigator.pop(context), ), ], ), - ), - const SizedBox(height: 24), - const Padding( - padding: EdgeInsets.fromLTRB(25, 0, 25, 15), - child: Text( - "Birth Date?", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 117, 117, 117), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 24), + child: Center( + child: Padding( + padding: const EdgeInsets.only(top: 0), + child: SvgPicture.asset( + Assets.images.userinfo.path, + height: height /2.9, + ), + ), ), ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(25, 0, 25, 0), - child: TextField( - readOnly: true, - decoration: InputDecoration( - hintText: selectedDate != null - ? "${selectedDate!.month}/${selectedDate!.day}/${selectedDate!.year}" - : "mm/dd/yyyy", - hintStyle: const TextStyle(fontWeight: FontWeight.w500, color: Colors.grey), - filled: true, - fillColor: const Color.fromARGB(133, 250, 250, 250), - enabledBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: Color.fromARGB(255, 14, 63, 102)), + SizedBox(height: height/20), + const Padding( + padding: EdgeInsets.fromLTRB(25, 0, 25, 20), + child: Text( + "what do you like to be called in this application?", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Color.fromARGB(255, 117, 117, 117), ), - focusedBorder: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(12)), - borderSide: BorderSide(color: Colors.grey, width: 2), + ), + ), + const Padding( + padding: EdgeInsets.fromLTRB(25, 0, 25, 0), + child: TextField( + decoration: InputDecoration( + counterText: '', + hintText: "Enter here...", + hintStyle: TextStyle( + fontWeight: FontWeight.normal, color: Colors.grey), + filled: true, + fillColor: Color.fromARGB(255, 250, 250, 250), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: + BorderSide(color: Color.fromARGB(255, 14, 63, 102)), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(12)), + borderSide: BorderSide(color: Colors.grey, width: 2), + ), ), - suffixIcon: IconButton( - icon: SvgPicture.asset(Assets.icons.clander.path), - onPressed: () async { - final result = await showModalBottomSheet( - context: context, - builder: (_) => const _DatePickerSheet(), + ), + ), + const SizedBox(height: 24), + const Padding( + padding: EdgeInsets.fromLTRB(25, 0, 25, 10), + child: Text( + "Gender", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Color.fromARGB(255, 117, 117, 117), + ), + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 25), + child: Wrap( + spacing: 5, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Prefer not to say", + style: TextStyle( + color: Color.fromARGB(255, 112, 112, 110))), + Radio( + value: "Prefer not to say", + groupValue: selectedGender, + activeColor: Colors.blue, + onChanged: (value) { + setState(() { + selectedGender = value!; + }); + }, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Male", + style: TextStyle( + color: Color.fromARGB(255, 112, 112, 110))), + Radio( + value: "Male", + groupValue: selectedGender, + activeColor: Colors.blue, + onChanged: (value) { + setState(() { + selectedGender = value!; + }); + }, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Text("Female", + style: TextStyle( + color: Color.fromARGB(255, 112, 112, 110))), + Radio( + value: "Female", + groupValue: selectedGender, + activeColor: Colors.blue, + onChanged: (value) { + setState(() { + selectedGender = value!; + }); + }, + ), + ], + ), + ], + ), + ), + SizedBox(height: height/12), + // const Padding( + // padding: EdgeInsets.fromLTRB(25, 0, 25, 15), + // child: Text( + // "Birth Date?", + // style: TextStyle( + // fontWeight: FontWeight.bold, + // color: Color.fromARGB(255, 117, 117, 117), + // ), + // ), + // ), + // Padding( + // padding: const EdgeInsets.fromLTRB(25, 0, 25, 0), + // child: TextField( + // readOnly: true, + // decoration: InputDecoration( + // hintText: selectedDate != null + // ? "${selectedDate!.month}/${selectedDate!.day}/${selectedDate!.year}" + // : "mm/dd/yyyy", + // hintStyle: const TextStyle( + // fontWeight: FontWeight.w500, color: Colors.grey), + // filled: true, + // fillColor: const Color.fromARGB(133, 250, 250, 250), + // enabledBorder: const OutlineInputBorder( + // borderRadius: BorderRadius.all(Radius.circular(12)), + // borderSide: + // BorderSide(color: Color.fromARGB(255, 14, 63, 102)), + // ), + // focusedBorder: const OutlineInputBorder( + // borderRadius: BorderRadius.all(Radius.circular(12)), + // borderSide: BorderSide(color: Colors.grey, width: 2), + // ), + // suffixIcon: IconButton( + // icon: SvgPicture.asset(Assets.icons.clander.path), + // onPressed: () async { + // final result = await showModalBottomSheet( + // context: context, + // builder: (_) => const DatePickerCanvas(), + // ); + // if (result != null) { + // setState(() { + // selectedDate = result; + // }); + // } + // }, + // ), + // ), + // ), + // ), + // const Padding( + // padding: EdgeInsets.fromLTRB(25, 25, 25, 10), + // child: Text( + // "Interests / Preferences", + // style: TextStyle( + // fontWeight: FontWeight.bold, + // color: Color.fromARGB(255, 117, 117, 117), + // ), + // ), + // ), + // Interests( + // icon: Assets.icons.shoppingCart.path, + // title: "Shopping & Retail", + // options: [ + // "Fashion & Clothing", + // "Electronics & Gadgets", + // "Shoes & Accessories" + // ], + // ), + // const SizedBox(height: 30), + Center( + child: SizedBox( + width: width*0.9, + child: Button( + text: "Submit", + onPressed: () { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => const Nearby()), + (route) => false, ); - if (result != null) { - setState(() { - selectedDate = result; - }); - } - }, + }, color: const Color.fromARGB(255, 30, 137, 221), ), ), ), - ), - Padding( - padding: EdgeInsets.fromLTRB(25, 25, 25, 10), - child: Text( - "Interests / Preferences", - style: TextStyle( - fontWeight: FontWeight.bold, - color: Color.fromARGB(255, 117, 117, 117), + GestureDetector( + onTap: () { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (context) => const Nearby()), + (route) => false, + ); + }, + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Center( + child: InkWell( + child: Text( + "Skip", + style: TextStyle( + color: LightAppColors.primary, + fontWeight: FontWeight.bold, + fontSize: 16), + ), + ), + ), ), - ), - ), - Interests(icon: Assets.icons.shoppingCart.path,title: "Shopping & Retail",options: ["Fashion & Clothing","Electronics & Gadgets","Shoes & Accessories"],), - SizedBox(height: 30,), - Center(child: SizedBox( - width: 300, - child: LogInButton(text: "Submit", onPressed:() { - Navigator.push(context, MaterialPageRoute(builder: - (context) => Nearby(), - )); - },))), - ], + ) + ], + ), ), ), ); } -} - - -class _DatePickerSheet extends StatefulWidget { - const _DatePickerSheet(); - - @override - State<_DatePickerSheet> createState() => _DatePickerSheetState(); -} - -class _DatePickerSheetState extends State<_DatePickerSheet> { - late DateTime now; - late int selectedYear; - late int selectedMonth; - int? selectedDay; - - late final List years; - - final List months = [ - 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', - 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' - ]; - - @override - void initState() { - super.initState(); - now = DateTime.now(); - selectedYear = now.year; - selectedMonth = now.month; - years = List.generate(100, (index) => now.year - index); - } - - List getCalendarDays() { - final firstDayOfMonth = DateTime(selectedYear, selectedMonth, 1); - final lastDayOfMonth = DateTime(selectedYear, selectedMonth + 1, 0); - final daysInMonth = lastDayOfMonth.day; - - List days = []; - - // Add empty days for alignment - for (int i = 0; i < firstDayOfMonth.weekday - 1; i++) { - days.add(null); - } - - // Add actual days - for (int day = 1; day <= daysInMonth; day++) { - days.add(DateTime(selectedYear, selectedMonth, day)); - } - - return days; - } - - @override - Widget build(BuildContext context) { - final days = getCalendarDays(); - final weekdays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; - - return Container( - padding: const EdgeInsets.all(16), - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.7, - ), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - // Month and year selection - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - // Month selection - SizedBox( - width: 200, - child: DropdownButton( - value: selectedMonth, - isExpanded: true, - items: List.generate(12, (index) { - return DropdownMenuItem( - value: index + 1, - child: Text(months[index]), - ); - }), - onChanged: (value) { - setState(() { - selectedMonth = value!; - selectedDay = null; - }); - }, - ), - ), - - // Year selection - SizedBox( - width: 120, - child: DropdownButton( - value: selectedYear, - isExpanded: true, - items: years.map((year) { - return DropdownMenuItem( - value: year, - child: Text(year.toString()), - ); - }).toList(), - onChanged: (value) { - setState(() { - selectedYear = value!; - selectedDay = null; - }); - }, - ), - ), - ], - ), - - const SizedBox(height: 16), - - // Weekday headers - Row( - children: weekdays.map((day) { - return Expanded( - child: Center( - child: Text( - day, - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.grey, - ), - ), - ), - ); - }).toList(), - ), - - const SizedBox(height: 8), - - // Calendar grid - Expanded( - child: GridView.count( - crossAxisCount: 7, - shrinkWrap: true, - children: days.map((date) { - return date == null - ? const SizedBox.shrink() - : InkWell( - onTap: () => setState(() => selectedDay = date.day), - child: Container( - margin: const EdgeInsets.all(4), - decoration: BoxDecoration( - color: selectedDay == date.day - ? Colors.blue - : Colors.transparent, - shape: BoxShape.circle, - ), - child: Center( - child: Text( - date.day.toString(), - style: TextStyle( - color: selectedDay == date.day - ? Colors.white - : Colors.black, - fontWeight: selectedDay == date.day - ? FontWeight.bold - : FontWeight.normal, - ), - ), - ), - ), - ); - }).toList(), - ), - ), - - const SizedBox(height: 16), - - // Confirm button - ElevatedButton( - style: ElevatedButton.styleFrom( - minimumSize: const Size(double.infinity, 50), - backgroundColor: Colors.blue, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - ), - ), - onPressed: () { - if (selectedDay != null) { - Navigator.pop( - context, - DateTime(selectedYear, selectedMonth, selectedDay!), - ); - } - }, - child: const Text( - "Confirm", - style: TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ); - } } \ No newline at end of file diff --git a/lib/screens/nearby/bestNearby.dart b/lib/screens/nearby/bestNearby.dart new file mode 100644 index 0000000..fe3681b --- /dev/null +++ b/lib/screens/nearby/bestNearby.dart @@ -0,0 +1,186 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:lba/gen/assets.gen.dart'; + +class Bestnearby extends StatelessWidget { + const Bestnearby({super.key,}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 2, + shadowColor: Colors.grey, + automaticallyImplyLeading: false, + leading: InkWell( + onTap: () => Navigator.pop(context), + child: SvgPicture.asset( + Assets.icons.back.path, + fit: BoxFit.scaleDown, + ), + ), + title: const Text("Top Deals & Stores", style: TextStyle(fontSize: 18)), + ), + body: SingleChildScrollView( + child: Column( + children: [ + NearbyItems(detailsType: 0), + NearbyItems(detailsType: 1), + NearbyItems(detailsType: 2) + ], + ), + ), + ); + } +} + +class NearbyItems extends StatelessWidget { + const NearbyItems({super.key, required this.detailsType}); + + final int detailsType; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Text( + detailsType==0?"Highest Discount Areas":detailsType==1?"Highest Discount Areas":detailsType==2?"Best Deal, Don't Miss":"", + style: TextStyle(fontSize: 15), + ), + const SizedBox(width: 8), + const Expanded( + child: Divider(color: Colors.grey, thickness: 2), + ), + ], + ), + ), + SizedBox( + height: detailsType==0?180:detailsType==1?240:detailsType==2?240:200, + child: ListView.builder( + itemCount: 5, + scrollDirection: Axis.horizontal, + itemBuilder: (context, index) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 7), + Container( + width: 120, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Colors.grey[200], + ), + child: Image.asset( + Assets.images.topDealsAndStores.path, + ), + ), + SizedBox(height: 5), + Text( + "Dubai Outlet Mall", + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox(height: 5), + detailsType == 0 + ? Row( + children: [ + SvgPicture.asset(Assets.icons.routing.path), + SizedBox(width: 4), + Text( + "1.2 km away", + style: TextStyle(fontSize: 12), + ), + ], + ) + : detailsType == 1 + ? Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 2), + Row( + children: [ + SvgPicture.asset(Assets.icons.category2.path), + SizedBox(width: 4), + Text("Stationery store"), + ], + ), + SizedBox(height: 5), + Row( + children: [ + SvgPicture.asset( + Assets.icons.icRoundLocalOffer.path, + ), + SizedBox(width: 4), + Text( + "22 - 35% off", + style: TextStyle(color: Colors.red), + ), + ], + ), + SizedBox(height: 5), + Row( + children: [ + SvgPicture.asset(Assets.icons.routing.path), + SizedBox(width: 4), + Text("3.6 km away"), + ], + ), + ], + ) + : detailsType == 2 + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 2), + Row( + children: [ + SvgPicture.asset(Assets.icons.shop2.path), + SizedBox(width: 4), + Text("Jumbo Electronics "), + ], + ), + SizedBox(height: 5), + Row( + children: [ + SvgPicture.asset( + Assets.icons.icRoundLocalOffer.path, + ), + SizedBox(width: 4), + Text( + "22 - 35% off", + style: TextStyle(color: Colors.red), + ), + ], + ), + SizedBox(height: 5), + Row( + children: [ + SvgPicture.asset(Assets.icons.routing.path), + SizedBox(width: 4), + Text("3.6 km away"), + ], + ), + ], + ) + : SizedBox(), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} diff --git a/lib/screens/nearby/listScreen.dart b/lib/screens/nearby/listScreen.dart new file mode 100644 index 0000000..f107ab3 --- /dev/null +++ b/lib/screens/nearby/listScreen.dart @@ -0,0 +1,263 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:lba/extension/screenSize.dart'; +import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/widgets/orderType.dart'; +import 'package:lba/widgets/remainingTime.dart'; + +class ListScreen extends StatefulWidget { + final bool delivery; + final bool pickup; + int initialTimerStatus; + final String expiryTimeString; + + ListScreen({ + super.key, + required this.delivery, + required this.pickup, + this.initialTimerStatus=3, + required this.expiryTimeString, + }); + + @override + State createState() => _ListScreenState(); +} + +class _ListScreenState extends State { + late RemainingTime _timer; + late ValueNotifier _timerStatus; + + @override + void initState() { + super.initState(); + _timer = RemainingTime(); + _timerStatus = ValueNotifier(widget.initialTimerStatus); + _timer.initializeFromExpiry(expiryTimeString: widget.expiryTimeString); + + _timer.remainingSeconds.addListener(() { + if (_timer.remainingSeconds.value <= 0) { + _timerStatus.value = 3; + } else if (_timer.remainingSeconds.value < 18000) { + _timerStatus.value = 1; + } else if (_timer.remainingSeconds.value > 18000) { + _timerStatus.value = 2; + } + }); + } + + @override + void dispose() { + _timer.dispose(); + _timerStatus.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final width = context.screenWidth; + final height = context.screenHeight; + return + Padding( + padding: const EdgeInsets.only(bottom: 16), + child: Stack( + children: [ + Container( + margin: const EdgeInsets.only(top: 60), + padding: const EdgeInsets.only(top: 80), + decoration: BoxDecoration( + color: const Color.fromARGB(255, 242, 242, 241), + borderRadius: BorderRadius.circular(10), + ), + child: Padding( + padding: EdgeInsets.fromLTRB(12, 0, width/50, 15), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Row( + children: [ + SvgPicture.asset(Assets.icons.phCheese.path), + const SizedBox(width: 5), + const Text( + "Amul Cheese Slices", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + SvgPicture.asset( + Assets.icons.location.path, + color: const Color.fromARGB(255, 157, 157, 155), + width: 14, + ), + const SizedBox(width: 5), + const Text("Sharjah (750m away)",style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500 + ),), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + SvgPicture.asset( + Assets.icons.coin.path, + width: 14, + ), + const SizedBox(width: 5), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: const [ + Text( + "18.15 AED", + style: TextStyle( + color: Color.fromARGB(255, 157, 157, 155), + fontSize: 10, + decoration: TextDecoration.lineThrough, + ), + ), + SizedBox(height: 0), + Row( + children: [ + Text("15.84 AED",style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500 + ),), + SizedBox(width: 4), + Text( + "(13% off)", + style: TextStyle( + color: Color.fromARGB(255, 76, 175, 80), + fontSize: 10, + fontWeight: FontWeight.w500 + ), + ), + ], + ), + ], + ), + ], + ), + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(0, 35, widget.delivery == false || widget.pickup == false ? width/5 : width/60, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SvgPicture.asset( + Assets.icons.star.path, + width: 14, + ), + const SizedBox(width: 2), + const Text( + "4.8", + style: TextStyle( + color: Color.fromARGB(255, 112, 112, 110), + ), + ), + ], + ), + const SizedBox(height: 10), + Row( + children: [ + if (widget.delivery) + OrderType( + icon: Assets.icons.cardPos.path, + typename: "Delivery", + ), + if (widget.delivery) const SizedBox(width: 5), + if (widget.pickup) + OrderType( + icon: Assets.icons.shoppingCart.path, + typename: "Pickup", + ), + ], + ), + ], + ), + ), + ], + ), + ), + ), + ClipRRect( + borderRadius: const BorderRadius.only( + topRight: Radius.circular(10), + topLeft: Radius.circular(10), + ), + child: Image.asset( + Assets.images.media.path, + width: double.infinity, + height: 130, + fit: BoxFit.cover, + ), + ), + Positioned( + top: 10, + left: 10, + child: ValueListenableBuilder( + valueListenable: _timerStatus, + builder: (context, timerStatus, child) { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6), + height: 20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(6), + color: timerStatus == 1 + ? const Color.fromARGB(255, 255, 193, 7) + : timerStatus == 2 + ? const Color.fromARGB(255, 76, 175, 80) + : timerStatus == 3 + ? const Color.fromARGB(255, 244, 67, 54) + : const Color.fromARGB(255, 244, 67, 54), + ), + child: Row( + children: [ + SvgPicture.asset( + timerStatus == 1 + ? Assets.icons.timer.path + : timerStatus == 2 + ? Assets.icons.timerStart.path + : Assets.icons.timerPause.path, + width: 12, + height: 12, + ), + const SizedBox(width: 5), + ValueListenableBuilder( + valueListenable: _timer.remainingSeconds, + builder: (context, seconds, child) { + return Text( + timerStatus==3? "Unavailable":"${_timer.formatTime()} left", + style: const TextStyle( + color: Colors.white, + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ); + }, + ), + ], + ), + ); + }, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/screens/nearby/map.dart b/lib/screens/nearby/map.dart new file mode 100644 index 0000000..2ab4685 --- /dev/null +++ b/lib/screens/nearby/map.dart @@ -0,0 +1,470 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_map/flutter_map.dart'; +import 'package:latlong2/latlong.dart'; +import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/res/colors.dart'; +import 'package:lba/screens/nearby/bestNearby.dart'; +import 'package:location/location.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class MarkerData { + final LatLng point; + final String type; + final String asset; + final String name; + final String place; + final String distance; + final String description; + + MarkerData({ + required this.point, + required this.type, + required this.asset, + required this.name, + required this.place, + required this.distance, + required this.description, + }); +} + +class CustomMap extends StatefulWidget { + const CustomMap({super.key}); + + @override + _CustomMapState createState() => _CustomMapState(); +} + +class _CustomMapState extends State + with SingleTickerProviderStateMixin { + Location location = Location(); + late bool _serviceEnabled; + late PermissionStatus _permissionGranted; + late LocationData _locationData; + late LatLng _currentCenter; + final LatLng _defaultLocation = const LatLng(25.1972, 55.2744); // Burj Khalifa + final MapController _mapController = MapController(); + late AnimationController _animationController; + late Animation _centerAnimation; + late Animation _rotationAnimation; + + final List _manualMarkers = [ + MarkerData( + point: const LatLng(25.2399, 55.2744), + type: 'air', + asset: Assets.icons.materialSymbolsLocationAir.path, + name: "McDonald's", + place: "Mall of the Emirates", + distance: "(3.2 Km away)", + description: "Momos, Fast Food, Chinese", + ), + MarkerData( + point: const LatLng(25.1950, 55.2399), + type: 'ent', + asset: Assets.icons.materialSymbolsLocationEnt.path, + name: "Chattels & More", + place: "Mall of the Emirates ", + distance: "(1 Km away)", + description: "Furniture and Home Store", + ), + MarkerData( + point: const LatLng(25.1990, 55.2720), + type: 'health', + asset: Assets.icons.materialSymbolsLocationHealth.path, + name: "McDonald's", + place: "Mall of the Emirates", + distance: "(3.2 Km away)", + description: "Momos, Fast Food, Chinese", + ), + ]; + + List _markers = []; + int? _selectedMarkerIndex; + + @override + void initState() { + super.initState(); + _currentCenter = _defaultLocation; + _initializeMarkers(); + _checkLocationServices(); + + // Initialize AnimationController + _animationController = AnimationController( + duration: const Duration(milliseconds: 500), + vsync: this, + ); + } + + void _initializeMarkers() { + _markers = + _manualMarkers.asMap().entries.map((entry) { + int index = entry.key; + MarkerData markerData = entry.value; + bool isSelected = _selectedMarkerIndex == index; + return Marker( + width: 60.0, + height: 60.0, + point: markerData.point, + child: GestureDetector( + onTap: () { + setState(() { + _selectedMarkerIndex = index; + }); + }, + child: Container( + decoration: + isSelected + ? BoxDecoration( + shape: BoxShape.circle, + border: Border.all(color: Colors.red, width: 3.0), + ) + : null, + child: SvgPicture.asset( + markerData.asset, + width: 40.0, + height: 40.0, + ), + ), + ), + ); + }).toList(); + } + + Future _checkLocationServices() async { + _serviceEnabled = await location.serviceEnabled(); + if (!_serviceEnabled) { + _serviceEnabled = await location.requestService(); + if (!_serviceEnabled) { + setState(() { + _currentCenter = _defaultLocation; + }); + _mapController.move(_currentCenter, 13.0); + return; + } + } + + _permissionGranted = await location.hasPermission(); + if (_permissionGranted == PermissionStatus.denied) { + _permissionGranted = await location.requestPermission(); + if (_permissionGranted != PermissionStatus.granted) { + setState(() { + _currentCenter = _defaultLocation; + }); + _mapController.move(_currentCenter, 13.0); + return; + } + } + + try { + _locationData = await location.getLocation(); + setState(() { + _currentCenter = LatLng( + _locationData.latitude!, + _locationData.longitude!, + ); + }); + _mapController.move(_currentCenter, 13.0); + } catch (e) { + print('Error getting location: $e'); + setState(() { + _currentCenter = _defaultLocation; + }); + _mapController.move(_currentCenter, 13.0); + } + } + + void _recenterMap() { + final currentCenter = _mapController.camera.center; + final currentRotation = _mapController.camera.rotation; + + _centerAnimation = LatLngTween( + begin: currentCenter, + end: _currentCenter, + ).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + + _rotationAnimation = Tween( + begin: currentRotation, + end: 0.0, + ).animate( + CurvedAnimation(parent: _animationController, curve: Curves.easeInOut), + ); + + _animationController.addListener(() { + _mapController.move(_centerAnimation.value, 13.0); + _mapController.rotate(_rotationAnimation.value); + }); + + _animationController.reset(); + _animationController.forward(); + } + + Widget _buildMarkerDetails() { + return Positioned( + bottom: 10, + left: 0, + right: 0, + child: Column( + children: [ + // Recenter Button + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: ElevatedButton( + onPressed: _recenterMap, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.white, + foregroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + child: Row( + children: [ + SvgPicture.asset(Assets.icons.recenter.path), + SizedBox(width: 5,), + Text('Re-Center',style: TextStyle(fontSize: 15),), + ], + ), + ), + ), + ElevatedButton( + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => Bestnearby(),)); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + ), + ), + child: Text("Best Nearby",style: TextStyle(fontSize: 15),), + ), + ], + ), + ), + // Marker Details + if (_selectedMarkerIndex == null) + Container( + height: 190, + width: double.infinity, + padding: const EdgeInsets.only(bottom: 25,left: 8), + child: Center( + child: ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: _manualMarkers.length, + itemBuilder: (context, index) { + final marker = _manualMarkers[index]; + return Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + padding: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: LightAppColors.nearbyPopup, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(11), + ), + child: Image.asset(Assets.images.image.path), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + marker.name, + style: TextStyle(color: Colors.black), + ), + SizedBox(height: 5), + Text( + "${marker.place} ${marker.distance}", + style: TextStyle( + color: LightAppColors.nearbyPopuphint, + ), + ), + SizedBox(height: 5), + Text( + marker.description, + style: TextStyle( + color: LightAppColors.nearbyPopuphint, + fontSize: 10, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset( + Assets.icons.mDSPublicTWTag.path, + ), + SizedBox(width: 10), + Text( + "(15%) 43 - 36.55 AED ", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ], + ), + ), + ), + ); + }, + ), + ), + ) + else + Container( + margin: const EdgeInsets.symmetric(horizontal: 5), + padding: const EdgeInsets.all(8.0).copyWith(bottom: 15), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: LightAppColors.nearbyPopup, + border: Border.all(color: Colors.white, width: 2.0), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(11), + ), + child: Image.asset(Assets.images.image.path), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + _manualMarkers[_selectedMarkerIndex!].name, + style: TextStyle(color: Colors.black), + ), + SizedBox(height: 5), + Text( + "${_manualMarkers[_selectedMarkerIndex!].place} ${_manualMarkers[_selectedMarkerIndex!].distance}", + style: TextStyle( + color: LightAppColors.nearbyPopuphint, + ), + ), + SizedBox(height: 5), + Text( + _manualMarkers[_selectedMarkerIndex!] + .description, + style: TextStyle( + color: LightAppColors.nearbyPopuphint, + fontSize: 10, + ), + ), + ], + ), + ), + ], + ), + SizedBox(height: 10), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + SvgPicture.asset(Assets.icons.mDSPublicTWTag.path), + SizedBox(width: 10), + Text( + "(15%) 43 - 36.55 AED ", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: OrientationBuilder( + builder: (context, orientation) { + if (orientation == Orientation.portrait) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _recenterMap(); + }); + } + return Stack( + children: [ + FlutterMap( + mapController: _mapController, + options: MapOptions( + initialCenter: _defaultLocation, + initialZoom: 13.0, + ), + children: [ + TileLayer( + urlTemplate: + 'https://tile.openstreetmap.org/{z}/{x}/{y}.png', + userAgentPackageName: 'com.example.app', + ), + MarkerLayer(markers: _markers), + ], + ), + _buildMarkerDetails(), + ], + ); + }, + ), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } +} + +// Helper class for animating LatLng +class LatLngTween extends Tween { + LatLngTween({required LatLng begin, required LatLng end}) + : super(begin: begin, end: end); + + @override + LatLng lerp(double t) { + return LatLng( + begin!.latitude + (end!.latitude - begin!.latitude) * t, + begin!.longitude + (end!.longitude - begin!.longitude) * t, + ); + } +} diff --git a/lib/screens/nearby/nearby.dart b/lib/screens/nearby/nearby.dart index e83c20c..a4f2f0e 100644 --- a/lib/screens/nearby/nearby.dart +++ b/lib/screens/nearby/nearby.dart @@ -1,6 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:lba/gen/assets.gen.dart'; +import 'package:lba/screens/nearby/listScreen.dart'; +import 'package:lba/screens/nearby/map.dart'; +import 'package:lba/widgets/customBottomSheet.dart'; +import 'package:lba/widgets/gpsPopup.dart'; class Nearby extends StatefulWidget { const Nearby({super.key}); @@ -10,10 +14,29 @@ class Nearby extends StatefulWidget { } class _NearbyState extends State { - int selectedIndex = 0; + String selectedOption = "Relevance"; + final List options = ["Relevance", "Newest", "Oldest", "Popularity"]; + int selectedIndex = 1; + List selectedOptions = []; + + @override + void dispose() { + super.dispose(); + } + + @override + void initState() { + super.initState(); + Future.delayed(Duration.zero, () { + if (mounted) { + showGPSDialog(context); + } + }); + } @override Widget build(BuildContext context) { + return Scaffold( appBar: AppBar( toolbarHeight: 70, @@ -46,8 +69,9 @@ class _NearbyState extends State { ), filled: true, fillColor: const Color.fromARGB(255, 248, 248, 248), - contentPadding: - const EdgeInsets.symmetric(horizontal: 16.0), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16.0, + ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(12.0), borderSide: const BorderSide( @@ -67,38 +91,21 @@ class _NearbyState extends State { ), ), const SizedBox(width: 10), - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: const Color.fromARGB(255, 14, 63, 102), - width: 1.5, - ), - ), - child: IconButton( - icon: SvgPicture.asset(Assets.icons.sort.path), - onPressed: () {}, - padding: const EdgeInsets.all(8), - ), + _buildIconButton( + icon: Assets.icons.sort.path, + onPressed: () { + CustomBottomSheet.show(context, [ + "Food & Dining", + "Entertainment & Leisure", + "Health & Fitness", + "Travel & Transportation", + ]); + }, ), const SizedBox(width: 8), - Container( - width: 40, - height: 40, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: const Color.fromARGB(255, 14, 63, 102), - width: 1.5, - ), - ), - child: IconButton( - icon: SvgPicture.asset(Assets.icons.location.path), - onPressed: () {}, - padding: const EdgeInsets.all(8), - ), + _buildIconButton( + icon: Assets.icons.location.path, + onPressed: () {}, ), ], ), @@ -107,54 +114,108 @@ class _NearbyState extends State { ), backgroundColor: Colors.white, ), - body: Center( - child: Column( + body: + Column( + children: [ + _buildToggleButtons(), + Container( + width: 350, + height: 3, + color: const Color.fromARGB(255, 14, 63, 102), + ), + Expanded( + child: selectedIndex == 1 + ? _buildListContent() + : CustomMap(), + ), + // ElevatedButton( + // onPressed: () => showGPSDialog(context), + // child: Text("Test GPS Dialog"), + // ) + ], + ), + ); + } + + Widget _buildIconButton({ + required String icon, + required VoidCallback onPressed, + }) { + return Container( + width: 40, + height: 40, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + border: Border.all( + color: const Color.fromARGB(255, 14, 63, 102), + width: 1.5, + ), + ), + child: IconButton( + icon: SvgPicture.asset(icon), + onPressed: onPressed, + padding: const EdgeInsets.all(8), + ), + ); + } + + Widget _buildToggleButtons() { + return Padding( + padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), + child: SizedBox( + width: 350, + height: 60, + child: Stack( + clipBehavior: Clip.none, children: [ - Padding( - padding: const EdgeInsets.fromLTRB(0, 20, 0, 0), - child: SizedBox( - width: 340, - height: 60, - child: Stack( - clipBehavior: Clip.none, - children: [ - Positioned( - left: selectedIndex==0?10:0, - child: buildToggleButton( - text: 'list', - isSelected: selectedIndex == 1, - onTap: () => setState(() => selectedIndex = 1), - borderRadius: BorderRadius.only( - topLeft: const Radius.circular(12), - bottomLeft: const Radius.circular(12), - topRight: selectedIndex == 1 - ? const Radius.circular(12) - : Radius.zero, - bottomRight: selectedIndex == 1 - ? const Radius.circular(12) - : Radius.zero, - ), - ), - ), - Positioned( - right: selectedIndex==1?1:0, - child: buildToggleButton( - text: 'map', - isSelected: selectedIndex == 0, - onTap: () => setState(() => selectedIndex = 0), - borderRadius: BorderRadius.only( - topRight: const Radius.circular(12), - bottomRight: const Radius.circular(12), - topLeft: selectedIndex == 0 - ? const Radius.circular(12) - : Radius.zero, - bottomLeft: selectedIndex == 0 - ? const Radius.circular(12) - : Radius.zero, - ), - ), - ), - ], + Positioned( + left: 50, + right: 50, + child: Container( + height: 50, + width: 170, + color: const Color.fromARGB(255, 234, 234, 233), + ), + ), + Positioned( + left: 0, + child: _buildToggleButton( + color: selectedIndex == 1 + ? const Color.fromARGB(255, 10, 69, 117) + : const Color.fromARGB(100, 177, 177, 177), + icon: Assets.icons.elementEqual.path, + iconColor: selectedIndex == 1 + ? const Color.fromARGB(255, 234, 245, 254) + : const Color.fromARGB(255, 157, 157, 155), + text: 'list', + isSelected: selectedIndex == 1, + onTap: () => setState(() => selectedIndex = 1), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + bottomLeft: Radius.circular(12), + topRight: selectedIndex==1? Radius.circular(12):Radius.zero, + bottomRight: selectedIndex==1? Radius.circular(12):Radius.zero, + ), + ), + ), + Positioned( + right: 0, + child: _buildToggleButton( + color: selectedIndex == 0 + ? const Color.fromARGB(255, 10, 69, 117) + : const Color.fromARGB(100, 177, 177, 177), + iconColor: selectedIndex == 0 + ? const Color.fromARGB(255, 234, 245, 254) + : const Color.fromARGB(255, 157, 157, 155), + icon: Assets.icons.mapSelected.path, + text: 'map', + isSelected: selectedIndex == 0, + onTap: () => setState(() => selectedIndex = 0), + borderRadius: BorderRadius.only( + topRight: Radius.circular(12), + bottomRight: Radius.circular(12), + bottomLeft: selectedIndex==0? Radius.circular(12):Radius.zero, + topLeft: selectedIndex==0? Radius.circular(12):Radius.zero, ), ), ), @@ -164,46 +225,138 @@ class _NearbyState extends State { ); } - Widget buildToggleButton({ - required String text, - required bool isSelected, - required VoidCallback onTap, - required BorderRadius borderRadius, - }) { - return GestureDetector( - onTap: onTap, - child: Container( - width: 170, - height: 60, - decoration: BoxDecoration( - borderRadius: borderRadius, - color: isSelected - ? const Color.fromARGB(255, 14, 63, 102) - : const Color.fromARGB(255, 234, 234, 233), - boxShadow: isSelected - ? [ - BoxShadow( - color: Colors.black.withOpacity(0.2), - spreadRadius: 1, - blurRadius: 4, - offset: const Offset(0, 2), - ) - ] - : null, - ), - alignment: Alignment.center, - child: Row( + Widget _buildListContent() { + return SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.fromLTRB(30, 5, 30, 0), + child: Column( children: [ - Text( - text, - style: TextStyle( - color: isSelected ? Colors.white : Colors.black87, - fontWeight: FontWeight.bold, - ), + Row( + children: [ + const Text( + "Sort by:", + style: TextStyle(fontWeight: FontWeight.w600), + ), + const SizedBox(width: 10), + DropdownButton( + value: selectedOption, + icon: Padding( + padding: const EdgeInsets.only(left: 6), + child: SvgPicture.asset(Assets.icons.arrowDown.path), + ), + elevation: 16, + style: const TextStyle( + color: Color.fromARGB(255, 33, 150, 243), + fontWeight: FontWeight.w500, + ), + underline: const SizedBox(), + onChanged: (String? newValue) { + setState(() { + selectedOption = newValue!; + }); + }, + items: options.map>((String value) { + return DropdownMenuItem( + value: value, + child: Text(value), + ); + }).toList(), + ), + ], + ), + const SizedBox(height: 10), + ListScreen( + delivery: true, + pickup: false, + expiryTimeString: "2025-05-17T13:20:49.623619357Z", + ), + ListScreen( + delivery: false, + pickup: true, + expiryTimeString: "2025-05-25T05:20:49.623619357Z", + ), + ListScreen( + delivery: true, + pickup: true, + expiryTimeString: "2025-05-12T13:20:37.435249520Z", ), ], ), ), ); } + + Widget _buildToggleButton({ + required String text, + required bool isSelected, + required VoidCallback onTap, + required BorderRadius borderRadius, + required Color color, + required String icon, + required Color iconColor, +}) { + return GestureDetector( + onTap: onTap, + child: Stack( + children: [ + Container( + width: 170, + height: 50, + decoration: BoxDecoration( + borderRadius: borderRadius, + color: isSelected + ? const Color.fromARGB(255, 14, 63, 102) + : const Color.fromARGB(255, 234, 234, 233), + boxShadow: isSelected + ? [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + spreadRadius: 1, + blurRadius: 4, + offset: const Offset(0, 2), + ), + ] + : null, + ), + alignment: Alignment.center, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + width: 30, + height: 30, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: color, + ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Center(child: SvgPicture.asset(icon, color: iconColor)), + ), + ), + const SizedBox(width: 10), + Text( + text, + style: TextStyle( + color: isSelected ? Colors.white : Colors.black87, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + if (isSelected) + Positioned( + bottom: 0, + left: 0, + right: 0, + child: SvgPicture.asset( + Assets.icons.shape.path, + height: 4, + ), + ), + ], + ), + ); +} } \ No newline at end of file diff --git a/lib/widgets/loginButton.dart b/lib/widgets/button.dart similarity index 64% rename from lib/widgets/loginButton.dart rename to lib/widgets/button.dart index bd0ae50..999edda 100644 --- a/lib/widgets/loginButton.dart +++ b/lib/widgets/button.dart @@ -1,28 +1,35 @@ import 'package:flutter/material.dart'; -class LogInButton extends StatelessWidget { +class Button extends StatefulWidget { final String text; final VoidCallback onPressed; + final Color color; - const LogInButton({ + const Button({ super.key, required this.text, - required this.onPressed, + required this.onPressed, + required this.color, }); + @override + State