├── ios ├── Runner │ ├── Runner-Bridging-Header.h │ ├── Assets.xcassets │ │ ├── LaunchImage.imageset │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ ├── README.md │ │ │ └── Contents.json │ │ └── AppIcon.appiconset │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── GoogleService-Info.plist │ ├── Info.plist │ └── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings ├── .gitignore └── Podfile ├── assets ├── images │ ├── img_kid.jpg │ ├── img_men.png │ ├── img_user.jpg │ ├── img_women.png │ ├── img_banner1.jpg │ ├── img_banner2.jpg │ ├── img_empty_box.png │ ├── img_free_delivery.png │ └── img_lowest_price.jpg ├── icons │ ├── user_black.png │ ├── icon_flutter.png │ ├── social │ │ ├── google.png │ │ ├── facebook.png │ │ ├── mail_black.png │ │ └── phone_black.png │ ├── icon_bell_black.png │ ├── icon_box_black.png │ ├── icon_cross_black.png │ ├── icon_down_arrow.png │ ├── icon_home_black.png │ ├── icon_men_dress.png │ ├── icon_menu_black.png │ ├── icon_women_dress.png │ ├── icon_account_black.png │ ├── icon_shopping_bag.png │ ├── icon_bookmark_black.png │ ├── icon_cosmetics_black.png │ ├── icon_kid_cloth_black.png │ ├── icon_left_arrow_black.png │ ├── icon_portfolio_black.png │ ├── icon_right_direct_black.png │ └── icon_winter_cloth_black.png └── screens │ ├── ios │ ├── screen1.png │ ├── screen2.png │ ├── screen3.png │ ├── screen4.png │ └── screen5.png │ └── android │ ├── screen1.png │ ├── screen2.png │ ├── screen3.png │ └── screen4.png ├── android ├── gradle.properties ├── .gitignore ├── .idea │ ├── caches │ │ └── build_file_checksums.ser │ ├── vcs.xml │ ├── runConfigurations.xml │ ├── misc.xml │ ├── gradle.xml │ ├── codeStyles │ │ └── Project.xml │ └── modules.xml ├── app │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── test │ │ │ │ │ └── myntra │ │ │ │ │ └── myntra_test_app │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── google-services.json │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── settings.gradle └── build.gradle ├── lib ├── src │ ├── core │ │ ├── models │ │ │ ├── http_exception.dart │ │ │ ├── search_models.dart │ │ │ ├── table_schemas │ │ │ │ ├── common_tables_schema.dart │ │ │ │ ├── user_table_schema.dart │ │ │ │ ├── manufacturer_table_schema.dart │ │ │ │ └── prod_table_schema.dart │ │ │ ├── common_models.dart │ │ │ └── product_model.dart │ │ ├── extensions │ │ │ └── color_extension.dart │ │ ├── helpers │ │ │ ├── url_constants.dart │ │ │ ├── prefs_constants.dart │ │ │ ├── helper.dart │ │ │ ├── common_constants.dart │ │ │ ├── alert_helper.dart │ │ │ └── validators.dart │ │ ├── view_model │ │ │ ├── product_provider.dart │ │ │ ├── products_provider.dart │ │ │ ├── auth_provider.dart │ │ │ └── category_provider.dart │ │ └── blocs │ │ │ └── item_detail_bloc.dart │ ├── ui │ │ ├── view │ │ │ ├── splash_screen.dart │ │ │ ├── account_summary │ │ │ │ ├── account_summary_top_view.dart │ │ │ │ └── account_summary_screen.dart │ │ │ ├── orders_list_screen.dart │ │ │ ├── onboarding │ │ │ │ ├── onboard_options_screen.dart │ │ │ │ └── login_screen.dart │ │ │ ├── home_screen.dart │ │ │ └── products │ │ │ │ └── items_list_screen.dart │ │ └── widget │ │ │ ├── custom_progress_bar.dart │ │ │ ├── tiles │ │ │ ├── home_tiles │ │ │ │ ├── home_screen_top_category_tile.dart │ │ │ │ └── home_screen_hot_deals_list_tile.dart │ │ │ ├── item_tiles │ │ │ │ ├── item_qty_grid_tile.dart │ │ │ │ ├── item_size_grid_tile.dart │ │ │ │ └── item_grid_tile.dart │ │ │ ├── account_summary_list_tile.dart │ │ │ └── app_drawer │ │ │ │ ├── app_drawer_list_tile.dart │ │ │ │ └── app_drawer_sub_list_tile.dart │ │ │ ├── custom_default_outline_button_with_icon.dart │ │ │ ├── custom_button_with_image.dart │ │ │ ├── item_detail_carousel_with_indicator.dart │ │ │ ├── custom_default_text_form_field.dart │ │ │ ├── app_drawer_profile_view.dart │ │ │ ├── custom_app_bar.dart │ │ │ ├── bottom_sheets │ │ │ └── select_qty_bottom_sheet.dart │ │ │ └── item_detail_floating_bottom_bar.dart │ └── res │ │ └── values │ │ └── theme.dart ├── provider_setup.dart └── main.dart ├── pubspec.yaml ├── test └── widget_test.dart ├── README.md └── .gitignore /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /assets/images/img_kid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_kid.jpg -------------------------------------------------------------------------------- /assets/images/img_men.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_men.png -------------------------------------------------------------------------------- /assets/icons/user_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/user_black.png -------------------------------------------------------------------------------- /assets/images/img_user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_user.jpg -------------------------------------------------------------------------------- /assets/images/img_women.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_women.png -------------------------------------------------------------------------------- /assets/icons/icon_flutter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_flutter.png -------------------------------------------------------------------------------- /assets/icons/social/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/social/google.png -------------------------------------------------------------------------------- /assets/images/img_banner1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_banner1.jpg -------------------------------------------------------------------------------- /assets/images/img_banner2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_banner2.jpg -------------------------------------------------------------------------------- /assets/screens/ios/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/ios/screen1.png -------------------------------------------------------------------------------- /assets/screens/ios/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/ios/screen2.png -------------------------------------------------------------------------------- /assets/screens/ios/screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/ios/screen3.png -------------------------------------------------------------------------------- /assets/screens/ios/screen4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/ios/screen4.png -------------------------------------------------------------------------------- /assets/screens/ios/screen5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/ios/screen5.png -------------------------------------------------------------------------------- /assets/icons/icon_bell_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_bell_black.png -------------------------------------------------------------------------------- /assets/icons/icon_box_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_box_black.png -------------------------------------------------------------------------------- /assets/icons/icon_cross_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_cross_black.png -------------------------------------------------------------------------------- /assets/icons/icon_down_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_down_arrow.png -------------------------------------------------------------------------------- /assets/icons/icon_home_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_home_black.png -------------------------------------------------------------------------------- /assets/icons/icon_men_dress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_men_dress.png -------------------------------------------------------------------------------- /assets/icons/icon_menu_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_menu_black.png -------------------------------------------------------------------------------- /assets/icons/icon_women_dress.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_women_dress.png -------------------------------------------------------------------------------- /assets/icons/social/facebook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/social/facebook.png -------------------------------------------------------------------------------- /assets/images/img_empty_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_empty_box.png -------------------------------------------------------------------------------- /assets/icons/icon_account_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_account_black.png -------------------------------------------------------------------------------- /assets/icons/icon_shopping_bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_shopping_bag.png -------------------------------------------------------------------------------- /assets/icons/social/mail_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/social/mail_black.png -------------------------------------------------------------------------------- /assets/icons/social/phone_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/social/phone_black.png -------------------------------------------------------------------------------- /assets/images/img_free_delivery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_free_delivery.png -------------------------------------------------------------------------------- /assets/images/img_lowest_price.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/images/img_lowest_price.jpg -------------------------------------------------------------------------------- /assets/screens/android/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/android/screen1.png -------------------------------------------------------------------------------- /assets/screens/android/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/android/screen2.png -------------------------------------------------------------------------------- /assets/screens/android/screen3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/android/screen3.png -------------------------------------------------------------------------------- /assets/screens/android/screen4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/screens/android/screen4.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /assets/icons/icon_bookmark_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_bookmark_black.png -------------------------------------------------------------------------------- /assets/icons/icon_cosmetics_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_cosmetics_black.png -------------------------------------------------------------------------------- /assets/icons/icon_kid_cloth_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_kid_cloth_black.png -------------------------------------------------------------------------------- /assets/icons/icon_left_arrow_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_left_arrow_black.png -------------------------------------------------------------------------------- /assets/icons/icon_portfolio_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_portfolio_black.png -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /assets/icons/icon_right_direct_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_right_direct_black.png -------------------------------------------------------------------------------- /assets/icons/icon_winter_cloth_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/assets/icons/icon_winter_cloth_black.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | -------------------------------------------------------------------------------- /android/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/android/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flutter-devs/flutter_shopping_portal/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/core/models/http_exception.dart: -------------------------------------------------------------------------------- 1 | class HttpException implements Exception { 2 | final String message; 3 | 4 | HttpException({this.message}); 5 | 6 | @override 7 | String toString() { 8 | // TODO: implement toString 9 | return message; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Mar 18 17:03:42 IST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/core/models/search_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SearchProductsModel { 4 | final category; 5 | final subCategory; 6 | final subSubCategory; 7 | 8 | SearchProductsModel({ 9 | @required this.category, 10 | @required this.subCategory, 11 | @required this.subSubCategory, 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildSystemType 6 | Latest 7 | PreviewsEnabled 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/core/extensions/color_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HexColor extends Color { 4 | static int _getColorFromHex(String hexColor) { 5 | hexColor = hexColor.toUpperCase().replaceAll("#", ""); 6 | if (hexColor.length == 6) { 7 | hexColor = "FF" + hexColor; 8 | } 9 | return int.parse(hexColor, radix: 16); 10 | } 11 | 12 | HexColor(final String hexColor) : super(_getColorFromHex(hexColor)); 13 | } 14 | -------------------------------------------------------------------------------- /lib/src/core/models/table_schemas/common_tables_schema.dart: -------------------------------------------------------------------------------- 1 | class CategoryTable { 2 | static const imageUrl = 'image_url'; 3 | static const isActive = 'is_active'; 4 | static const menuIconName = 'menu_icon_name'; 5 | static const name = 'name'; 6 | static const subCatIDs = 'sub_cat_ids'; 7 | } 8 | 9 | class SubCategoryTable { 10 | static const isActive = 'is_active'; 11 | static const name = 'name'; 12 | static const subSubCatIDs = 'sub_sub_cat_ids'; 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/test/myntra/myntra_test_app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.test.myntra.myntra_test_app 2 | 3 | import androidx.annotation.NonNull; 4 | import io.flutter.embedding.android.FlutterActivity 5 | import io.flutter.embedding.engine.FlutterEngine 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { 10 | GeneratedPluginRegistrant.registerWith(flutterEngine); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/ui/view/splash_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SplashScreen extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Scaffold( 7 | body: Container( 8 | alignment: Alignment.center, 9 | padding: EdgeInsets.symmetric( 10 | horizontal: 10, 11 | ), 12 | child: Text( 13 | 'Friendly Shopping', 14 | style: TextStyle( 15 | fontSize: 20, 16 | fontWeight: FontWeight.bold, 17 | ), 18 | ), 19 | ), 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /android/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /lib/src/core/models/table_schemas/user_table_schema.dart: -------------------------------------------------------------------------------- 1 | class UserTable { 2 | static const cartListDetailMap = 'cart_list_map'; //UserCartListMap 3 | static const emailID = 'email_id'; 4 | static const gender = 'gender'; 5 | static const imageUrl = 'image_url'; 6 | static const mobNum = 'mob_num'; 7 | static const name = 'name'; 8 | static const userID = 'user_id'; 9 | static const wishListDetailMap = 'wish_list_map'; //UserWishListMap 10 | } 11 | 12 | class UserCartListMap { 13 | static const prodIDsList = 'prod_ids_list'; 14 | } 15 | 16 | class UserWishListMap { 17 | static const prodIDsList = 'prod_ids_list'; 18 | } 19 | -------------------------------------------------------------------------------- /lib/src/res/values/theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | var theme = ThemeData(primarySwatch: white, accentColor: Colors.grey); 4 | 5 | final MaterialColor white = const MaterialColor( 6 | 0xFFFFFFFF, 7 | const { 8 | 50: const Color(0xFFFFFFFF), 9 | 100: const Color(0xFFFFFFFF), 10 | 200: const Color(0xFFFFFFFF), 11 | 300: const Color(0xFFFFFFFF), 12 | 400: const Color(0xFFFFFFFF), 13 | 500: const Color(0xFFFFFFFF), 14 | 600: const Color(0xFFFFFFFF), 15 | 700: const Color(0xFFFFFFFF), 16 | 800: const Color(0xFFFFFFFF), 17 | 900: const Color(0xFFFFFFFF), 18 | }, 19 | ); 20 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/src/core/helpers/url_constants.dart: -------------------------------------------------------------------------------- 1 | class UrlConstants { 2 | // static const baseUrl = 'https://firestore.googleapis.com/v1/projects/friendly-shopping-f031c/databases/(default)/documents/'; 3 | // static const api_key = 'AIzaSyDXndZgvk35y1ZbXM2hYR4pTx67KCyF-r4'; 4 | 5 | //Common 6 | // static const getCategories = baseUrl + 'categories?key=$api_key'; 7 | 8 | } 9 | 10 | class Tables { 11 | //Common 12 | static const categories = 'categories'; 13 | static const manufacturers = 'manufacturers'; 14 | static const products = 'products'; 15 | static const subCategories = 'sub_categories'; 16 | static const subSubCategories = 'sub_sub_categories'; 17 | static const users = 'users'; 18 | } 19 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.6.1' 10 | classpath 'com.google.gms:google-services:4.3.0' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | rootProject.buildDir = '../build' 23 | subprojects { 24 | project.buildDir = "${rootProject.buildDir}/${project.name}" 25 | } 26 | subprojects { 27 | project.evaluationDependsOn(':app') 28 | } 29 | 30 | task clean(type: Delete) { 31 | delete rootProject.buildDir 32 | } 33 | -------------------------------------------------------------------------------- /lib/src/core/helpers/prefs_constants.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | class FSPrefs { 6 | static const UserData = 'userData'; 7 | static const ExpDateTime = 'userExpDateTime'; 8 | static const UserID = 'userID'; 9 | static const UserDocID = 'UserDocID'; 10 | static const UserImageUrl = 'userImageUrl'; 11 | static const UserName = 'userName'; 12 | static const UserToken = 'userToken'; 13 | 14 | static Future> getUserPrefsMap() async { 15 | final prefs = await SharedPreferences.getInstance(); 16 | if (!prefs.containsKey(FSPrefs.UserData)) { 17 | return Map(); 18 | } 19 | final extractedUserData = 20 | json.decode(prefs.getString(FSPrefs.UserData)) as Map; 21 | return extractedUserData; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 1.8 14 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: myntra_test_app 2 | description: A flutter app to showcase online shopping portal using Provider architecture. FireStore has been used as backend for this app. 3 | 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.2.2 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | carousel_slider: ^1.3.0 13 | cloud_firestore: ^0.13.4 14 | cupertino_icons: ^0.1.2 15 | firebase_auth: 0.15.3 16 | # firebase_core: 0.4.4+3 17 | firebase_storage: ^3.1.3 18 | flutter_test: 19 | sdk: flutter 20 | flutter_widgets: ^0.1.10 21 | http: ^0.12.0+4 22 | provider: ^4.0.4 23 | progress_hud: ^1.1.0 24 | shared_preferences: ^0.5.6+3 25 | 26 | dev_dependencies: 27 | 28 | 29 | dependency_overrides: 30 | firebase_core: 0.4.4 31 | 32 | flutter: 33 | 34 | uses-material-design: true 35 | 36 | assets: 37 | - assets/icons/ 38 | - assets/icons/social/ 39 | - assets/images/ 40 | 41 | -------------------------------------------------------------------------------- /lib/src/core/models/table_schemas/manufacturer_table_schema.dart: -------------------------------------------------------------------------------- 1 | class ManufacturerTable { 2 | static const addressDetail = 'address_detail'; //ManufacturerAddressMap 3 | static const brandsArray = 'brands_array'; //ManufacturerBrandsSubTable 4 | static const name = 'name'; 5 | // static const contactDetail = 'contact_detail'; 6 | } 7 | 8 | class ManufacturerAddressMap { 9 | static const add1 = 'add_1'; 10 | static const add2 = 'add_2'; 11 | static const cc = 'cc'; 12 | static const city = 'city'; 13 | static const country = 'country'; 14 | static const mobNum = 'mob_num'; 15 | static const pinCode = 'pin_code'; 16 | static const state = 'state'; 17 | } 18 | 19 | class ManufacturerBrandsSubTable { 20 | static const name = 'name'; 21 | // static const add2 = 'add_2'; 22 | // static const city = 'city'; 23 | // static const country = 'country'; 24 | // static const pinCode = 'pin_code'; 25 | // static const state = 'state'; 26 | } 27 | -------------------------------------------------------------------------------- /lib/src/ui/widget/custom_progress_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | import 'package:progress_hud/progress_hud.dart'; 4 | 5 | class CustomProgressBar extends StatelessWidget { 6 | final bool status; 7 | 8 | CustomProgressBar({@required this.status}); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | if (status) { 13 | return ProgressHUD( 14 | backgroundColor: Colors.black45, 15 | color: AppColors.silver, 16 | containerColor: Colors.black, 17 | borderRadius: 10.0, 18 | ); 19 | } 20 | return Container( 21 | height: 0.0, 22 | width: 0.0, 23 | ); 24 | } 25 | // Widget buildAndShowHideCircularProgressWith({@required bool status}) { 26 | // if (status) { 27 | // return CustomProgressBar(); 28 | // } 29 | // return Container( 30 | // height: 0.0, 31 | // width: 0.0, 32 | // ); 33 | // } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /lib/provider_setup.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/view_model/auth_provider.dart'; 3 | import 'package:myntra_test_app/src/core/view_model/category_provider.dart'; 4 | import 'package:myntra_test_app/src/core/view_model/product_provider.dart'; 5 | import 'package:myntra_test_app/src/core/view_model/products_provider.dart'; 6 | import 'package:provider/provider.dart'; 7 | import 'package:provider/single_child_widget.dart'; 8 | 9 | import 'package:myntra_test_app/src/core/blocs/item_detail_bloc.dart'; 10 | 11 | var providers = [ 12 | ChangeNotifierProvider( 13 | create: (_) => AuthProvider(), 14 | ), 15 | ChangeNotifierProvider( 16 | create: (_) => CategoryProvider(), 17 | ), 18 | ChangeNotifierProvider( 19 | create: (_) => ProductsProvider(), 20 | ), 21 | Provider( 22 | create: (_) => ItemDetailBloc(), 23 | ), 24 | ChangeNotifierProxyProvider( 25 | update: (_, authProvider, productProvider) => productProvider 26 | ..userID = authProvider.getUserID 27 | ..userDocID = authProvider.getUserDocID, 28 | create: (_) => ProductProvider(), 29 | ), 30 | ]; 31 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:myntra_test_app/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/home_tiles/home_screen_top_category_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | 4 | class HomeScreenTopCategoryListTile extends StatelessWidget { 5 | final String imageName; 6 | final String title; 7 | 8 | HomeScreenTopCategoryListTile( 9 | {@required this.imageName, @required this.title}); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return Container( 14 | width: 130, 15 | padding: EdgeInsets.symmetric(vertical: 5, horizontal: 2.5), 16 | child: Card( 17 | elevation: 2, 18 | child: Column( 19 | children: [ 20 | Expanded( 21 | child: FadeInImage( 22 | placeholder: AssetImage( 23 | AppIcons.flutter, 24 | ), 25 | image: NetworkImage(imageName), 26 | fit: BoxFit.cover, 27 | ), 28 | ), 29 | Container( 30 | height: 25, 31 | // color: Colors.red, 32 | alignment: Alignment.center, 33 | child: Text( 34 | title, 35 | textAlign: TextAlign.center, 36 | ), 37 | ), 38 | ], 39 | ), 40 | ), 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/core/models/common_models.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | //class SubMenuModal { 4 | // final String title; 5 | // final List subItems; 6 | // 7 | // SubMenuModal({@required this.title, @required this.subItems}); 8 | //} 9 | 10 | class CategoryModal { 11 | final String name; 12 | final String id; 13 | final String homePageImageUrl; 14 | final String menuIconStoragePath; 15 | String menuIconImageUrl; 16 | final List subCategoriesIDs; 17 | List subCategories; 18 | 19 | CategoryModal({ 20 | @required this.name, 21 | @required this.id, 22 | @required this.homePageImageUrl, 23 | @required this.menuIconStoragePath, 24 | this.menuIconImageUrl = '', 25 | @required this.subCategoriesIDs, 26 | this.subCategories, 27 | }); 28 | } 29 | 30 | class SubCategoryModal { 31 | final String name; 32 | final String id; 33 | final List subSubCategoriesIDs; 34 | List subSubCategories; 35 | 36 | SubCategoryModal({ 37 | @required this.name, 38 | @required this.id, 39 | @required this.subSubCategoriesIDs, 40 | this.subSubCategories, 41 | }); 42 | } 43 | 44 | class SubSubCategoryModal { 45 | final String name; 46 | final String id; 47 | 48 | SubSubCategoryModal({ 49 | @required this.name, 50 | @required this.id, 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /ios/Runner/GoogleService-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CLIENT_ID 6 | 812637154023-c84andcs1gj9tplhtiskftvtt284me61.apps.googleusercontent.com 7 | REVERSED_CLIENT_ID 8 | com.googleusercontent.apps.812637154023-c84andcs1gj9tplhtiskftvtt284me61 9 | API_KEY 10 | AIzaSyA73_0GYed2ERXR0V-5SjomWmkt3VgakuE 11 | GCM_SENDER_ID 12 | 812637154023 13 | PLIST_VERSION 14 | 1 15 | BUNDLE_ID 16 | com.successive.adhoc.friendlyshoppingapp 17 | PROJECT_ID 18 | friendly-shopping-f031c 19 | STORAGE_BUCKET 20 | friendly-shopping-f031c.appspot.com 21 | IS_ADS_ENABLED 22 | 23 | IS_ANALYTICS_ENABLED 24 | 25 | IS_APPINVITE_ENABLED 26 | 27 | IS_GCM_ENABLED 28 | 29 | IS_SIGNIN_ENABLED 30 | 31 | GOOGLE_APP_ID 32 | 1:812637154023:ios:55a9b79490542a6302d042 33 | DATABASE_URL 34 | https://friendly-shopping-f031c.firebaseio.com 35 | 36 | -------------------------------------------------------------------------------- /lib/src/ui/widget/custom_default_outline_button_with_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | 4 | class CustomDefaultOutlineButtonWithIcon extends StatelessWidget { 5 | final String titleString; 6 | final TextStyle titleTextStyle; 7 | 8 | final double height; 9 | 10 | Function buttonClicked; 11 | 12 | CustomDefaultOutlineButtonWithIcon({ 13 | @required this.titleString, 14 | this.titleTextStyle, 15 | this.height = 40, 16 | this.buttonClicked, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return OutlineButton( 22 | child: Text( 23 | titleString, 24 | style: TextStyle( 25 | color: titleTextStyle.color ?? AppColors.cabaret, 26 | fontSize: titleTextStyle.fontSize ?? 14, 27 | fontWeight: titleTextStyle.fontWeight ?? FontWeight.w600, 28 | ), 29 | ), 30 | onPressed: buttonClicked ?? 31 | () { 32 | print( 33 | 'CustomDefaultOutlineButtonWithIcon clicked with title: $titleString'); 34 | }, 35 | borderSide: BorderSide( 36 | color: AppColors.silver, 37 | ), 38 | shape: RoundedRectangleBorder( 39 | borderRadius: BorderRadius.circular(3.0), 40 | ), 41 | textColor: AppColors.cabaret, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/ui/widget/custom_button_with_image.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/widgets.dart'; 3 | 4 | class CustomButtonWithImage extends StatelessWidget { 5 | final String imageNameString; 6 | final String titleString; 7 | final double imageViewHeight = 18; 8 | 9 | CustomButtonWithImage( 10 | {@required this.imageNameString, @required this.titleString}); 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Container( 15 | height: 50, 16 | decoration: BoxDecoration( 17 | borderRadius: BorderRadius.circular(5), 18 | border: Border.all( 19 | color: Colors.grey, 20 | width: 0.5, 21 | ), 22 | ), 23 | child: Row( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | Container( 27 | height: imageViewHeight, 28 | width: imageViewHeight, 29 | child: Image.asset( 30 | imageNameString, 31 | fit: BoxFit.contain, 32 | ), 33 | ), 34 | SizedBox( 35 | width: 10, 36 | ), 37 | Text( 38 | titleString, 39 | style: TextStyle( 40 | color: Colors.black, 41 | fontWeight: FontWeight.w600, 42 | ), 43 | ), 44 | ], 45 | ), 46 | ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /android/app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "812637154023", 4 | "firebase_url": "https://friendly-shopping-f031c.firebaseio.com", 5 | "project_id": "friendly-shopping-f031c", 6 | "storage_bucket": "friendly-shopping-f031c.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:812637154023:android:beb991e6e1f7878e02d042", 12 | "android_client_info": { 13 | "package_name": "com.aeologic.friendlyshoppingapp" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "812637154023-5gha3hthkl2vegi3acrm3qrl7c64bj5g.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyCzkVezNfFGtYa8Nmpfb3vqA4-OD393O3k" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "812637154023-5gha3hthkl2vegi3acrm3qrl7c64bj5g.apps.googleusercontent.com", 32 | "client_type": 3 33 | }, 34 | { 35 | "client_id": "812637154023-c84andcs1gj9tplhtiskftvtt284me61.apps.googleusercontent.com", 36 | "client_type": 2, 37 | "ios_info": { 38 | "bundle_id": "com.successive.adhoc.friendlyshoppingapp" 39 | } 40 | } 41 | ] 42 | } 43 | } 44 | } 45 | ], 46 | "configuration_version": "1" 47 | } -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | myntra_test_app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | UILaunchStoryboardName 31 | LaunchScreen 32 | UIMainStoryboardFile 33 | Main 34 | UIStatusBarStyle 35 | UIStatusBarStyleDarkContent 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | 40 | UISupportedInterfaceOrientations~ipad 41 | 42 | UIInterfaceOrientationPortrait 43 | 44 | UIUserInterfaceStyle 45 | Light 46 | UIViewControllerBasedStatusBarAppearance 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/item_tiles/item_qty_grid_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ItemQtyGridTile extends StatelessWidget { 4 | final int itemQty; 5 | final bool isSelected; 6 | final bool isSelectable; 7 | final Function newQtySelected; 8 | 9 | final double tileWidth = 55; 10 | 11 | ItemQtyGridTile({ 12 | @required this.itemQty, 13 | this.isSelected = false, 14 | this.isSelectable = false, 15 | this.newQtySelected, 16 | }); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return SizedBox( 21 | width: tileWidth, 22 | child: Column( 23 | children: [ 24 | GestureDetector( 25 | onTap: isSelectable ? () => newQtySelected(itemQty) : null, 26 | child: Container( 27 | margin: EdgeInsets.symmetric(horizontal: 5, vertical: 5), 28 | decoration: BoxDecoration( 29 | // color: Colors.t, 30 | borderRadius: BorderRadius.all( 31 | Radius.circular((tileWidth - 10) / 2), 32 | ), 33 | border: Border.all( 34 | color: isSelected ? Colors.red : Colors.black, 35 | width: 0.8, 36 | ), 37 | ), 38 | alignment: Alignment.center, 39 | width: tileWidth - 10, 40 | height: tileWidth - 10, 41 | child: Text( 42 | '$itemQty', 43 | style: TextStyle( 44 | color: isSelected ? Colors.red : Colors.black, 45 | ), 46 | ), 47 | ), 48 | ), 49 | Spacer(), 50 | ], 51 | ), 52 | ); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 20 | 21 | 22 | 23 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /lib/src/core/view_model/product_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:myntra_test_app/src/core/helpers/url_constants.dart'; 5 | import 'package:myntra_test_app/src/core/models/table_schemas/user_table_schema.dart'; 6 | 7 | class ProductProvider with ChangeNotifier { 8 | String userID; 9 | String userDocID; 10 | 11 | final _databaseReference = Firestore.instance; 12 | 13 | Future addProdToCartListWith({@required String prodID}) async { 14 | // print('addProdToCartListWith prodID: $prodID'); 15 | try { 16 | await _databaseReference 17 | .collection(Tables.users) 18 | .document('$userDocID') 19 | .updateData({ 20 | UserTable.cartListDetailMap: { 21 | UserCartListMap.prodIDsList: FieldValue.arrayUnion([prodID]) 22 | } 23 | }); 24 | 25 | print('Add prod to cart done'); 26 | 27 | ///${UserTable.wishListDetailMap} 28 | return true; 29 | } catch (e) { 30 | print('Add prod to cart error: $e'); 31 | return false; 32 | } 33 | } 34 | 35 | Future addProdToWishListWith({@required String prodID}) async { 36 | // print('addProdToWishListWith prodID: $prodID'); 37 | try { 38 | await _databaseReference 39 | .collection(Tables.users) 40 | .document('$userDocID') 41 | .updateData({ 42 | UserTable.wishListDetailMap: { 43 | UserWishListMap.prodIDsList: FieldValue.arrayUnion([prodID]) 44 | } 45 | }); 46 | print('Add prod to wishlist done'); 47 | 48 | ///${UserTable.wishListDetailMap} 49 | return true; 50 | } catch (e) { 51 | print('Add prod to wishlist error: $e'); 52 | return false; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/core/blocs/item_detail_bloc.dart: -------------------------------------------------------------------------------- 1 | //StreamController(s) 2 | //Streams and sinks getters 3 | //Constructor - add data, listen to changes 4 | //Core functions 5 | //Dispose 6 | 7 | import 'dart:async'; 8 | 9 | class ItemDetailBloc { 10 | bool _isBlocDisposed = false; 11 | 12 | bool _showFullProdDetail = false; 13 | bool _showBottomBar = true; 14 | 15 | //StreamController(s) 16 | 17 | final _showHideProdDetailStreamController = StreamController(); 18 | 19 | Stream get showHideProdDetailStream => 20 | _showHideProdDetailStreamController.stream; 21 | 22 | bool get getProdDetailStatus => _showFullProdDetail; 23 | 24 | final _sizeChartVisiblityCheckerStreamController = StreamController(); 25 | 26 | Stream get sizeChartVisiblityCheckerStream => 27 | _sizeChartVisiblityCheckerStreamController.stream; 28 | 29 | bool get getSizeChartVisibilityStatus => _showBottomBar; 30 | 31 | //Constructor - add data, listen to changes 32 | 33 | ItemDetailBloc() { 34 | _showHideProdDetailStreamController.add(_showFullProdDetail); 35 | 36 | _sizeChartVisiblityCheckerStreamController.add(_showBottomBar); 37 | } 38 | 39 | //Core functions 40 | 41 | void updateProdDetailViewStatus() { 42 | print('updateProdDetailViewStatus'); 43 | _showFullProdDetail = !_showFullProdDetail; 44 | print('_showFullProdDetail: $_showFullProdDetail'); 45 | _showHideProdDetailStreamController.sink.add(_showFullProdDetail); 46 | } 47 | 48 | void changeBottomBarVisibiltyTo(bool status) { 49 | if (_isBlocDisposed) { 50 | return; 51 | } 52 | _sizeChartVisiblityCheckerStreamController.sink.add(status); 53 | } 54 | 55 | //Dispose 56 | 57 | void dispose() { 58 | print('ItemDetailBloc closed'); 59 | _showHideProdDetailStreamController.close(); 60 | _sizeChartVisiblityCheckerStreamController.close(); 61 | _isBlocDisposed = true; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /lib/src/core/helpers/helper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 5 | import 'package:myntra_test_app/src/core/models/product_model.dart'; 6 | 7 | class Helper { 8 | static void dismissKeyboard({@required BuildContext ctx}) { 9 | FocusScopeNode currentFocus = FocusScope.of(ctx); 10 | if (!currentFocus.hasPrimaryFocus) { 11 | currentFocus.unfocus(); 12 | } 13 | } 14 | 15 | static DiscountTypes getDiscountTypeFor({@required int code}) { 16 | var discountType = DiscountTypes.None; 17 | 18 | switch (code) { 19 | case 1: 20 | discountType = DiscountTypes.Amount; 21 | break; 22 | 23 | case 2: 24 | discountType = DiscountTypes.Percentage; 25 | break; 26 | 27 | default: 28 | break; 29 | } 30 | 31 | return discountType; 32 | } 33 | 34 | static int getIteratedPriceToShowFor({@required ProductModel product}) { 35 | var iteratedPrice = product.priceMap.price; 36 | final discountType = 37 | getDiscountTypeFor(code: product.priceMap.discountType); 38 | switch (discountType) { 39 | case DiscountTypes.Amount: 40 | iteratedPrice = iteratedPrice - product.priceMap.discountAmount; 41 | break; 42 | 43 | case DiscountTypes.Percentage: 44 | iteratedPrice = (iteratedPrice - 45 | (iteratedPrice * product.priceMap.discountPercentage) / 100) as int; 46 | break; 47 | 48 | default: 49 | break; 50 | } 51 | 52 | return iteratedPrice; 53 | } 54 | 55 | static String getMenuOptionValue(MenuOptions menuOption) { 56 | switch (menuOption) { 57 | case MenuOptions.Account: 58 | return 'Account'; 59 | case MenuOptions.None: 60 | return 'None'; 61 | 62 | default: 63 | return ''; 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /android/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/provider_setup.dart'; 3 | import 'package:myntra_test_app/src/res/values/theme.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:myntra_test_app/src/core/helpers/helper.dart'; 6 | import 'package:myntra_test_app/src/core/view_model/auth_provider.dart'; 7 | import 'package:myntra_test_app/src/ui/view/home_screen.dart'; 8 | import 'package:myntra_test_app/src/ui/view/splash_screen.dart'; 9 | 10 | // This is the top level function where execution starts 11 | void main() { 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | return runApp(MyApp()); 14 | } 15 | 16 | class MyApp extends StatelessWidget { 17 | // This widget is the root of your application. 18 | @override 19 | Widget build(BuildContext context) { 20 | return GestureDetector( 21 | onTap: () { 22 | Helper.dismissKeyboard(ctx: context); 23 | }, 24 | // Adding providers at the top level of widgets 25 | child: MultiProvider( 26 | providers: providers, 27 | // This indicates that we are using Material property in our app 28 | child: MaterialApp( 29 | title: 'Flutter Demo', 30 | // Defining the global theme of the app 31 | theme: theme, 32 | // To disable debug banner 33 | debugShowCheckedModeBanner: false, 34 | // Checking the user is authorized or not by wrapping to Consumer 35 | home: Consumer( 36 | builder: (ctx, authProvider, _) { 37 | return authProvider.isAuthorized 38 | ? HomeScreen() 39 | : FutureBuilder( 40 | future: authProvider.tryAutoLogin(), 41 | builder: (ctx1, snapshot) { 42 | return snapshot.connectionState == 43 | ConnectionState.waiting 44 | ? SplashScreen() 45 | : HomeScreen(); 46 | }, 47 | ); 48 | }, 49 | ), 50 | // Defining the app routes 51 | routes: { 52 | HomeScreen.routeName: (ctx) => HomeScreen(), 53 | }, 54 | ), 55 | ), 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId 'com.aeologic.friendlyshoppingapp' 42 | minSdkVersion 21 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'androidx.test:runner:1.1.1' 66 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1' 67 | } 68 | 69 | apply plugin: 'com.google.gms.google-services' -------------------------------------------------------------------------------- /lib/src/ui/widget/item_detail_carousel_with_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:carousel_slider/carousel_slider.dart'; 3 | 4 | class ItemDetailCarouselWithIndicator extends StatefulWidget { 5 | final List itemsArray; 6 | 7 | ItemDetailCarouselWithIndicator({@required this.itemsArray}); 8 | 9 | @override 10 | _ItemDetailCarouselWithIndicatorState createState() => 11 | _ItemDetailCarouselWithIndicatorState(); 12 | } 13 | 14 | class _ItemDetailCarouselWithIndicatorState 15 | extends State { 16 | int _current = 0; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | final double height = MediaQuery.of(context).size.height; 21 | 22 | return SingleChildScrollView( 23 | child: Column(children: [ 24 | CarouselSlider.builder( 25 | height: height / 2, 26 | itemCount: widget.itemsArray.length, 27 | itemBuilder: (BuildContext ctx, int itemIndex) => Column( 28 | mainAxisAlignment: MainAxisAlignment.end, 29 | children: [ 30 | Expanded( 31 | child: Image.network( 32 | widget.itemsArray[itemIndex], 33 | fit: BoxFit.cover, 34 | ), 35 | ), 36 | Container( 37 | height: 0.2, 38 | color: Colors.grey, 39 | ), 40 | ], 41 | ), 42 | enableInfiniteScroll: false, 43 | viewportFraction: 1.0, 44 | onPageChanged: (index) { 45 | setState(() { 46 | _current = index; 47 | }); 48 | }, 49 | ), 50 | Row( 51 | mainAxisAlignment: MainAxisAlignment.center, 52 | children: widget.itemsArray.map((item) { 53 | return Container( 54 | width: 8.0, 55 | height: 8.0, 56 | margin: EdgeInsets.symmetric(vertical: 10.0, horizontal: 2.0), 57 | decoration: BoxDecoration( 58 | shape: BoxShape.circle, 59 | color: _current == widget.itemsArray.indexOf(item) 60 | ? Color.fromRGBO(0, 0, 0, 0.9) 61 | : Color.fromRGBO(0, 0, 0, 0.4)), 62 | ); 63 | }).toList(), 64 | ), 65 | ]), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/ui/view/account_summary/account_summary_top_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | import 'package:myntra_test_app/src/core/helpers/prefs_constants.dart'; 4 | 5 | class AccountSummaryTopView extends StatefulWidget { 6 | @override 7 | _AccountSummaryTopViewState createState() => _AccountSummaryTopViewState(); 8 | } 9 | 10 | class _AccountSummaryTopViewState extends State { 11 | String _name = ''; 12 | 13 | @override 14 | void initState() { 15 | super.initState(); 16 | _loadUserPrefs(); 17 | } 18 | 19 | _loadUserPrefs() async { 20 | final userPrefsMap = await FSPrefs.getUserPrefsMap(); 21 | setState(() { 22 | _name = userPrefsMap[FSPrefs.UserName] ?? ''; 23 | }); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Stack( 29 | children: [ 30 | Container( 31 | height: 260, 32 | decoration: BoxDecoration( 33 | gradient: LinearGradient( 34 | begin: Alignment.topRight, 35 | end: Alignment.bottomLeft, 36 | colors: [Colors.blue, Colors.red], 37 | ), 38 | ), 39 | ), 40 | Positioned( 41 | bottom: 0, 42 | right: 0, 43 | left: 0, 44 | child: Container( 45 | padding: EdgeInsets.all(15), 46 | color: Colors.white, 47 | height: 100, 48 | ), 49 | ), 50 | Positioned( 51 | left: 15, 52 | bottom: 30, 53 | child: Container( 54 | height: 150, 55 | // padding: EdgeInsets.all(20), 56 | decoration: BoxDecoration( 57 | borderRadius: BorderRadius.circular(5), 58 | border: Border.all( 59 | color: Colors.white, 60 | width: 2, 61 | ), 62 | color: Colors.white, 63 | ), 64 | child: Image.asset(AppImages.user), 65 | ), 66 | ), 67 | Positioned( 68 | left: 180, 69 | bottom: 70, 70 | child: Text( 71 | _name, 72 | style: TextStyle( 73 | fontSize: 17, 74 | fontWeight: FontWeight.w600, 75 | ), 76 | ), 77 | ) 78 | ], 79 | ); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /lib/src/core/models/table_schemas/prod_table_schema.dart: -------------------------------------------------------------------------------- 1 | class ProductTable { 2 | static const categoriesIDsArray = 'categories_ids_array'; 3 | static const customerReviewsArray = 4 | 'customer_reviews_array'; //ProdCustomerReviewsSubTable 5 | static const customerReviewsCount = 'customer_reviews_count'; 6 | static const descriptionMap = 'description_map'; //ProdDescriptionMap 7 | static const priceDetailMap = 'price_detail'; //ProdPriceMap 8 | static const imgUrlsArray = 'img_urls_array'; 9 | static const isActive = 'is_active'; 10 | static const manufacturerDetailMap = 11 | 'manufacturer_detail_map'; //ProdManufacturerMap 12 | static const sizesDetailMap = 'sizes_detail_map'; //ProdSizesDetailMap 13 | static const specialMsg = 'special_msg'; 14 | static const subCategoriesIDsArray = 'sub_categories_ids_array'; 15 | static const subSubcategoriesIDsArray = 'sub_sub_categories_ids_array'; 16 | } 17 | 18 | class ProdCustomerReviewsSubTable { 19 | static const dislikesCount = 'dislikes_count'; 20 | static const imagesArray = 'images_array'; 21 | static const likesCount = 'likes_count'; 22 | static const message = 'message'; 23 | static const timeStamp = 'time_stamp'; 24 | static const userId = 'user_id'; 25 | } 26 | 27 | class ProdDescriptionMap { 28 | static const long = 'long'; 29 | static const materialAndCare = 'material_and_care'; 30 | static const short1 = 'short_1'; 31 | static const short2 = 'short_2'; 32 | static const sizeAndFit = 'size_and_fit'; 33 | } 34 | 35 | class ProdPriceMap { 36 | static const discountAmount = 'discount_amount'; 37 | static const discountMaxAmountViaPercentage = 38 | 'discount_max_amount_via_percentage'; // 0 - Consider no limit. 39 | static const discountPercentage = 'discount_percentage'; //Upper limit is 100. 40 | static const discountType = 41 | 'discount_type'; //0 - No disc, 1 - Amount disc, 2 - Percentage disc. 42 | static const price = 'price'; 43 | } 44 | 45 | class ProdManufacturerMap { 46 | static const id = 'id'; 47 | static const brandName = 'brand_name'; 48 | static const brandId = 'brand_id'; 49 | static const name = 'name'; 50 | static const prodCode = 'prod_code'; 51 | } 52 | 53 | class ProdSizesDetailMap { 54 | static const list = 'list'; //[ProdSizeMap] 55 | static const specialMsg = 'special_msg'; 56 | } 57 | 58 | class ProdSizeMap { 59 | static const id = 'id'; 60 | static const isActive = 'is_active'; 61 | static const name = 'name'; 62 | static const remainingQty = 'remaining_qty'; 63 | } 64 | -------------------------------------------------------------------------------- /lib/src/core/models/product_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | class ProductModel { 4 | final int customerReviewsCount; 5 | final ProdDescriptionModel descriptionMap; 6 | final String id; 7 | final List imgUrlsArray; 8 | final ProdManufacturerModel manufacturerDetailMap; 9 | final ProdPriceModel priceMap; 10 | int selectedSizeIndex; 11 | final ProdSizesDetailModel sizesmap; 12 | final String specialMsg; 13 | 14 | ProductModel({ 15 | @required this.customerReviewsCount, 16 | @required this.descriptionMap, 17 | @required this.id, 18 | @required this.imgUrlsArray, 19 | @required this.manufacturerDetailMap, 20 | @required this.priceMap, 21 | this.selectedSizeIndex = 0, 22 | @required this.sizesmap, 23 | @required this.specialMsg, 24 | }); 25 | } 26 | 27 | class ProdDescriptionModel { 28 | final String long; 29 | final String materialAndCare; 30 | final String short1; 31 | final String short2; 32 | final String sizeAndFit; 33 | 34 | ProdDescriptionModel({ 35 | @required this.long, 36 | @required this.materialAndCare, 37 | @required this.short1, 38 | @required this.short2, 39 | @required this.sizeAndFit, 40 | }); 41 | } 42 | 43 | class ProdPriceModel { 44 | final int discountAmount; 45 | final int discountMaxAmountViaPercentage; 46 | final int discountPercentage; 47 | final int discountType; 48 | final int price; 49 | 50 | ProdPriceModel({ 51 | @required this.discountAmount, 52 | @required this.discountMaxAmountViaPercentage, 53 | @required this.discountPercentage, 54 | @required this.discountType, 55 | @required this.price, 56 | }); 57 | } 58 | 59 | class ProdManufacturerModel { 60 | final String brandName; 61 | final String brandId; 62 | final String id; 63 | final String name; 64 | final String prodCode; 65 | 66 | ProdManufacturerModel({ 67 | @required this.brandId, 68 | @required this.brandName, 69 | @required this.id, 70 | @required this.name, 71 | @required this.prodCode, 72 | }); 73 | } 74 | 75 | class ProdSizesDetailModel { 76 | final List list; 77 | final String specialMsg; 78 | 79 | ProdSizesDetailModel({ 80 | @required this.list, 81 | @required this.specialMsg, 82 | }); 83 | } 84 | 85 | class ProdSizeModel { 86 | final int id; 87 | final String name; 88 | final int remainingQty; 89 | 90 | ProdSizeModel({ 91 | @required this.id, 92 | @required this.name, 93 | @required this.remainingQty, 94 | }); 95 | } 96 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/item_tiles/item_size_grid_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/models/product_model.dart'; 3 | 4 | class ProductSizeGridTile extends StatelessWidget { 5 | final ProdSizeModel prodSize; 6 | final bool isSelected; 7 | final bool isSelectable; 8 | final Function newSizeSelected; 9 | 10 | final double tileWidth = 55; 11 | 12 | ProductSizeGridTile({ 13 | @required this.prodSize, 14 | this.isSelected = false, 15 | this.isSelectable = false, 16 | this.newSizeSelected, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return SizedBox( 22 | width: tileWidth, 23 | child: Column( 24 | children: [ 25 | GestureDetector( 26 | onTap: isSelectable ? () => newSizeSelected(prodSize) : null, 27 | child: Container( 28 | margin: EdgeInsets.symmetric(horizontal: 5, vertical: 5), 29 | decoration: BoxDecoration( 30 | // color: Colors.t, 31 | borderRadius: BorderRadius.all( 32 | Radius.circular((tileWidth - 10) / 2), 33 | ), 34 | border: Border.all( 35 | color: isSelected ? Colors.red : Colors.black, 36 | width: 0.8, 37 | ), 38 | ), 39 | alignment: Alignment.center, 40 | width: tileWidth - 10, 41 | height: tileWidth - 10, 42 | child: Text( 43 | prodSize.name ?? '', 44 | style: TextStyle( 45 | color: isSelected ? Colors.red : Colors.black, 46 | ), 47 | ), 48 | ), 49 | ), 50 | if (prodSize.remainingQty < 10) 51 | Container( 52 | decoration: BoxDecoration( 53 | borderRadius: BorderRadius.all( 54 | Radius.circular(1.5), 55 | ), 56 | border: Border.all( 57 | color: Colors.pink, 58 | width: 1, 59 | ), 60 | ), 61 | padding: EdgeInsets.symmetric(horizontal: 5, vertical: 3), 62 | // color: Colors.yellow, 63 | child: Text( 64 | '${prodSize.remainingQty} left', 65 | style: TextStyle(color: Colors.pink, fontSize: 8), 66 | ), 67 | ), 68 | Spacer(), 69 | ], 70 | ), 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/account_summary_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 4 | 5 | class AccountSummaryListTile extends StatelessWidget { 6 | final Widget icon; 7 | final String title; 8 | final String summary; 9 | final MenuOptions menuOption; 10 | final bool isShowDivider; 11 | final Function goToScreen; 12 | 13 | AccountSummaryListTile( 14 | {@required this.icon, 15 | @required this.title, 16 | @required this.summary, 17 | this.menuOption, 18 | @required this.goToScreen, 19 | this.isShowDivider = true}); 20 | 21 | final double tileHeight = 80; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return InkWell( 26 | onTap: menuOption != null ? () => goToScreen(menuOption) : () {}, 27 | child: Column( 28 | children: [ 29 | Container( 30 | height: tileHeight, 31 | child: Row( 32 | // mainAxisAlignment: MainAxisAlignment.spaceAround, 33 | children: [ 34 | Container( 35 | width: tileHeight - 20, 36 | padding: const EdgeInsets.symmetric( 37 | horizontal: 10.0, 38 | vertical: 25, 39 | ), 40 | child: icon, 41 | ), 42 | Expanded( 43 | child: Column( 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | mainAxisAlignment: MainAxisAlignment.center, 46 | children: [ 47 | Text( 48 | title, 49 | style: TextStyle( 50 | color: Colors.black, 51 | fontWeight: FontWeight.w400, 52 | fontSize: 15), 53 | ), 54 | SizedBox( 55 | height: 5, 56 | ), 57 | Text( 58 | summary, 59 | style: TextStyle( 60 | color: Colors.grey[600], 61 | fontSize: 10, 62 | fontWeight: FontWeight.w300), 63 | ), 64 | ], 65 | ), 66 | ), 67 | Container( 68 | width: tileHeight, 69 | padding: const EdgeInsets.all(20.0), 70 | child: Icon( 71 | Icons.chevron_right, 72 | color: Colors.grey[400], 73 | ), 74 | ), 75 | ], 76 | ), 77 | ), 78 | if (isShowDivider) 79 | Container( 80 | height: 0.1, 81 | color: Colors.grey, 82 | ), 83 | ], 84 | ), 85 | ); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/src/ui/widget/custom_default_text_form_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | 4 | class CustomDefaultTextFormField extends StatelessWidget { 5 | final String placeHolderString; 6 | final TextEditingController textController; 7 | final FocusNode ownFocusNode; 8 | final FocusNode nextFocusNode; 9 | final TextInputType textInputType; 10 | final TextInputAction textInputAction; 11 | final int maxTextCount; 12 | final bool isObscureText; 13 | Function validationFunc; 14 | 15 | CustomDefaultTextFormField({ 16 | this.placeHolderString = '', 17 | this.textController, 18 | this.ownFocusNode, 19 | this.nextFocusNode, 20 | this.textInputType = TextInputType.text, 21 | this.textInputAction = TextInputAction.next, 22 | this.maxTextCount, 23 | this.isObscureText = false, 24 | this.validationFunc, 25 | }); 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return TextFormField( 30 | decoration: InputDecoration( 31 | counterText: '', 32 | contentPadding: EdgeInsets.symmetric( 33 | horizontal: 5, 34 | vertical: 2, 35 | ), 36 | enabledBorder: UnderlineInputBorder( 37 | borderSide: BorderSide(color: Colors.grey[800], width: 1), 38 | borderRadius: BorderRadius.circular(5), 39 | ), 40 | focusedBorder: UnderlineInputBorder( 41 | borderSide: BorderSide(color: Colors.red, width: 1), 42 | borderRadius: BorderRadius.circular(5), 43 | ), 44 | errorBorder: UnderlineInputBorder( 45 | borderSide: BorderSide(color: Colors.grey[800], width: 1), 46 | borderRadius: BorderRadius.circular(5), 47 | ), 48 | focusedErrorBorder: UnderlineInputBorder( 49 | borderSide: BorderSide(color: Colors.grey[800], width: 1), 50 | borderRadius: BorderRadius.circular(5), 51 | ), 52 | errorStyle: TextStyle( 53 | color: Colors.red, 54 | fontSize: 11, 55 | fontWeight: FontWeight.w400, 56 | ), 57 | labelText: placeHolderString, 58 | labelStyle: TextStyle( 59 | color: Colors.grey[800], 60 | fontSize: 13, 61 | fontWeight: FontWeight.w400, 62 | ), 63 | ), 64 | style: TextStyle( 65 | fontSize: 13, 66 | ), 67 | obscureText: isObscureText, 68 | cursorColor: AppColors.silver, 69 | cursorWidth: 0.5, 70 | textInputAction: textInputAction, 71 | controller: textController, 72 | focusNode: ownFocusNode, 73 | keyboardType: textInputType, 74 | maxLength: maxTextCount, 75 | onFieldSubmitted: (_) { 76 | if (nextFocusNode != null) { 77 | FocusScope.of(context).requestFocus(nextFocusNode); 78 | } else { 79 | FocusScope.of(context).unfocus(); 80 | } 81 | }, 82 | validator: validationFunc, 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/core/helpers/common_constants.dart: -------------------------------------------------------------------------------- 1 | import 'dart:core'; 2 | 3 | import 'package:myntra_test_app/src/core/extensions/color_extension.dart'; 4 | 5 | enum CustomAppBarLeftButtonsTypes { 6 | Back, 7 | Cross, 8 | Menu, 9 | } 10 | 11 | enum DiscountTypes { Amount, None, Percentage } 12 | 13 | enum GenderTypes { 14 | Female, 15 | Male, 16 | None, 17 | } 18 | 19 | enum MenuOptions { 20 | Account, 21 | None, 22 | } 23 | 24 | class AppColors { 25 | static final cabaret = HexColor('#DA4C6E'); 26 | static final ocean_green = HexColor('#46A786'); 27 | static final silver = HexColor('#BDBDBD'); 28 | } 29 | 30 | class AppConstants { 31 | static const preferredAppBarHeight = 50.0; 32 | } 33 | 34 | class AppIcons { 35 | static const account_black = 'assets/icons/icon_account_black.png'; 36 | static const bell_black = 'assets/icons/icon_bell_black.png'; 37 | static const bookmark_black = 'assets/icons/icon_bookmark_black.png'; 38 | static const box_black = 'assets/icons/icon_box_black.png'; 39 | static const cosmetics_black = 'assets/icons/icon_cosmetics_black.png'; 40 | static const cross_black = 'assets/icons/icon_cross_black.png'; 41 | static const down_arrow = 'assets/icons/icon_down_arrow.png'; 42 | static const flutter = 'assets/icons/icon_flutter.png'; 43 | static const home_black = 'assets/icons/icon_home_black.png'; 44 | static const kid_cloth_black = 'assets/icons/icon_kid_cloth_black.png'; 45 | static const left_arrow_black = 'assets/icons/icon_left_arrow_black.png'; 46 | static const men_dress = 'assets/icons/icon_men_dress.png'; 47 | static const menu_black = 'assets/icons/icon_menu_black.png'; 48 | static const portfolio_black = 'assets/icons/icon_portfolio_black.png'; 49 | static const right_direct_black = 'assets/icons/icon_right_direct_black.png'; 50 | static const shopping_bag_black = 'assets/icons/icon_shopping_bag.png'; 51 | static const winter_cloth_black = 'assets/icons/icon_winter_cloth_black.png'; 52 | static const women_dress = 'assets/icons/icon_women_dress.png'; 53 | static const UserBlack = 'assets/icons/user_black.png'; 54 | 55 | static const Facebook = 'assets/icons/social/facebook.png'; 56 | static const Google = 'assets/icons/social/google.png'; 57 | static const MailBlack = 'assets/icons/social/mail_black.png'; 58 | static const PhoneBlack = 'assets/icons/social/phone_black.png'; 59 | } 60 | 61 | class AppImages { 62 | static const banner1 = 'assets/images/img_banner1.jpg'; 63 | static const banner2 = 'assets/images/img_banner2.jpg'; 64 | static const emptyBox = 'assets/images/img_empty_box.png'; 65 | static const free_delivery = 'assets/images/img_free_delivery.png'; 66 | static const kids = 'assets/images/img_kid.jpg'; 67 | static const lowest_price = 'assets/images/img_lowest_price.jpg'; 68 | static const men = 'assets/images/img_men.png'; 69 | static const user = 'assets/images/img_user.jpg'; 70 | static const women = 'assets/images/img_women.png'; 71 | } 72 | 73 | class AppStrings { 74 | static const Cancel = 'Cancel'; 75 | static const No = 'No'; 76 | static const Ok = 'Ok'; 77 | static const RupeeSign = '₹'; 78 | static const Yes = 'Yes'; 79 | } 80 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 69 | 71 | 77 | 78 | 79 | 80 | 82 | 83 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /lib/src/ui/widget/app_drawer_profile_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/prefs_constants.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 6 | import 'package:myntra_test_app/src/core/view_model/auth_provider.dart'; 7 | 8 | class AppDrawerProfileView extends StatefulWidget { 9 | @override 10 | _AppDrawerProfileViewState createState() => _AppDrawerProfileViewState(); 11 | } 12 | 13 | class _AppDrawerProfileViewState extends State { 14 | String _name = 'Login - Sign up'; 15 | 16 | _loadUserPrefs() async { 17 | final userPrefsMap = await FSPrefs.getUserPrefsMap(); 18 | setState(() { 19 | _name = userPrefsMap[FSPrefs.UserName]; 20 | }); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final authProvider = Provider.of(context, listen: false); 26 | if (authProvider.isAuthorized) { 27 | _loadUserPrefs(); 28 | } 29 | // final titleString = 30 | // authProvider.isAuthorized ? 'Welcome' : 'Login - Sign up'; 31 | 32 | return Container( 33 | height: 140, 34 | decoration: BoxDecoration( 35 | gradient: LinearGradient( 36 | begin: Alignment.topRight, 37 | end: Alignment.bottomLeft, 38 | colors: [Colors.blue, Colors.red], 39 | ), 40 | ), 41 | // color: Colors.black45, 42 | child: Stack( 43 | children: [ 44 | Positioned( 45 | right: 0, 46 | left: 0, 47 | bottom: 0, 48 | child: Container( 49 | padding: EdgeInsets.symmetric( 50 | horizontal: 15, 51 | vertical: 10, 52 | ), 53 | child: Column( 54 | crossAxisAlignment: CrossAxisAlignment.start, 55 | children: [ 56 | Container( 57 | child: Container( 58 | height: 70, 59 | padding: authProvider.isAuthorized 60 | ? EdgeInsets.all(15) 61 | : EdgeInsets.all(15), 62 | decoration: BoxDecoration( 63 | borderRadius: BorderRadius.circular(7), 64 | border: Border.all( 65 | color: Colors.white, 66 | width: 2, 67 | ), 68 | color: Colors.grey[300], 69 | ), 70 | child: Image.asset(AppIcons.UserBlack), 71 | ), 72 | ), 73 | SizedBox( 74 | height: 10, 75 | ), 76 | Row( 77 | children: [ 78 | Text( 79 | _name, 80 | style: TextStyle( 81 | color: Colors.white, 82 | ), 83 | ), 84 | Spacer(), 85 | Icon( 86 | Icons.chevron_right, 87 | color: Colors.white, 88 | ), 89 | ], 90 | ), 91 | ], 92 | ), 93 | ), 94 | ) 95 | ], 96 | ), 97 | ); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/src/ui/widget/custom_app_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | 4 | class CustomAppBar extends StatelessWidget { 5 | final CustomAppBarLeftButtonsTypes leftButton; 6 | final MaterialPageRoute popRoute; 7 | final String title; 8 | final bool isShowDivider; 9 | final List rightWidgets; 10 | 11 | CustomAppBar({ 12 | this.leftButton = CustomAppBarLeftButtonsTypes.Back, 13 | this.popRoute, 14 | @required this.title, 15 | this.rightWidgets, 16 | this.isShowDivider = true, 17 | }); 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return SafeArea( 22 | child: Container( 23 | // color: Colors.white, 24 | height: 100, 25 | child: Column( 26 | mainAxisAlignment: MainAxisAlignment.end, 27 | children: [ 28 | Expanded( 29 | child: Container( 30 | // color: Colors.yellow, 31 | padding: 32 | const EdgeInsets.symmetric(horizontal: 10, vertical: 5), 33 | child: Row( 34 | children: [ 35 | buildLeftIconButton(context), 36 | SizedBox( 37 | width: 10, 38 | ), 39 | Text( 40 | title, 41 | style: TextStyle( 42 | fontWeight: FontWeight.w600, 43 | fontSize: 14, 44 | ), 45 | ), 46 | Spacer(), 47 | if (rightWidgets != null) ...rightWidgets 48 | ], 49 | ), 50 | ), 51 | ), 52 | Container( 53 | height: 0.3, 54 | color: AppColors.silver, 55 | ), 56 | ], 57 | ), 58 | ), 59 | ); 60 | } 61 | 62 | Widget buildLeftIconButton(BuildContext context) { 63 | switch (leftButton) { 64 | case CustomAppBarLeftButtonsTypes.Back: 65 | return Container( 66 | width: 30, 67 | child: IconButton( 68 | padding: EdgeInsets.symmetric( 69 | vertical: 10, 70 | ), 71 | icon: Image( 72 | image: AssetImage( 73 | AppIcons.left_arrow_black, 74 | ), 75 | fit: BoxFit.cover, 76 | ), 77 | onPressed: () { 78 | if (popRoute != null) { 79 | Navigator.popUntil(context, (popRoute) => true); 80 | } else { 81 | Navigator.of(context).pop(); 82 | } 83 | }, 84 | ), 85 | ); 86 | break; 87 | 88 | // case CustomAppBarLeftButtons.Cross: 89 | 90 | case CustomAppBarLeftButtonsTypes.Menu: 91 | return Container( 92 | width: 30, 93 | child: IconButton( 94 | padding: EdgeInsets.symmetric( 95 | vertical: 10, 96 | ), 97 | icon: Image( 98 | image: AssetImage( 99 | AppIcons.menu_black, 100 | ), 101 | fit: BoxFit.cover, 102 | ), 103 | onPressed: () { 104 | Scaffold.of(context).openDrawer(); 105 | }, 106 | ), 107 | ); 108 | 109 | default: 110 | return Container(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/ui/widget/bottom_sheets/select_qty_bottom_sheet.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:myntra_test_app/widgets/tiles/item_tiles/item_qty_grid_tile.dart'; 4 | 5 | class SelectQtyBottomSheet extends StatefulWidget { 6 | final int selectedQty; 7 | final Function doneBtnClickedWithNewQty; 8 | 9 | SelectQtyBottomSheet( 10 | {@required this.selectedQty, @required this.doneBtnClickedWithNewQty}); 11 | 12 | @override 13 | _SelectQtyBottomSheetState createState() => _SelectQtyBottomSheetState(); 14 | } 15 | 16 | class _SelectQtyBottomSheetState extends State { 17 | final qtyArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 18 | 19 | int _newSelectedQty; 20 | 21 | void newQtySelected(int newSize) { 22 | setState(() { 23 | _newSelectedQty = newSize; 24 | }); 25 | } 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return SingleChildScrollView( 30 | child: SafeArea( 31 | child: Container( 32 | padding: EdgeInsets.all(15), 33 | color: Colors.white, 34 | child: Column( 35 | crossAxisAlignment: CrossAxisAlignment.start, 36 | mainAxisAlignment: MainAxisAlignment.end, 37 | children: [ 38 | Row( 39 | children: [ 40 | Text( 41 | 'Select Quantity', 42 | // style: TextStyle(color: Colors.grey), 43 | ), 44 | Spacer(), 45 | GestureDetector( 46 | onTap: () => Navigator.of(context).pop(), 47 | child: Icon(Icons.close)), 48 | ], 49 | ), 50 | SizedBox( 51 | height: 10, 52 | ), 53 | Container( 54 | height: 55, 55 | // color: Colors.green, 56 | child: ListView.builder( 57 | itemBuilder: (ctx, index) => ItemQtyGridTile( 58 | itemQty: qtyArray[index], 59 | isSelected: _newSelectedQty != null 60 | ? qtyArray[index] == _newSelectedQty 61 | : qtyArray[index] == widget.selectedQty, 62 | isSelectable: true, 63 | newQtySelected: newQtySelected, 64 | ), 65 | itemCount: qtyArray.length, 66 | scrollDirection: Axis.horizontal, 67 | ), 68 | ), 69 | SizedBox( 70 | height: 15, 71 | ), 72 | Container( 73 | height: 50, 74 | width: double.infinity, 75 | color: Colors.white, 76 | child: FlatButton( 77 | onPressed: () { 78 | widget.doneBtnClickedWithNewQty(_newSelectedQty != null 79 | ? _newSelectedQty 80 | : widget.selectedQty); 81 | Navigator.of(context).pop(); 82 | }, 83 | child: Text( 84 | 'DONE', 85 | style: TextStyle( 86 | color: Colors.white, 87 | fontWeight: FontWeight.w700, 88 | // fontSize: 11, 89 | ), 90 | ), 91 | color: Colors.pink, 92 | ), 93 | ) 94 | ], 95 | ), 96 | ), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | use_frameworks! 37 | use_modular_headers! 38 | 39 | # Flutter Pod 40 | 41 | copied_flutter_dir = File.join(__dir__, 'Flutter') 42 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 43 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 44 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 45 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 46 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 47 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 48 | 49 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 50 | unless File.exist?(generated_xcode_build_settings_path) 51 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 52 | end 53 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 54 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 55 | 56 | unless File.exist?(copied_framework_path) 57 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 58 | end 59 | unless File.exist?(copied_podspec_path) 60 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 61 | end 62 | end 63 | 64 | # Keep pod path relative so it can be checked into Podfile.lock. 65 | pod 'Flutter', :path => 'Flutter' 66 | 67 | # Plugin Pods 68 | 69 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 70 | # referring to absolute paths on developers' machines. 71 | system('rm -rf .symlinks') 72 | system('mkdir -p .symlinks/plugins') 73 | plugin_pods = parse_KV_file('../.flutter-plugins') 74 | plugin_pods.each do |name, path| 75 | symlink = File.join('.symlinks', 'plugins', name) 76 | File.symlink(path, symlink) 77 | pod name, :path => File.join(symlink, 'ios') 78 | end 79 | end 80 | 81 | # Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system. 82 | install! 'cocoapods', :disable_input_output_paths => true 83 | 84 | post_install do |installer| 85 | installer.pods_project.targets.each do |target| 86 | target.build_configurations.each do |config| 87 | config.build_settings['ENABLE_BITCODE'] = 'NO' 88 | end 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/src/ui/view/orders_list_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 4 | import 'package:myntra_test_app/src/ui/view/home_screen.dart'; 5 | import 'package:myntra_test_app/src/ui/widget/custom_app_bar.dart'; 6 | 7 | class OrdersListScreen extends StatelessWidget { 8 | static const routeName = '/order-list'; 9 | 10 | final ordersArray = []; 11 | 12 | @override 13 | Widget build(BuildContext context) { 14 | return Scaffold( 15 | appBar: PreferredSize( 16 | child: CustomAppBar( 17 | title: 'ORDERS', 18 | ), 19 | preferredSize: Size(null, AppConstants.preferredAppBarHeight), 20 | ), 21 | body: SafeArea( 22 | child: Column( 23 | children: [ 24 | SizedBox( 25 | height: 20, 26 | child: Container( 27 | color: Colors.grey[100], 28 | ), 29 | ), 30 | ordersArray.length == 0 31 | ? Container( 32 | width: double.infinity, 33 | color: Colors.white, 34 | padding: EdgeInsets.symmetric(vertical: 30, horizontal: 20), 35 | child: Column( 36 | children: [ 37 | Text( 38 | 'NO ACTIVE ORDERS', 39 | style: TextStyle( 40 | color: Colors.grey[600], 41 | fontSize: 16, 42 | fontWeight: FontWeight.w500), 43 | ), 44 | SizedBox( 45 | height: 20, 46 | ), 47 | Text( 48 | 'There are no recent orders to show.', 49 | style: TextStyle( 50 | color: Colors.grey[500], 51 | fontSize: 14, 52 | ), 53 | ), 54 | SizedBox( 55 | height: 20, 56 | ), 57 | Container( 58 | child: Image.asset('assets/images/img_empty_box.png'), 59 | height: 220, 60 | ), 61 | Container( 62 | padding: EdgeInsets.all(20), 63 | // width: double.infinity, 64 | height: 90, 65 | // color: Colors.grey[200], 66 | child: OutlineButton( 67 | child: Text(' START SHOPPING '), 68 | onPressed: () { 69 | Navigator.of(context).pushNamedAndRemoveUntil( 70 | HomeScreen.routeName, 71 | (Route route) => false); 72 | }, 73 | borderSide: BorderSide(color: Colors.grey), 74 | shape: RoundedRectangleBorder( 75 | borderRadius: BorderRadius.circular(5.0), 76 | ), 77 | textColor: Colors.blueAccent, 78 | ), 79 | ) 80 | ], 81 | ), 82 | ) 83 | : Expanded( 84 | child: ListView.builder( 85 | itemBuilder: (ctx, index) => null, 86 | itemCount: 0, 87 | ), 88 | ), 89 | ], 90 | ), 91 | ), 92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/app_drawer/app_drawer_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 4 | import 'package:myntra_test_app/src/core/helpers/helper.dart'; 5 | import 'package:myntra_test_app/src/core/models/common_models.dart'; 6 | 7 | class AppDrawerListTile extends StatelessWidget { 8 | final MenuOptions menuOption; 9 | final Color titleColor; 10 | final FontWeight titleWeight; 11 | final Widget leadItem; 12 | final Widget trailingItem; 13 | 14 | Function menuOptionViewTapped; 15 | 16 | AppDrawerListTile({ 17 | @required this.menuOption, 18 | this.titleColor = Colors.black, 19 | this.titleWeight = FontWeight.w500, 20 | @required this.leadItem, 21 | this.trailingItem, 22 | @required this.menuOptionViewTapped, 23 | }); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return InkWell( 28 | onTap: () => menuOptionViewTapped(menuOption), 29 | child: Container( 30 | padding: EdgeInsets.symmetric( 31 | horizontal: 15, 32 | vertical: 10, 33 | ), 34 | child: Row( 35 | children: [ 36 | Container( 37 | // color: Colors.green, 38 | // padding: EdgeInsets.symmetric(horizontal: 5,), 39 | height: 30, 40 | width: 20, 41 | alignment: Alignment.center, 42 | child: leadItem, 43 | ), 44 | SizedBox( 45 | width: 20, 46 | ), 47 | Text( 48 | Helper.getMenuOptionValue(menuOption), 49 | style: TextStyle( 50 | color: titleColor, 51 | fontWeight: titleWeight, 52 | fontSize: 13, 53 | ), 54 | ), 55 | Spacer(), 56 | if (trailingItem != null) trailingItem, 57 | ], 58 | ), 59 | ), 60 | ); 61 | } 62 | } 63 | 64 | class AppDrawerCategoryListTile extends StatelessWidget { 65 | final CategoryModal category; 66 | final Color titleColor; 67 | final FontWeight titleWeight; 68 | final Widget leadItem; 69 | final Widget trailingItem; 70 | 71 | Function categoryViewTapped; 72 | 73 | AppDrawerCategoryListTile({ 74 | @required this.category, 75 | this.titleColor = Colors.black, 76 | this.titleWeight = FontWeight.w500, 77 | @required this.leadItem, 78 | this.trailingItem, 79 | @required this.categoryViewTapped, 80 | }); 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return InkWell( 85 | onTap: () => categoryViewTapped(category), 86 | child: Container( 87 | padding: EdgeInsets.symmetric( 88 | horizontal: 15, 89 | vertical: 10, 90 | ), 91 | child: Row( 92 | children: [ 93 | Container( 94 | // color: Colors.green, 95 | // padding: EdgeInsets.symmetric(horizontal: 5,), 96 | height: 30, 97 | width: 20, 98 | alignment: Alignment.center, 99 | child: leadItem, 100 | ), 101 | SizedBox( 102 | width: 20, 103 | ), 104 | Text( 105 | category.name, 106 | // Helper.getMenuOptionValue(menuOption), 107 | style: TextStyle( 108 | color: titleColor, 109 | fontWeight: titleWeight, 110 | fontSize: 13, 111 | ), 112 | ), 113 | Spacer(), 114 | if (trailingItem != null) trailingItem, 115 | ], 116 | ), 117 | ), 118 | ); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/core/helpers/alert_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 7 | 8 | class AlertActionModel { 9 | final String title; 10 | Function action; 11 | bool isDestructiveAction; 12 | 13 | AlertActionModel({ 14 | @required this.title, 15 | @required this.action, 16 | this.isDestructiveAction = false, 17 | }); 18 | } 19 | 20 | class AlertHelper { 21 | static void showOnlyOkAlertDialog({ 22 | @required BuildContext ctx, 23 | @required String msg, 24 | }) { 25 | showDialog( 26 | barrierDismissible: false, 27 | context: ctx, 28 | builder: (ctx) => Platform.isIOS 29 | ? CupertinoAlertDialog( 30 | content: Text(msg), 31 | actions: [ 32 | CupertinoDialogAction( 33 | child: Text("Ok"), 34 | onPressed: () { 35 | Navigator.of(ctx).pop(); 36 | }, 37 | ), 38 | ], 39 | ) 40 | : AlertDialog( 41 | content: Text(msg), 42 | actions: [ 43 | FlatButton( 44 | child: Text( 45 | AppStrings.Ok, 46 | style: TextStyle( 47 | color: Colors.black, 48 | ), 49 | ), 50 | onPressed: () { 51 | Navigator.of(ctx).pop(); 52 | }, 53 | ), 54 | ], 55 | ), 56 | ); 57 | } 58 | 59 | static void showAlertDialogWithMultipleActions({ 60 | @required BuildContext ctx, 61 | @required String msg, 62 | @required List actions, 63 | bool doAddCancelAction = false, 64 | }) { 65 | if (doAddCancelAction) { 66 | actions.add( 67 | AlertActionModel( 68 | title: AppStrings.Cancel, 69 | action: () {}, 70 | isDestructiveAction: true, 71 | ), 72 | ); 73 | } 74 | showDialog( 75 | barrierDismissible: false, 76 | context: ctx, 77 | builder: (ctx) => Platform.isIOS 78 | ? CupertinoAlertDialog( 79 | content: Text(msg), 80 | actions: actions 81 | .map((action) => CupertinoDialogAction( 82 | isDestructiveAction: action.isDestructiveAction, 83 | child: Text(action.title), 84 | onPressed: () { 85 | Navigator.of(ctx).pop(); 86 | action.action(); 87 | }, 88 | )) 89 | .toList(), 90 | ) 91 | : SimpleDialog( 92 | title: Text( 93 | msg, 94 | textAlign: TextAlign.center, 95 | ), 96 | children: actions 97 | .map((action) => FlatButton( 98 | child: Text( 99 | action.title, 100 | style: TextStyle( 101 | color: action.isDestructiveAction 102 | ? Colors.red 103 | : Colors.black, 104 | ), 105 | ), 106 | onPressed: () { 107 | Navigator.of(ctx).pop(); 108 | action.action(); 109 | }, 110 | )) 111 | .toList(), 112 | ), 113 | ); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/src/ui/widget/item_detail_floating_bottom_bar.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:myntra_test_app/src/core/models/product_model.dart'; 6 | import 'package:myntra_test_app/src/core/view_model/product_provider.dart'; 7 | 8 | class ItemDetailFloatingBottomBar extends StatefulWidget { 9 | @override 10 | _ItemDetailFloatingBottomBarState createState() => 11 | _ItemDetailFloatingBottomBarState(); 12 | 13 | final ProductModel product; 14 | 15 | ItemDetailFloatingBottomBar({ 16 | @required this.product, 17 | }); 18 | } 19 | 20 | class _ItemDetailFloatingBottomBarState 21 | extends State { 22 | var _isWishListLoading = false; 23 | var _isCartLoading = false; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final prodProvider = Provider.of(context, listen: false); 28 | void wishListBtnClicked() async { 29 | setState(() { 30 | _isWishListLoading = true; 31 | }); 32 | 33 | final status = 34 | await prodProvider.addProdToWishListWith(prodID: widget.product.id); 35 | setState(() { 36 | _isWishListLoading = false; 37 | }); 38 | } 39 | 40 | void addToCartBtnClicked() async { 41 | setState(() { 42 | _isCartLoading = true; 43 | }); 44 | final status = 45 | await prodProvider.addProdToCartListWith(prodID: widget.product.id); 46 | setState(() { 47 | _isCartLoading = false; 48 | }); 49 | } 50 | 51 | _buildWishListLoader() { 52 | return Container( 53 | decoration: BoxDecoration( 54 | border: Border.all( 55 | width: 0.5, 56 | color: Colors.grey, 57 | ), 58 | ), 59 | padding: EdgeInsets.all( 60 | 5, 61 | ), 62 | child: Center( 63 | child: CircularProgressIndicator( 64 | backgroundColor: AppColors.cabaret, 65 | ), 66 | ), 67 | ); 68 | } 69 | 70 | return Container( 71 | height: 50, 72 | color: Colors.white, 73 | child: Row( 74 | crossAxisAlignment: CrossAxisAlignment.stretch, 75 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 76 | children: [ 77 | Expanded( 78 | flex: 9, 79 | child: _isWishListLoading 80 | ? _buildWishListLoader() 81 | : OutlineButton.icon( 82 | onPressed: wishListBtnClicked, 83 | icon: Icon(Icons.bookmark), 84 | label: const Text( 85 | 'ADD TO WISHLIST', 86 | style: TextStyle(fontSize: 10), 87 | ), 88 | ), 89 | ), 90 | SizedBox( 91 | width: 10, 92 | ), 93 | Expanded( 94 | flex: 11, 95 | child: _isCartLoading 96 | ? _buildWishListLoader() 97 | : FlatButton.icon( 98 | onPressed: addToCartBtnClicked, 99 | icon: Icon( 100 | Icons.shopping_cart, 101 | color: Colors.white, 102 | ), 103 | label: Text( 104 | 'ADD TO BAG', 105 | style: TextStyle( 106 | color: Colors.white, 107 | fontSize: 10, 108 | ), 109 | ), 110 | color: Colors.pink, 111 | ), 112 | ), 113 | ], 114 | ), 115 | ); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /android/.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | xmlns:android 14 | 15 | ^$ 16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 | xmlns:.* 25 | 26 | ^$ 27 | 28 | 29 | BY_NAME 30 | 31 |
32 |
33 | 34 | 35 | 36 | .*:id 37 | 38 | http://schemas.android.com/apk/res/android 39 | 40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | .*:name 48 | 49 | http://schemas.android.com/apk/res/android 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | name 59 | 60 | ^$ 61 | 62 | 63 | 64 |
65 |
66 | 67 | 68 | 69 | style 70 | 71 | ^$ 72 | 73 | 74 | 75 |
76 |
77 | 78 | 79 | 80 | .* 81 | 82 | ^$ 83 | 84 | 85 | BY_NAME 86 | 87 |
88 |
89 | 90 | 91 | 92 | .* 93 | 94 | http://schemas.android.com/apk/res/android 95 | 96 | 97 | ANDROID_ATTRIBUTE_ORDER 98 | 99 |
100 |
101 | 102 | 103 | 104 | .* 105 | 106 | .* 107 | 108 | 109 | BY_NAME 110 | 111 |
112 |
113 |
114 |
115 |
116 |
-------------------------------------------------------------------------------- /android/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /lib/src/ui/view/account_summary/account_summary_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import 'package:myntra_test_app/src/core/helpers/alert_helper.dart'; 5 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 6 | import 'package:myntra_test_app/src/core/view_model/auth_provider.dart'; 7 | import 'package:myntra_test_app/src/ui/view/account_summary/account_summary_top_view.dart'; 8 | import 'package:myntra_test_app/src/ui/view/home_screen.dart'; 9 | import 'package:myntra_test_app/src/ui/widget/custom_app_bar.dart'; 10 | 11 | class AccountSummaryScreen extends StatelessWidget { 12 | final isComingAfterSignUp; 13 | 14 | AccountSummaryScreen({this.isComingAfterSignUp = false}); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | final authProvider = Provider.of(context, listen: false); 19 | 20 | void _logOutBtnClicked() { 21 | AlertHelper.showAlertDialogWithMultipleActions( 22 | ctx: context, 23 | msg: 'Are you sure to logout?', 24 | actions: [ 25 | AlertActionModel( 26 | title: AppStrings.Yes, 27 | action: () { 28 | authProvider.logOut(); 29 | 30 | final route = MaterialPageRoute(builder: (ctx) => HomeScreen()); 31 | Navigator.push(context, route); 32 | }, 33 | ), 34 | AlertActionModel( 35 | title: AppStrings.No, 36 | action: () {}, 37 | ), 38 | ], 39 | ); 40 | } 41 | 42 | return Scaffold( 43 | appBar: PreferredSize( 44 | preferredSize: Size(null, AppConstants.preferredAppBarHeight), 45 | child: CustomAppBar( 46 | title: '', 47 | // leftButton: isComingAfterSignUp 48 | // ? CustomAppBarLeftButtonsTypes.Menu 49 | // : CustomAppBarLeftButtonsTypes.Back, 50 | popRoute: isComingAfterSignUp 51 | ? MaterialPageRoute(builder: (ctx) => HomeScreen()) 52 | : null, 53 | ), 54 | ), 55 | body: SafeArea( 56 | child: SingleChildScrollView( 57 | physics: const ClampingScrollPhysics(), 58 | child: Column( 59 | children: [ 60 | AccountSummaryTopView(), 61 | SizedBox( 62 | height: 20, 63 | child: Container( 64 | color: Colors.grey[200], 65 | ), 66 | ), 67 | SizedBox( 68 | height: 20, 69 | child: Container( 70 | color: Colors.grey[200], 71 | ), 72 | ), 73 | SizedBox( 74 | height: 20, 75 | child: Container( 76 | color: Colors.grey[200], 77 | ), 78 | ), 79 | Container( 80 | padding: EdgeInsets.all(20), 81 | width: double.infinity, 82 | height: 90, 83 | color: Colors.grey[200], 84 | child: OutlineButton( 85 | child: Text( 86 | 'LOG OUT', 87 | style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600), 88 | ), 89 | onPressed: _logOutBtnClicked, 90 | borderSide: BorderSide(color: AppColors.cabaret), 91 | shape: RoundedRectangleBorder( 92 | borderRadius: BorderRadius.circular(3.0), 93 | ), 94 | textColor: AppColors.cabaret, 95 | ), 96 | ), 97 | ], 98 | ), 99 | ), 100 | ), 101 | // if (isComingAfterSignUp) 102 | // drawer: isComingAfterSignUp ? AppDrawer() : null, 103 | ); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/home_tiles/home_screen_hot_deals_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class HomeScreenHotDealsListTile extends StatelessWidget { 4 | final String imageName; 5 | final String heading; 6 | final String msg; 7 | final String title; 8 | 9 | HomeScreenHotDealsListTile( 10 | {@required this.imageName, 11 | @required this.heading, 12 | @required this.msg, 13 | @required this.title}); 14 | 15 | final double totalWidth = 350; 16 | final double gapWidth = 10; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | width: totalWidth, 22 | padding: EdgeInsets.symmetric(vertical: 5, horizontal: 2.5), 23 | child: Card( 24 | elevation: 2, 25 | child: Row( 26 | children: [ 27 | Container( 28 | // color: Colors.red, 29 | width: (totalWidth - gapWidth) / 2, 30 | child: Image.asset( 31 | imageName, 32 | fit: BoxFit.cover, 33 | height: double.infinity, 34 | ), 35 | ), 36 | SizedBox( 37 | width: gapWidth, 38 | ), 39 | Expanded( 40 | child: Padding( 41 | padding: EdgeInsets.all(5), 42 | // color: Colors.yellow, 43 | child: Column( 44 | crossAxisAlignment: CrossAxisAlignment.start, 45 | mainAxisAlignment: MainAxisAlignment.spaceAround, 46 | children: [ 47 | Column( 48 | crossAxisAlignment: CrossAxisAlignment.start, 49 | children: [ 50 | Container( 51 | height: 1, 52 | child: Container( 53 | width: 30, 54 | color: Colors.black, 55 | ), 56 | ), 57 | SizedBox( 58 | height: 10, 59 | ), 60 | Text( 61 | heading, 62 | style: TextStyle( 63 | color: Colors.grey, 64 | fontSize: 10, 65 | fontWeight: FontWeight.w500, 66 | ), 67 | ), 68 | ], 69 | ), 70 | Text( 71 | title, 72 | style: TextStyle( 73 | fontWeight: FontWeight.w600, 74 | ), 75 | ), 76 | Column( 77 | crossAxisAlignment: CrossAxisAlignment.start, 78 | children: [ 79 | Text( 80 | msg, 81 | style: TextStyle( 82 | color: Colors.grey, 83 | fontSize: 10, 84 | fontWeight: FontWeight.w500, 85 | ), 86 | ), 87 | SizedBox( 88 | height: 10, 89 | ), 90 | Container( 91 | height: 1, 92 | child: Container( 93 | width: 30, 94 | color: Colors.black, 95 | ), 96 | ), 97 | ], 98 | ), 99 | ], 100 | ), 101 | ), 102 | ), 103 | ], 104 | ), 105 | ), 106 | ); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/core/helpers/validators.dart: -------------------------------------------------------------------------------- 1 | class Validator { 2 | static String validateEmail(String value) { 3 | // print("validate email ///////////////////////////////////"); 4 | String pattern = 5 | r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'; 6 | RegExp regExp = new RegExp(pattern); 7 | if (value.length == 0) { 8 | return "Email is required!"; 9 | } else if (!regExp.hasMatch(value)) { 10 | return "Invalid email ID!"; 11 | } else { 12 | return null; 13 | } 14 | } 15 | 16 | static String validateEmpty(String value) { 17 | // print("validate empty ///////////////////////////////////"); 18 | if (value.length == 0) { 19 | return "Field can't be empty!"; 20 | } 21 | return null; 22 | } 23 | 24 | static String validateLoginPassword(String value) { 25 | String pattern = 26 | r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#\$&*~]).{8,}$'; 27 | RegExp regExp = new RegExp(pattern); 28 | if (value.length == 0) { 29 | return "Password is required!"; 30 | } else if (!regExp.hasMatch(value)) { 31 | return "At least 1 digit, special case, capital letter required!"; 32 | } else { 33 | return null; 34 | } 35 | } 36 | 37 | static String validateMobile(String value) { 38 | // print("Validate mobile ///////////////////////////////////"); 39 | String patttern = r'(^(?:[+0]9)?[0-9]{10,12}$)'; 40 | RegExp regExp = new RegExp(patttern); 41 | if (value.length == 0) { 42 | return 'Please enter mobile number!'; 43 | } else if (!regExp.hasMatch(value)) { 44 | return 'Please enter valid mobile number!'; 45 | } 46 | return null; 47 | } 48 | 49 | static String validateOptionalMobile(String value) { 50 | // print("Validate mobile ///////////////////////////////////"); 51 | String patttern = r'(^(?:[+0]9)?[0-9]{10,12}$)'; 52 | RegExp regExp = new RegExp(patttern); 53 | if (value.length == 0) { 54 | return null; 55 | } else if (!regExp.hasMatch(value)) { 56 | return 'Please enter valid mobile number!'; 57 | } 58 | return null; 59 | } 60 | 61 | String validatePassword(String value) { 62 | String upperCase = '(?=.*[A-Z])'; 63 | String lowerCase = '(?=.*[a-z])'; 64 | String digit = '(?=.*?[0-9])'; 65 | String specialChar = '(?=.*?[!@#\$&*~])'; 66 | String passwordLength = '.{8,}'; 67 | 68 | String pattern = 69 | r'^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[!@#\$&*~]).{8,}$'; 70 | 71 | RegExp regExpUpperCase = new RegExp(upperCase); 72 | RegExp regExpLowerCase = new RegExp(lowerCase); 73 | RegExp regExpDigit = new RegExp(digit); 74 | RegExp regExpSpecialChar = new RegExp(specialChar); 75 | RegExp regExpPasswordLength = new RegExp(passwordLength); 76 | 77 | if (value.length == 0) { 78 | return "Password is required!"; 79 | } else if (!regExpUpperCase.hasMatch(value)) { 80 | return "Atleast one upper case letter required!"; 81 | } else if (!regExpLowerCase.hasMatch(value)) { 82 | return "Atleast one Lower Case Required"; 83 | } else if (!regExpDigit.hasMatch(value)) { 84 | return "Atleast one Digit Required"; 85 | } else if (!regExpSpecialChar.hasMatch(value)) { 86 | return "Atleast one Special Char{@ ,*,# etc} Required"; 87 | } else if (!regExpPasswordLength.hasMatch(value)) { 88 | return "Password length should be minimum 8 "; 89 | } else { 90 | return null; 91 | } 92 | } 93 | 94 | static String validatePincode(String value) { 95 | // print("Validate pincode"); 96 | String digit = '(?=.*?[0-9])'; 97 | // String patttern = r'(^(?:[+0]9)?[0-9]{10,12}$)'; 98 | RegExp regExp = new RegExp(digit); 99 | if (value.length == 0) { 100 | return 'Please enter pincode'; 101 | } else if (value.length < 6) { 102 | return 'Please enter valid pincode'; 103 | } else if (!regExp.hasMatch(value)) { 104 | return 'Please enter valid pincode'; 105 | } 106 | return null; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /lib/src/ui/view/onboarding/onboard_options_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 5 | import 'package:myntra_test_app/src/ui/view/onboarding/login_screen.dart'; 6 | import 'package:myntra_test_app/src/ui/view//onboarding/sign_up_screen.dart'; 7 | import 'package:myntra_test_app/src/ui/widget/custom_app_bar.dart'; 8 | import 'package:myntra_test_app/src/ui/widget/custom_button_with_image.dart'; 9 | 10 | class OnboardOptionsScreen extends StatelessWidget { 11 | @override 12 | Widget build(BuildContext context) { 13 | void _normalLoginBtnClicked() { 14 | final route = MaterialPageRoute(builder: (context) => LoginScreen()); 15 | Navigator.push(context, route); 16 | } 17 | 18 | void _signupClicked() { 19 | final route = MaterialPageRoute(builder: (context) => SignUpScreen()); 20 | Navigator.push(context, route); 21 | } 22 | 23 | return Scaffold( 24 | appBar: PreferredSize( 25 | child: CustomAppBar( 26 | title: '', 27 | ), 28 | preferredSize: Size(null, AppConstants.preferredAppBarHeight), 29 | ), 30 | body: SafeArea( 31 | child: SingleChildScrollView( 32 | padding: EdgeInsets.all(10), 33 | child: Column( 34 | children: [ 35 | Row( 36 | children: [ 37 | Expanded( 38 | child: InkWell( 39 | child: CustomButtonWithImage( 40 | imageNameString: AppIcons.Facebook, 41 | titleString: 'Facebook', 42 | ), 43 | onTap: () {}, 44 | ), 45 | ), 46 | SizedBox( 47 | width: 15, 48 | ), 49 | Expanded( 50 | child: InkWell( 51 | child: CustomButtonWithImage( 52 | imageNameString: AppIcons.Google, 53 | titleString: 'Google', 54 | ), 55 | onTap: () {}, 56 | ), 57 | ), 58 | ], 59 | ), 60 | SizedBox( 61 | height: 15, 62 | ), 63 | InkWell( 64 | child: CustomButtonWithImage( 65 | imageNameString: AppIcons.MailBlack, 66 | titleString: 'Log in using email', 67 | ), 68 | onTap: _normalLoginBtnClicked, 69 | ), 70 | SizedBox( 71 | height: 15, 72 | ), 73 | InkWell( 74 | child: CustomButtonWithImage( 75 | imageNameString: AppIcons.PhoneBlack, 76 | titleString: 'Log in using mobile number', 77 | ), 78 | onTap: _normalLoginBtnClicked, 79 | ), 80 | SizedBox( 81 | height: 25, 82 | ), 83 | RichText( 84 | text: TextSpan( 85 | children: [ 86 | TextSpan( 87 | text: 'New to Friendly Shopping? ', 88 | style: TextStyle( 89 | color: Colors.black, 90 | fontSize: 14, 91 | fontWeight: FontWeight.w300, 92 | ), 93 | ), 94 | TextSpan( 95 | text: 'Sign up ', 96 | style: TextStyle( 97 | color: AppColors.cabaret, 98 | fontWeight: FontWeight.w700, 99 | fontSize: 14, 100 | ), 101 | recognizer: TapGestureRecognizer() 102 | ..onTap = () => _signupClicked(), 103 | ), 104 | ], 105 | ), 106 | ), 107 | ], 108 | ), 109 | ), 110 | ), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/core/view_model/products_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_storage/firebase_storage.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:myntra_test_app/src/core/helpers/alert_helper.dart'; 5 | import 'package:myntra_test_app/src/core/helpers/helper.dart'; 6 | import 'package:myntra_test_app/src/core/models/table_schemas/prod_table_schema.dart'; 7 | import 'package:myntra_test_app/src/core/helpers/url_constants.dart'; 8 | import 'package:myntra_test_app/src/core/models/product_model.dart'; 9 | 10 | class ProductsProvider with ChangeNotifier { 11 | final databaseReference = Firestore.instance; 12 | 13 | Future> fetchProducts({ 14 | @required BuildContext ctx, 15 | }) async { 16 | try { 17 | final snapShot = await databaseReference 18 | .collection(Tables.products) 19 | .where(ProductTable.isActive, isEqualTo: true) 20 | .getDocuments(); 21 | if (snapShot.documents.isEmpty) { 22 | return []; 23 | } 24 | List _prodsArray = []; 25 | snapShot.documents.forEach((doc) { 26 | // print('${doc.documentID}'); 27 | 28 | var imgUrlsJson = doc[ProductTable.imgUrlsArray]; 29 | List imgUrls = 30 | imgUrlsJson == null ? [] : List.from(imgUrlsJson); 31 | 32 | var sizesJson = 33 | doc[ProductTable.sizesDetailMap][ProdSizesDetailMap.list]; 34 | List> sizesDynamicArray = 35 | sizesJson == null ? [] : List>.from(sizesJson); 36 | List sizesArray = []; 37 | sizesDynamicArray.forEach((sizeDynamic) { 38 | // print('sizeDynamic: $sizeDynamic'); 39 | final prodSize = ProdSizeModel( 40 | id: sizeDynamic[ProdSizeMap.id], 41 | name: sizeDynamic[ProdSizeMap.name], 42 | remainingQty: sizeDynamic[ProdSizeMap.remainingQty] ?? 0, 43 | ); 44 | // print('prodSize name: ${prodSize.name}'); 45 | sizesArray.add(prodSize); 46 | }); 47 | 48 | final prod = ProductModel( 49 | id: doc.documentID, 50 | customerReviewsCount: doc[ProductTable.customerReviewsCount], 51 | descriptionMap: ProdDescriptionModel( 52 | long: doc[ProductTable.descriptionMap][ProdDescriptionMap.long], 53 | materialAndCare: doc[ProductTable.descriptionMap] 54 | [ProdDescriptionMap.materialAndCare], 55 | short1: doc[ProductTable.descriptionMap][ProdDescriptionMap.short1], 56 | short2: doc[ProductTable.descriptionMap][ProdDescriptionMap.short2], 57 | sizeAndFit: doc[ProductTable.descriptionMap] 58 | [ProdDescriptionMap.sizeAndFit], 59 | ), 60 | imgUrlsArray: imgUrls, 61 | manufacturerDetailMap: ProdManufacturerModel( 62 | brandId: doc[ProductTable.manufacturerDetailMap] 63 | [ProdManufacturerMap.brandId], 64 | brandName: doc[ProductTable.manufacturerDetailMap] 65 | [ProdManufacturerMap.brandName], 66 | id: doc[ProductTable.manufacturerDetailMap][ProdManufacturerMap.id], 67 | name: doc[ProductTable.manufacturerDetailMap] 68 | [ProdManufacturerMap.name], 69 | prodCode: doc[ProductTable.manufacturerDetailMap] 70 | [ProdManufacturerMap.prodCode], 71 | ), 72 | priceMap: ProdPriceModel( 73 | discountAmount: doc[ProductTable.priceDetailMap] 74 | [ProdPriceMap.discountAmount], 75 | discountMaxAmountViaPercentage: doc[ProductTable.priceDetailMap] 76 | [ProdPriceMap.discountMaxAmountViaPercentage], 77 | discountPercentage: doc[ProductTable.priceDetailMap] 78 | [ProdPriceMap.discountPercentage], 79 | discountType: doc[ProductTable.priceDetailMap] 80 | [ProdPriceMap.discountType], 81 | price: doc[ProductTable.priceDetailMap][ProdPriceMap.price], 82 | ), 83 | sizesmap: ProdSizesDetailModel( 84 | list: sizesArray, 85 | specialMsg: doc[ProductTable.sizesDetailMap] 86 | [ProdSizesDetailMap.specialMsg] ?? 87 | '', 88 | ), 89 | specialMsg: doc[ProductTable.specialMsg] ?? '', 90 | ); 91 | _prodsArray.add(prod); 92 | }); 93 | 94 | return _prodsArray; 95 | } catch (error) { 96 | print('fetchProducts error: $error'); 97 | AlertHelper.showOnlyOkAlertDialog(ctx: ctx, msg: error.message); 98 | return []; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/src/ui/view/home_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/rendering.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 6 | import 'package:myntra_test_app/src/core/models/common_models.dart'; 7 | import 'package:myntra_test_app/src/core/view_model/category_provider.dart'; 8 | import 'package:myntra_test_app/src/ui/widget/app_drawer.dart'; 9 | import 'package:myntra_test_app/src/ui/widget/custom_app_bar.dart'; 10 | import 'package:myntra_test_app/src/ui/widget/tiles/home_tiles/home_screen_hot_deals_list_tile.dart'; 11 | import 'package:myntra_test_app/src/ui/widget/tiles/home_tiles/home_screen_top_category_tile.dart'; 12 | 13 | class HomeScreen extends StatefulWidget { 14 | static const routeName = 'home'; 15 | 16 | @override 17 | _HomeScreenState createState() => _HomeScreenState(); 18 | } 19 | 20 | class _HomeScreenState extends State { 21 | var _isLoading = false; 22 | List topCategoriesArray = []; 23 | 24 | @override 25 | void initState() { 26 | Future.delayed(Duration.zero).then((_) { 27 | if (mounted) { 28 | print('HomeScreen mounted: $mounted'); 29 | } 30 | setState(() { 31 | _isLoading = true; 32 | }); 33 | final catProvider = Provider.of(context, listen: false); 34 | catProvider.fetchAndSetCategories().then((_) { 35 | setState(() { 36 | topCategoriesArray = catProvider.categories.values.toList(); 37 | _isLoading = false; 38 | }); 39 | }); 40 | }); 41 | 42 | super.initState(); 43 | } 44 | 45 | @override 46 | void dispose() { 47 | print('HomeScreen disposed'); 48 | super.dispose(); 49 | } 50 | 51 | @override 52 | Widget build(BuildContext context) { 53 | _buildAppBar(BuildContext context) { 54 | return PreferredSize( 55 | preferredSize: Size(null, AppConstants.preferredAppBarHeight), 56 | child: CustomAppBar( 57 | leftButton: CustomAppBarLeftButtonsTypes.Menu, 58 | title: 'Friendly Shopping', 59 | ), 60 | ); 61 | } 62 | 63 | _buildScaffoldView() { 64 | return Scaffold( 65 | appBar: _buildAppBar(context), 66 | body: SafeArea( 67 | child: SingleChildScrollView( 68 | child: Column( 69 | children: [ 70 | Column( 71 | children: [ 72 | _buildTopListView(), 73 | Image.asset( 74 | AppImages.banner1, 75 | fit: BoxFit.cover, 76 | // height: 300, 77 | ), 78 | SizedBox( 79 | height: 10, 80 | ), 81 | Container( 82 | height: 200, 83 | child: ListView.builder( 84 | itemBuilder: (ctx, index) => HomeScreenHotDealsListTile( 85 | heading: 'THE DESI WAY', 86 | title: 'Shop Ethnic Wear At Min. 50% Off!', 87 | imageName: AppImages.women, 88 | msg: '+ DON\'T MISS THIS', 89 | ), 90 | scrollDirection: Axis.horizontal, 91 | itemCount: topCategoriesArray.length, 92 | ), 93 | ), 94 | SizedBox( 95 | height: 10, 96 | ), 97 | Image.asset( 98 | AppImages.banner2, 99 | fit: BoxFit.cover, 100 | // height: 300, 101 | ), 102 | ], 103 | ), 104 | ], 105 | ), 106 | ), 107 | ), // T 108 | drawer: 109 | AppDrawer(), // his trailing comma makes auto-formatting nicer for build methods. 110 | ); 111 | } 112 | 113 | return _buildScaffoldView(); 114 | } 115 | 116 | _buildTopListView() { 117 | return Container( 118 | height: 110, 119 | child: _isLoading 120 | ? Center( 121 | child: CircularProgressIndicator(), 122 | ) 123 | : ListView.builder( 124 | itemBuilder: (ctx, index) => HomeScreenTopCategoryListTile( 125 | title: topCategoriesArray[index].name, 126 | imageName: topCategoriesArray[index].homePageImageUrl, 127 | ), 128 | scrollDirection: Axis.horizontal, 129 | itemCount: topCategoriesArray.length, 130 | ), 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/item_tiles/item_grid_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 3 | import 'package:myntra_test_app/src/core/helpers/helper.dart'; 4 | 5 | import 'package:myntra_test_app/src/core/models/product_model.dart'; 6 | 7 | class ItemGridTile extends StatelessWidget { 8 | final ProductModel product; 9 | 10 | ItemGridTile({@required this.product}); 11 | 12 | DiscountTypes get getDiscountType { 13 | return Helper.getDiscountTypeFor(code: product.priceMap.discountType); 14 | } 15 | 16 | String get getDiscountString { 17 | String discountString = ''; 18 | final discountType = getDiscountType; 19 | switch (discountType) { 20 | case DiscountTypes.Amount: 21 | discountString = 'Rs ${product.priceMap.discountAmount} off'; 22 | break; 23 | 24 | case DiscountTypes.Percentage: 25 | discountString = '${product.priceMap.discountPercentage}% off'; 26 | break; 27 | 28 | default: 29 | break; 30 | } 31 | return discountString; 32 | } 33 | 34 | Widget _buildPriceDiscountView() { 35 | return Row( 36 | children: [ 37 | SizedBox( 38 | width: 5, 39 | ), 40 | Text( 41 | 'Rs ${product.priceMap.price}', 42 | style: TextStyle( 43 | color: Colors.grey[500], 44 | decoration: TextDecoration.lineThrough, 45 | fontSize: 11, 46 | ), 47 | ), 48 | SizedBox( 49 | width: 5, 50 | ), 51 | Text( 52 | getDiscountString, 53 | style: TextStyle( 54 | color: Colors.orange, 55 | fontSize: 11, 56 | ), 57 | ), 58 | ], 59 | ); 60 | } 61 | 62 | Widget _buildPriceView() { 63 | return Padding( 64 | padding: const EdgeInsets.symmetric(vertical: 2.0), 65 | child: Row( 66 | children: [ 67 | Text( 68 | '${AppStrings.RupeeSign} ${Helper.getIteratedPriceToShowFor(product: product)}', 69 | style: TextStyle( 70 | color: Colors.black, 71 | fontWeight: FontWeight.w500, 72 | fontSize: 11, 73 | ), 74 | ), 75 | if (getDiscountType != DiscountTypes.None) _buildPriceDiscountView(), 76 | ], 77 | ), 78 | ); 79 | } 80 | 81 | @override 82 | Widget build(BuildContext context) { 83 | return Container( 84 | padding: EdgeInsets.all(10), 85 | decoration: BoxDecoration( 86 | color: Colors.white, 87 | border: Border.all( 88 | color: AppColors.silver, 89 | width: 0.5, 90 | ), 91 | ), 92 | child: Column( 93 | mainAxisAlignment: MainAxisAlignment.end, 94 | children: [ 95 | Expanded( 96 | child: FadeInImage( 97 | placeholder: AssetImage( 98 | AppIcons.flutter, 99 | ), 100 | image: NetworkImage(product.imgUrlsArray.first), 101 | fit: BoxFit.contain, 102 | ), 103 | ), 104 | Row( 105 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 106 | crossAxisAlignment: CrossAxisAlignment.start, 107 | children: [ 108 | Column( 109 | crossAxisAlignment: CrossAxisAlignment.start, 110 | children: [ 111 | Padding( 112 | padding: const EdgeInsets.symmetric(vertical: 2.0), 113 | child: Text( 114 | product.manufacturerDetailMap.brandName, 115 | style: TextStyle(fontSize: 13), 116 | ), 117 | ), 118 | Padding( 119 | padding: const EdgeInsets.symmetric(vertical: 2.0), 120 | child: Text( 121 | product.descriptionMap.short1, 122 | style: TextStyle( 123 | color: Colors.grey[500], 124 | fontSize: 11, 125 | ), 126 | ), 127 | ), 128 | _buildPriceView(), 129 | Padding( 130 | padding: const EdgeInsets.symmetric(vertical: 2.0), 131 | child: Text( 132 | product.specialMsg, 133 | style: TextStyle( 134 | color: Colors.red, 135 | fontWeight: FontWeight.bold, 136 | fontSize: 10, 137 | ), 138 | ), 139 | ), 140 | ], 141 | ), 142 | Container( 143 | alignment: Alignment.topRight, 144 | width: 20, 145 | child: Icon( 146 | Icons.bookmark_border, 147 | color: Colors.pink, 148 | ), 149 | ) 150 | ], 151 | ), 152 | ], 153 | ), 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/ui/view/products/items_list_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:provider/provider.dart'; 3 | 4 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 5 | import 'package:myntra_test_app/src/core/models/product_model.dart'; 6 | import 'package:myntra_test_app/src/core/models/search_models.dart'; 7 | import 'package:myntra_test_app/src/core/view_model/products_provider.dart'; 8 | import 'package:myntra_test_app/src/ui/view//products/item_detail_screen.dart'; 9 | import 'package:myntra_test_app/src/ui/widget/custom_app_bar.dart'; 10 | import 'package:myntra_test_app/src/ui/widget/custom_progress_bar.dart'; 11 | import 'package:myntra_test_app/src/ui/widget/tiles/item_tiles/item_grid_tile.dart'; 12 | 13 | class ItemsListScreen extends StatefulWidget { 14 | @override 15 | _ItemsListScreenState createState() => _ItemsListScreenState(); 16 | 17 | final SearchProductsModel searchProductsModel; 18 | final title; 19 | 20 | ItemsListScreen({ 21 | @required this.searchProductsModel, 22 | @required this.title, 23 | }); 24 | } 25 | 26 | class _ItemsListScreenState extends State { 27 | var _isLoading = false; 28 | List _productArray = []; 29 | 30 | @override 31 | void initState() { 32 | Future.delayed(Duration.zero).then((_) async { 33 | setState(() { 34 | _isLoading = true; 35 | }); 36 | _productArray = 37 | await Provider.of(context, listen: false) 38 | .fetchProducts(ctx: context); 39 | setState(() { 40 | _isLoading = false; 41 | }); 42 | }); 43 | 44 | super.initState(); 45 | } 46 | 47 | @override 48 | Widget build(BuildContext context) { 49 | void didTappedAtItem({@required int index}) { 50 | final prod = _productArray[index]; 51 | final route = MaterialPageRoute( 52 | builder: (ctx) => ItemDetailScreen( 53 | product: prod, 54 | searchProductsModel: widget.searchProductsModel, 55 | )); 56 | Navigator.push(context, route); 57 | } 58 | 59 | _buildAppBar() => PreferredSize( 60 | preferredSize: Size(null, AppConstants.preferredAppBarHeight), 61 | child: CustomAppBar( 62 | title: widget.title, 63 | ), 64 | ); 65 | 66 | _buildBottomView() { 67 | return Container( 68 | height: 50, 69 | color: Colors.white, 70 | child: Column( 71 | children: [ 72 | Container( 73 | height: 0.2, 74 | color: Colors.grey, 75 | ), 76 | Expanded( 77 | child: Row( 78 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 79 | children: [ 80 | FlatButton.icon( 81 | icon: Icon(Icons.sort_by_alpha), 82 | label: Text('Sort'), 83 | onPressed: () {}, 84 | ), 85 | Container( 86 | margin: EdgeInsets.symmetric( 87 | vertical: 10, 88 | ), 89 | width: 0.4, 90 | color: Colors.grey, 91 | ), 92 | FlatButton.icon( 93 | icon: Icon(Icons.filter_list), 94 | label: Text('Filter'), 95 | onPressed: () {}, 96 | ) 97 | ], 98 | ), 99 | ), 100 | ], 101 | ), 102 | ); 103 | } 104 | 105 | _buildGridView() { 106 | return Expanded( 107 | child: Container( 108 | // color: AppColors.silver, 109 | child: _productArray.length == 0 110 | ? Center( 111 | child: Text('No data yet'), 112 | ) 113 | : GridView.builder( 114 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( 115 | crossAxisCount: 2, 116 | crossAxisSpacing: 2, 117 | mainAxisSpacing: 2, 118 | childAspectRatio: 0.5, 119 | ), 120 | itemBuilder: (ctx, index) { 121 | return InkWell( 122 | onTap: () => didTappedAtItem(index: index), 123 | child: ItemGridTile( 124 | product: _productArray[index], 125 | ), 126 | ); 127 | }, 128 | itemCount: _productArray.length, 129 | ), 130 | ), 131 | ); 132 | } 133 | 134 | _buildScaffoldView() { 135 | return Scaffold( 136 | backgroundColor: Colors.white, 137 | appBar: _buildAppBar(), 138 | body: SafeArea( 139 | child: Column( 140 | children: [ 141 | _buildGridView(), 142 | _buildBottomView(), 143 | ], 144 | ), 145 | ), 146 | ); 147 | } 148 | 149 | return Stack( 150 | children: [ 151 | _buildScaffoldView(), 152 | CustomProgressBar(status: _isLoading), 153 | ], 154 | ); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /lib/src/ui/widget/tiles/app_drawer/app_drawer_sub_list_tile.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/models/common_models.dart'; 3 | import 'package:myntra_test_app/src/ui/view/products/items_list_screen.dart'; 4 | 5 | class AppDrawerSubListTile extends StatelessWidget { 6 | final SubCategoryModal subCategory; 7 | final bool isSelected; 8 | final int index; 9 | Function subCategoryViewTapped; 10 | Function subSubCategoryViewTapped; 11 | 12 | AppDrawerSubListTile({ 13 | @required this.subCategory, 14 | this.isSelected = false, 15 | @required this.index, 16 | @required this.subCategoryViewTapped, 17 | @required this.subSubCategoryViewTapped, 18 | }); 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Container( 23 | // height: 50, 24 | // color: Colors.grey[300], 25 | child: Column( 26 | mainAxisAlignment: MainAxisAlignment.end, 27 | children: [ 28 | InkWell( 29 | onTap: () => subCategoryViewTapped(subCategory), 30 | child: Container( 31 | padding: EdgeInsets.symmetric(horizontal: 25, vertical: 15), 32 | // color: Colors.yellow, 33 | child: Row( 34 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 35 | children: [ 36 | Text( 37 | subCategory.name, 38 | style: TextStyle( 39 | color: isSelected ? Colors.black : Colors.grey[800], 40 | fontSize: 13, 41 | fontWeight: 42 | isSelected ? FontWeight.w400 : FontWeight.w300, 43 | ), 44 | ), 45 | if (subCategory.subSubCategoriesIDs.length != 0) 46 | Icon( 47 | isSelected 48 | ? Icons.keyboard_arrow_up 49 | : Icons.keyboard_arrow_down, 50 | color: Colors.grey[400], 51 | ), 52 | ], 53 | ), 54 | ), 55 | ), 56 | Container( 57 | height: 0.3, 58 | color: Colors.grey[400], 59 | ), 60 | if (isSelected) 61 | Column( 62 | children: subCategory.subSubCategories.map((subSubCategory) { 63 | return InkWell( 64 | onTap: () => subSubCategoryViewTapped(subSubCategory), 65 | child: AppDrawerSubSubListTile( 66 | subSubCategory: subSubCategory, 67 | ), 68 | ); 69 | // return AppDrawerSubSubListTile( 70 | // title: subSubCategory.name, 71 | // ); 72 | }).toList(), 73 | ) 74 | ], 75 | ), 76 | ); 77 | } 78 | } 79 | 80 | class AppDrawerSubSubListTile extends StatelessWidget { 81 | final SubSubCategoryModal subSubCategory; 82 | 83 | AppDrawerSubSubListTile({ 84 | @required this.subSubCategory, 85 | }); 86 | 87 | @override 88 | Widget build(BuildContext context) { 89 | return Container( 90 | padding: EdgeInsets.only(left: 40), 91 | child: Column( 92 | mainAxisAlignment: MainAxisAlignment.end, 93 | children: [ 94 | Container( 95 | padding: EdgeInsets.symmetric(vertical: 15), 96 | child: Row( 97 | children: [ 98 | Text( 99 | subSubCategory.name, 100 | style: TextStyle( 101 | color: Colors.grey[800], 102 | fontSize: 12, 103 | fontWeight: FontWeight.w300, 104 | ), 105 | ), 106 | ], 107 | ), 108 | ), 109 | Container( 110 | height: 0.3, 111 | color: Colors.grey[400], 112 | ), 113 | ], 114 | ), 115 | ); 116 | // return InkWell( 117 | // onTap: () { 118 | // final route = 119 | // MaterialPageRoute(builder: (ctx) => ItemsListScreen(title: title)); 120 | // Navigator.push(context, route); 121 | //// Navigator.of(context) 122 | //// .pushNamed(ItemsListScreen.routeName, arguments: title); 123 | // }, 124 | // child: Container( 125 | // padding: EdgeInsets.only(left: 40), 126 | // child: Column( 127 | // mainAxisAlignment: MainAxisAlignment.end, 128 | // children: [ 129 | // Container( 130 | // padding: EdgeInsets.symmetric(vertical: 15), 131 | // child: Row( 132 | // children: [ 133 | // Text( 134 | // title, 135 | // style: TextStyle( 136 | // color: Colors.grey[800], 137 | // fontSize: 12, 138 | // fontWeight: FontWeight.w300, 139 | // ), 140 | // ), 141 | // ], 142 | // ), 143 | // ), 144 | // Container( 145 | // height: 0.3, 146 | // color: Colors.grey[400], 147 | // ), 148 | // ], 149 | // ), 150 | // ), 151 | // ); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter Shopping App using Provider 2 | 3 | A flutter app to showcase online shopping portal using `Provider` architecture. `FireStore` has been used as backend for this app. 4 | 5 | 6 | # Android Screens 7 | 8 | 9 | 10 | # iOS Screens 11 | 12 | 13 | 14 | 15 | # Provider [![pub package](https://img.shields.io/pub/v/provider.svg)](https://pub.dev/packages/provider) 16 | 17 | Among multiple state management in flutter, `Provider` is now more powerful, flixible and easy to undersand. It has the power of managing state in efficient manager by using `Consumer` and `Selector` and now it can deals with `Stream` and `Future` also. 18 | 19 | A mixture between dependency injection (DI) and state management, built with widgets for widgets. 20 | 21 | ## *Why?* 22 | It purposefully uses widgets for DI/state management instead of dart-only classes like `Stream`. The reason is, widgets are very simple yet robust and scalable. 23 | 24 | ## *Customizable* 25 | By using the power of provider we can make our own provider. `Provider` expose all the small components that makes a fully fledged provider. 26 | 27 | `Provider` includes below widgets for customizability: 28 | - `SingleChildCloneableWidget`, to make any widget works with `MultiProvider`. 29 | - `InheritedProvider`, the generic `InheritedWidget` obtained when doing `Provider.of`. 30 | - `DelegateWidget`/`BuilderDelegate`/`ValueDelegate` to help handle the logic of "MyProvider() that creates an object" vs "MyProvider.value() that can update over time". 31 | 32 | ## *Pros of Provider* 33 | - UI logic and business logic are clearly separated 34 | - Provider is customizable and gives flexibility to customize provider according to your need to boost the performance and stability. 35 | - Easy to understand and implement 36 | - Can be set up with unidirectional data flow without much difficulty, gaining the main benefit of Redux as well as provider is more powerful to handle global state. 37 | - Robustness, as it is harder to forget to handle the update scenario of a model/widget 38 | 39 | ## *Existing providers* 40 | 41 | `provider` exposes a few different kinds of "provider" for different types of objects. 42 | 43 | The complete list of all the objects available is [here](https://pub.dev/documentation/provider/latest/provider/provider-library.html) 44 | 45 | | name | description | 46 | | ----------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 47 | | [Provider](https://pub.dartlang.org/documentation/provider/latest/provider/Provider-class.html) | The most basic form of provider. It takes a value and exposes it, whatever the value is. | 48 | | [ListenableProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ListenableProvider-class.html) | A specific provider for Listenable object. ListenableProvider will listen to the object and ask widgets which depend on it to rebuild whenever the listener is called. | 49 | | [ChangeNotifierProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ChangeNotifierProvider-class.html) | A specification of ListenableProvider for ChangeNotifier. It will automatically call `ChangeNotifier.dispose` when needed. | 50 | | [ValueListenableProvider](https://pub.dartlang.org/documentation/provider/latest/provider/ValueListenableProvider-class.html) | Listen to a ValueListenable and only expose `ValueListenable.value`. | 51 | | [StreamProvider](https://pub.dartlang.org/documentation/provider/latest/provider/StreamProvider-class.html) | Listen to a Stream and expose the latest value emitted. | 52 | | [FutureProvider](https://pub.dartlang.org/documentation/provider/latest/provider/FutureProvider-class.html) | Takes a `Future` and updates dependents when the future completes. 53 | 54 | To read more about `provider`, see the [documentation](https://pub.dev/documentation/provider/latest/). -------------------------------------------------------------------------------- /lib/src/ui/view/onboarding/login_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:myntra_test_app/src/core/helpers/alert_helper.dart'; 3 | 4 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 5 | import 'package:myntra_test_app/src/core/helpers/helper.dart'; 6 | import 'package:myntra_test_app/src/core/helpers/validators.dart'; 7 | import 'package:myntra_test_app/src/core/view_model//auth_provider.dart'; 8 | import 'package:myntra_test_app/src/ui/view/account_summary/account_summary_screen.dart'; 9 | import 'package:myntra_test_app/src/ui/widget/custom_app_bar.dart'; 10 | import 'package:myntra_test_app/src/ui/widget/custom_default_text_form_field.dart'; 11 | import 'package:myntra_test_app/src/ui/widget/custom_progress_bar.dart'; 12 | import 'package:provider/provider.dart'; 13 | 14 | class LoginScreen extends StatefulWidget { 15 | @override 16 | _LoginScreenState createState() => _LoginScreenState(); 17 | } 18 | 19 | class _LoginScreenState extends State { 20 | final _mobNumEmailFocus = FocusNode(); 21 | final _mobNumEmailController = TextEditingController(); 22 | final _passwordFocus = FocusNode(); 23 | final _passwordController = TextEditingController(); 24 | final _loginFormKey = GlobalKey(); 25 | 26 | bool _isLoading = false; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | final authProvider = Provider.of(context, listen: false); 31 | 32 | void _submitLogInRequest() async { 33 | setState(() { 34 | _isLoading = true; 35 | }); 36 | 37 | Helper.dismissKeyboard(ctx: context); 38 | 39 | try { 40 | final isLogInDone = await authProvider.signIn( 41 | ctx: context, 42 | email: _mobNumEmailController.text, 43 | password: _passwordController.text, 44 | ); 45 | if (isLogInDone) { 46 | print('Move to profile screen'); 47 | final route = MaterialPageRoute( 48 | builder: (ctx) => AccountSummaryScreen( 49 | isComingAfterSignUp: true, 50 | ), 51 | ); 52 | Navigator.push(context, route); 53 | } else { 54 | print('authProvider isLogInDone false'); 55 | } 56 | } catch (error) { 57 | AlertHelper.showOnlyOkAlertDialog(ctx: context, msg: error.message); 58 | } 59 | setState(() { 60 | _isLoading = false; 61 | _loginFormKey.currentState.reset(); 62 | }); 63 | } 64 | 65 | void _loginBtnClicked() { 66 | Helper.dismissKeyboard(ctx: context); 67 | 68 | final isValid = _loginFormKey.currentState.validate(); 69 | if (isValid) { 70 | _loginFormKey.currentState.save(); 71 | _submitLogInRequest(); 72 | } 73 | } 74 | 75 | Widget _buildScaffoldView() { 76 | return Scaffold( 77 | appBar: PreferredSize( 78 | child: CustomAppBar( 79 | title: 'Login', 80 | ), 81 | preferredSize: Size( 82 | null, 83 | AppConstants.preferredAppBarHeight, 84 | ), 85 | ), 86 | body: SafeArea( 87 | child: SingleChildScrollView( 88 | padding: EdgeInsets.symmetric( 89 | vertical: 10, 90 | horizontal: 20, 91 | ), 92 | child: Form( 93 | key: _loginFormKey, 94 | child: Column( 95 | children: [ 96 | Container( 97 | height: 50, 98 | ), 99 | CustomDefaultTextFormField( 100 | placeHolderString: 'Mobile Number or Email ID', 101 | textController: _mobNumEmailController, 102 | ownFocusNode: _mobNumEmailFocus, 103 | nextFocusNode: _passwordFocus, 104 | validationFunc: Validator.validateEmpty, 105 | ), 106 | SizedBox( 107 | height: 40, 108 | ), 109 | CustomDefaultTextFormField( 110 | placeHolderString: 'Password', 111 | textController: _passwordController, 112 | ownFocusNode: _passwordFocus, 113 | textInputAction: TextInputAction.done, 114 | isObscureText: true, 115 | validationFunc: Validator.validateLoginPassword, 116 | ), 117 | SizedBox( 118 | height: 20, 119 | ), 120 | Container( 121 | height: 50, 122 | width: double.infinity, 123 | child: FlatButton( 124 | onPressed: _loginBtnClicked, 125 | // icon: Icon( 126 | // Icons.shopping_cart, 127 | // color: Colors.white, 128 | // ), 129 | child: Text( 130 | 'LOG IN', 131 | style: TextStyle( 132 | color: Colors.white, 133 | fontSize: 14, 134 | fontWeight: FontWeight.w600, 135 | ), 136 | ), 137 | color: Colors.pink, 138 | ), 139 | ), 140 | SizedBox( 141 | height: 20, 142 | ), 143 | Container( 144 | height: 50, 145 | width: double.infinity, 146 | child: OutlineButton( 147 | child: Text( 148 | 'QUICK LOG IN USING OTP', 149 | style: TextStyle( 150 | fontSize: 14, 151 | fontWeight: FontWeight.w600, 152 | ), 153 | ), 154 | onPressed: () {}, 155 | borderSide: BorderSide( 156 | color: AppColors.silver, 157 | ), 158 | shape: RoundedRectangleBorder( 159 | borderRadius: BorderRadius.circular(3.0), 160 | ), 161 | textColor: AppColors.cabaret, 162 | ), 163 | ), 164 | SizedBox( 165 | height: 20, 166 | ), 167 | FlatButton( 168 | onPressed: () {}, 169 | child: Text( 170 | 'Forgot Password?', 171 | style: TextStyle( 172 | color: AppColors.cabaret, 173 | fontSize: 14, 174 | fontWeight: FontWeight.w600, 175 | ), 176 | ), 177 | // color: Colors.pink, 178 | ), 179 | ], 180 | ), 181 | ), 182 | ), 183 | ), 184 | ); 185 | } 186 | 187 | return Stack( 188 | children: [ 189 | _buildScaffoldView(), 190 | CustomProgressBar(status: _isLoading), 191 | ], 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/src/core/view_model/auth_provider.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:cloud_firestore/cloud_firestore.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:firebase_auth/firebase_auth.dart'; 6 | import 'package:myntra_test_app/src/core/helpers/alert_helper.dart'; 7 | 8 | import 'package:myntra_test_app/src/core/helpers/common_constants.dart'; 9 | import 'package:myntra_test_app/src/core/helpers/prefs_constants.dart'; 10 | import 'package:myntra_test_app/src/core/models/table_schemas/user_table_schema.dart'; 11 | import 'package:myntra_test_app/src/core/helpers/url_constants.dart'; 12 | import 'package:myntra_test_app/src/core/models/http_exception.dart'; 13 | import 'package:shared_preferences/shared_preferences.dart'; 14 | 15 | class AuthProvider with ChangeNotifier { 16 | GenderTypes _signUpSelectedGenderType = GenderTypes.Male; 17 | final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; 18 | final databaseReference = Firestore.instance; 19 | 20 | // DateTime _expDateTime; 21 | String _token; 22 | String _userID; 23 | String _userDocID; 24 | 25 | GenderTypes get getSignUpSelectedGenderType { 26 | return _signUpSelectedGenderType; 27 | } 28 | 29 | void setSignUpSelectedGenderTypeTo({@required GenderTypes gender}) { 30 | _signUpSelectedGenderType = gender; 31 | notifyListeners(); 32 | } 33 | 34 | Future signUp({ 35 | @required BuildContext ctx, 36 | @required String email, 37 | @required String password, 38 | String mobNum = '', 39 | @required String name, 40 | }) async { 41 | final status = await _firebaseAuth 42 | .createUserWithEmailAndPassword(email: email, password: password) 43 | .then((authResult) async { 44 | FirebaseUser user = authResult.user; 45 | // AdditionalUserInfo additionalUserInfo = authResult.additionalUserInfo; 46 | print('FirebaseUser:'); 47 | print('User ID: ${user.uid}'); 48 | _userID = user.uid; 49 | // print('AdditionalUserInfo: ${additionalUserInfo}'); 50 | 51 | try { 52 | final userData = { 53 | UserTable.userID: _userID, 54 | UserTable.emailID: email, 55 | UserTable.gender: 56 | _signUpSelectedGenderType == GenderTypes.Male ? '1' : '2', 57 | UserTable.mobNum: mobNum, 58 | UserTable.name: name, 59 | }; 60 | await databaseReference 61 | .collection(Tables.users) 62 | .add(userData) 63 | .then((value) async { 64 | print('New user documentID: ${value.documentID}'); 65 | _userDocID = value.documentID; 66 | 67 | final prefs = await SharedPreferences.getInstance(); 68 | final userData = json.encode( 69 | { 70 | FSPrefs.UserID: _userID, 71 | FSPrefs.UserDocID: _userDocID, 72 | FSPrefs.UserImageUrl: '', 73 | FSPrefs.UserName: name, 74 | FSPrefs.UserToken: _token ?? '', 75 | // UserPrefsConstants.ExpDateTime: _expDateTime.toIso8601String() 76 | }, 77 | ); 78 | prefs.setString(FSPrefs.UserData, userData); 79 | }).catchError((err) { 80 | print('New user server error: ${err.message}'); 81 | }); 82 | } catch (e) { 83 | print('New user method error: ${e.message}'); 84 | } 85 | 86 | print('Return true'); 87 | return true; 88 | }).catchError((error) { 89 | print('createUserWithEmailAndPassword error: $error'); 90 | AlertHelper.showOnlyOkAlertDialog(ctx: ctx, msg: error.message); 91 | return false; 92 | }); 93 | return status; 94 | } 95 | 96 | Future signIn({ 97 | @required BuildContext ctx, 98 | @required String email, 99 | @required String password, 100 | }) async { 101 | try { 102 | final authResult = await _firebaseAuth.signInWithEmailAndPassword( 103 | email: email, password: password); 104 | FirebaseUser user = authResult.user; 105 | print('FirebaseUser:'); 106 | print('User ID: ${user.uid}'); 107 | 108 | final snapShot = await databaseReference 109 | .collection(Tables.users) 110 | .where(UserTable.userID, isEqualTo: user.uid) 111 | // .where(UserTable.emailID, isEqualTo: email) 112 | .getDocuments(); 113 | 114 | if (snapShot.documents.isEmpty) { 115 | print('UserTable: No documents found!'); 116 | throw HttpException(message: 'No user found!'); 117 | } 118 | snapShot.documents.forEach((document) async { 119 | print('documentID: ${document.documentID}'); 120 | 121 | final prefs = await SharedPreferences.getInstance(); 122 | final userData = json.encode( 123 | { 124 | FSPrefs.UserID: document[UserTable.userID], 125 | FSPrefs.UserDocID: document.documentID, 126 | FSPrefs.UserImageUrl: document[UserTable.imageUrl] ?? '', 127 | FSPrefs.UserName: document[UserTable.name], 128 | FSPrefs.UserToken: _token ?? '', 129 | // UserPrefsConstants.ExpDateTime: _expDateTime.toIso8601String() 130 | }, 131 | ); 132 | prefs.setString(FSPrefs.UserData, userData); 133 | 134 | _userID = user.uid; 135 | _userDocID = document.documentID; 136 | 137 | // print('AdditionalUserInfo.username: ${additionalUserInfo.username}'); 138 | }); 139 | 140 | return true; 141 | } catch (error) { 142 | print('signIn error: $error'); 143 | AlertHelper.showOnlyOkAlertDialog(ctx: ctx, msg: error.message); 144 | return false; 145 | } 146 | } 147 | 148 | Future tryAutoLogin() async { 149 | final prefs = await SharedPreferences.getInstance(); 150 | if (!prefs.containsKey(FSPrefs.UserData)) { 151 | return false; 152 | } 153 | final extractedUserData = 154 | json.decode(prefs.getString(FSPrefs.UserData)) as Map; 155 | // final expiryDateTime = DateTime.parse(extractedUserData['expiryDate']); 156 | // if (expiryDateTime.isBefore(DateTime.now())) { 157 | // return false; 158 | // } 159 | 160 | _token = extractedUserData[FSPrefs.UserToken] ?? ''; 161 | _userID = extractedUserData[FSPrefs.UserID]; 162 | _userDocID = extractedUserData[FSPrefs.UserDocID]; 163 | // _expDateTime = expiryDateTime; 164 | 165 | // _autoLogOut(); 166 | notifyListeners(); 167 | return true; 168 | } 169 | 170 | bool get isAuthorized { 171 | return getUserID != null; 172 | } 173 | 174 | // String get getToken { 175 | // if (_expDateTime != null && 176 | // _expDateTime.isAfter(DateTime.now()) && 177 | // _token != null) { 178 | // return _token; 179 | // } 180 | // return null; 181 | // } 182 | 183 | String get getUserID { 184 | return _userID; 185 | } 186 | 187 | String get getUserDocID { 188 | return _userDocID; 189 | } 190 | 191 | Future logOut() async { 192 | // _expDateTime = null; 193 | _userID = null; 194 | _token = null; 195 | // if (_authTimer != null) { 196 | // _authTimer.cancel(); 197 | // _authTimer = null; 198 | // } 199 | 200 | final prefs = await SharedPreferences.getInstance(); 201 | if (!prefs.containsKey(FSPrefs.UserData)) { 202 | return false; 203 | } 204 | prefs.remove(FSPrefs.UserData); 205 | 206 | // notifyListeners(); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/dart,swift,xcode,android,intellij,cocoapods,objective-c,androidstudio 2 | 3 | ### Android ### 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the ART/Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | out/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | android/.idea/libraries 24 | 25 | # Local configuration file (sdk path, etc) 26 | local.properties 27 | 28 | # Proguard folder generated by Eclipse 29 | proguard/ 30 | 31 | # Log Files 32 | *.log 33 | 34 | # Android Studio Navigation editor temp files 35 | .navigation/ 36 | 37 | # Android Studio captures folder 38 | captures/ 39 | 40 | # Intellij 41 | *.iml 42 | .idea/workspace.xml 43 | .idea/tasks.xml 44 | .idea/gradle.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | 48 | # External native build folder generated in Android Studio 2.2 and later 49 | .externalNativeBuild 50 | 51 | # Freeline 52 | freeline.py 53 | freeline/ 54 | freeline_project_description.json 55 | 56 | ### Android Patch ### 57 | gen-external-apklibs 58 | 59 | ### AndroidStudio ### 60 | # Covers files to be ignored for android development using Android Studio. 61 | 62 | # Built application files 63 | 64 | # Files for the ART/Dalvik VM 65 | 66 | # Java class files 67 | 68 | # Generated files 69 | 70 | # Gradle files 71 | .gradle 72 | 73 | # Signing files 74 | .signing/ 75 | 76 | # Local configuration file (sdk path, etc) 77 | 78 | # Proguard folder generated by Eclipse 79 | 80 | # Log Files 81 | 82 | # Android Studio 83 | /*/build/ 84 | /*/local.properties 85 | /*/out 86 | /*/*/build 87 | /*/*/production 88 | *.ipr 89 | *~ 90 | *.swp 91 | 92 | # Android Patch 93 | 94 | # External native build folder generated in Android Studio 2.2 and later 95 | 96 | # NDK 97 | obj/ 98 | 99 | # IntelliJ IDEA 100 | *.iws 101 | /out/ 102 | 103 | # User-specific configurations 104 | .idea/libraries/ 105 | .idea/.name 106 | .idea/compiler.xml 107 | .idea/copyright/profiles_settings.xml 108 | .idea/encodings.xml 109 | .idea/misc.xml 110 | .idea/modules.xml 111 | .idea/scopes/scope_settings.xml 112 | .idea/vcs.xml 113 | .idea/jsLibraryMappings.xml 114 | .idea/datasources.xml 115 | .idea/dataSources.ids 116 | .idea/sqlDataSources.xml 117 | .idea/dynamic.xml 118 | .idea/uiDesigner.xml 119 | 120 | # OS-specific files 121 | .DS_Store 122 | .DS_Store? 123 | ._* 124 | .Spotlight-V100 125 | .Trashes 126 | ehthumbs.db 127 | Thumbs.db 128 | 129 | # Legacy Eclipse project files 130 | .classpath 131 | .project 132 | 133 | # Mobile Tools for Java (J2ME) 134 | .mtj.tmp/ 135 | 136 | # Package Files # 137 | *.war 138 | *.ear 139 | 140 | # virtual machine crash logs (Reference: http://www.java.com/en/download/help/error_hotspot.xml) 141 | hs_err_pid* 142 | 143 | ## Plugin-specific files: 144 | 145 | # mpeltonen/sbt-idea plugin 146 | .idea_modules/ 147 | 148 | # JIRA plugin 149 | atlassian-ide-plugin.xml 150 | 151 | # Mongo Explorer plugin 152 | .idea/mongoSettings.xml 153 | 154 | # Crashlytics plugin (for Android Studio and IntelliJ) 155 | com_crashlytics_export_strings.xml 156 | crashlytics.properties 157 | crashlytics-build.properties 158 | fabric.properties 159 | 160 | ### AndroidStudio Patch ### 161 | 162 | !/gradle/wrapper/gradle-wrapper.jar 163 | 164 | ### CocoaPods ### 165 | ## CocoaPods GitIgnore Template 166 | 167 | # CocoaPods - Only use to conserve bandwidth / Save time on Pushing 168 | # - Also handy if you have a lage number of dependant pods 169 | # - AS PER https://guides.cocoapods.org/using/using-cocoapods.html NEVER IGONRE THE LOCK FILE 170 | Pods/ 171 | 172 | ### Dart ### 173 | # See https://www.dartlang.org/tools/private-files.html 174 | 175 | # Files and directories created by pub 176 | .packages 177 | .pub/ 178 | # If you're building an application, you may want to check-in your pubspec.lock 179 | pubspec.lock 180 | 181 | # Directory created by dartdoc 182 | # If you don't generate documentation locally you can remove this line. 183 | doc/api/ 184 | 185 | ### Intellij ### 186 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm 187 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 188 | 189 | # User-specific stuff: 190 | .idea/**/workspace.xml 191 | .idea/**/tasks.xml 192 | 193 | # Sensitive or high-churn files: 194 | .idea/**/dataSources/ 195 | .idea/**/dataSources.ids 196 | .idea/**/dataSources.xml 197 | .idea/**/dataSources.local.xml 198 | .idea/**/sqlDataSources.xml 199 | .idea/**/dynamic.xml 200 | .idea/**/uiDesigner.xml 201 | 202 | # Gradle: 203 | .idea/**/gradle.xml 204 | .idea/**/libraries 205 | 206 | # CMake 207 | cmake-build-debug/ 208 | 209 | # Mongo Explorer plugin: 210 | .idea/**/mongoSettings.xml 211 | 212 | ## File-based project format: 213 | 214 | ## Plugin-specific files: 215 | 216 | # IntelliJ 217 | 218 | # mpeltonen/sbt-idea plugin 219 | 220 | # JIRA plugin 221 | 222 | # Cursive Clojure plugin 223 | .idea/replstate.xml 224 | 225 | # Crashlytics plugin (for Android Studio and IntelliJ) 226 | 227 | ### Intellij Patch ### 228 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 229 | 230 | # *.iml 231 | # modules.xml 232 | # .idea/misc.xml 233 | # *.ipr 234 | 235 | # Sonarlint plugin 236 | .idea/sonarlint 237 | 238 | ### Objective-C ### 239 | # Xcode 240 | # 241 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 242 | 243 | ## Build generated 244 | DerivedData/ 245 | 246 | ## Various settings 247 | *.pbxuser 248 | !default.pbxuser 249 | *.mode1v3 250 | !default.mode1v3 251 | *.mode2v3 252 | !default.mode2v3 253 | *.perspectivev3 254 | !default.perspectivev3 255 | xcuserdata/ 256 | 257 | ## Other 258 | *.moved-aside 259 | *.xccheckout 260 | *.xcscmblueprint 261 | 262 | ## Obj-C/Swift specific 263 | *.hmap 264 | *.ipa 265 | *.dSYM.zip 266 | *.dSYM 267 | 268 | # CocoaPods - Refactored to standalone file 269 | 270 | 271 | # Carthage - Refactored to standalone file 272 | 273 | # fastlane 274 | # 275 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 276 | # screenshots whenever they are needed. 277 | # For more information about the recommended setup visit: 278 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 279 | 280 | fastlane/report.xml 281 | fastlane/Preview.html 282 | fastlane/screenshots 283 | fastlane/test_output 284 | 285 | # Code Injection 286 | # 287 | # After new code Injection tools there's a generated folder /iOSInjectionProject 288 | # https://github.com/johnno1962/injectionforxcode 289 | 290 | iOSInjectionProject/ 291 | 292 | ### Objective-C Patch ### 293 | 294 | ### Swift ### 295 | # Xcode 296 | # 297 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 298 | 299 | ## Build generated 300 | 301 | ## Various settings 302 | 303 | ## Other 304 | 305 | ## Obj-C/Swift specific 306 | 307 | ## Playgrounds 308 | timeline.xctimeline 309 | playground.xcworkspace 310 | 311 | # Swift Package Manager 312 | # 313 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 314 | # Packages/ 315 | # Package.pins 316 | .build/ 317 | 318 | # CocoaPods - Refactored to standalone file 319 | 320 | # Carthage - Refactored to standalone file 321 | 322 | # fastlane 323 | # 324 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 325 | # screenshots whenever they are needed. 326 | # For more information about the recommended setup visit: 327 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 328 | 329 | 330 | ### Xcode ### 331 | # Xcode 332 | # 333 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 334 | 335 | ## Build generated 336 | 337 | ## Various settings 338 | 339 | ## Other 340 | 341 | ### Xcode Patch ### 342 | *.xcodeproj/* 343 | !*.xcodeproj/project.pbxproj 344 | !*.xcodeproj/xcshareddata/ 345 | !*.xcworkspace/contents.xcworkspacedata 346 | /*.gcno 347 | 348 | # Build files 349 | build/ 350 | 351 | # End of https://www.gitignore.io/api/dart,swift,xcode,android,intellij,cocoapods,objective-c,androidstudio 352 | 353 | **/flutter_export_environment.sh 354 | -------------------------------------------------------------------------------- /lib/src/core/view_model/category_provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:cloud_firestore/cloud_firestore.dart'; 2 | import 'package:firebase_storage/firebase_storage.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import 'package:myntra_test_app/src/core/models/table_schemas/common_tables_schema.dart'; 6 | import 'package:myntra_test_app/src/core/helpers/url_constants.dart'; 7 | import 'package:myntra_test_app/src/core/models/common_models.dart'; 8 | 9 | class CategoryProvider with ChangeNotifier { 10 | final databaseReference = Firestore.instance; 11 | 12 | Map _categories = {}; 13 | 14 | Map get categories { 15 | return {..._categories}; 16 | } 17 | 18 | CategoryModal getCategoryBy(String id) { 19 | return _categories[id]; 20 | } 21 | 22 | void _addOrUpdate({@required CategoryModal category}) { 23 | if (_categories.containsKey(category.id)) { 24 | //Update category 25 | // print('Update category with name: ${category.name}'); 26 | _categories.update(category.id, (existingCategory) { 27 | return CategoryModal( 28 | name: existingCategory.name, 29 | id: existingCategory.id, 30 | homePageImageUrl: existingCategory.homePageImageUrl, 31 | menuIconStoragePath: existingCategory.menuIconStoragePath, 32 | menuIconImageUrl: category.menuIconImageUrl, 33 | subCategoriesIDs: existingCategory.subCategoriesIDs, 34 | subCategories: category.subCategories, 35 | ); 36 | }); 37 | } else { 38 | _categories.putIfAbsent(category.id, () { 39 | return CategoryModal( 40 | name: category.name, 41 | id: category.id, 42 | homePageImageUrl: category.homePageImageUrl, 43 | menuIconStoragePath: category.menuIconStoragePath, 44 | menuIconImageUrl: category.menuIconImageUrl, 45 | subCategoriesIDs: category.subCategoriesIDs, 46 | subCategories: category.subCategories, 47 | ); 48 | }); 49 | } 50 | } 51 | 52 | Future fetchAndSetCategories() async { 53 | if (_categories.isNotEmpty) { 54 | // notifyListeners(); 55 | return; 56 | } 57 | try { 58 | print('fetchAndSetCategories from server'); 59 | // final List loadedCategories = []; 60 | await databaseReference 61 | .collection(Tables.categories) 62 | .where(CategoryTable.isActive, isEqualTo: true) 63 | .getDocuments() 64 | .then((QuerySnapshot snapshot) { 65 | if (snapshot.documents.isEmpty) { 66 | return; 67 | } 68 | 69 | snapshot.documents.forEach((doc) { 70 | var subCategoriesIDsJson = doc[CategoryTable.subCatIDs]; 71 | List subCategoriesIDs = subCategoriesIDsJson == null 72 | ? [] 73 | : List.from(subCategoriesIDsJson); 74 | final newCat = CategoryModal( 75 | name: doc[CategoryTable.name], 76 | id: doc.documentID, 77 | homePageImageUrl: doc[CategoryTable.imageUrl], 78 | menuIconStoragePath: doc[CategoryTable.menuIconName], 79 | subCategoriesIDs: subCategoriesIDs, 80 | subCategories: [], 81 | ); 82 | _getDownloadableUrlFor(category: newCat); 83 | _addOrUpdate(category: newCat); 84 | // loadedCategories.add(newCat); 85 | }); 86 | 87 | // _categories = loadedCategories; 88 | notifyListeners(); 89 | 90 | _fetchAndSetSubCategories(); 91 | }); 92 | } catch (error) { 93 | print('fetchAndSetCategories error: ${error.message}'); 94 | throw error; 95 | } 96 | } 97 | 98 | Future _getDownloadableUrlFor({CategoryModal category}) async { 99 | try { 100 | // print('GetDownloadableUrlFor from server'); 101 | // print('${category.menuIconStoragePath}'); 102 | if (category.menuIconStoragePath == '') { 103 | return; 104 | } 105 | 106 | FirebaseStorage.instance 107 | .getReferenceFromUrl(category.menuIconStoragePath) 108 | .then((storageRef) async { 109 | String url = (await storageRef.getDownloadURL()).toString(); 110 | // print('Downloadable Url for ${category.name}: $url'); 111 | category.menuIconImageUrl = url; 112 | _addOrUpdate(category: category); 113 | }); 114 | } catch (error) { 115 | print('getDownloadableUrlFor ${category.name} error: $error'); 116 | throw error; 117 | } 118 | } 119 | 120 | Future _fetchAndSetSubCategories() async { 121 | try { 122 | print('fetchAndSetSubCategories from server'); 123 | final List loadedSubCategories = []; 124 | 125 | await databaseReference 126 | .collection(Tables.subCategories) 127 | .where(SubCategoryTable.isActive, isEqualTo: true) 128 | .getDocuments() 129 | .then((QuerySnapshot snapshot) { 130 | if (snapshot.documents.isEmpty) { 131 | print('fetchAndSetSubCategoriesFor documents: isEmpty'); 132 | return; 133 | } 134 | snapshot.documents.forEach((doc) { 135 | var subSubCategoriesIDsJson = doc[SubCategoryTable.subSubCatIDs]; 136 | // print("subSubCategoriesIDsJson: $subSubCategoriesIDsJson"); 137 | List subSubCategoriesIDs = subSubCategoriesIDsJson == null 138 | ? [] 139 | : List.from(subSubCategoriesIDsJson); 140 | 141 | // print('fetchAndSetSubCategoriesFor doc: ${doc.documentID}'); 142 | 143 | loadedSubCategories.add(SubCategoryModal( 144 | name: doc[SubCategoryTable.name], 145 | id: doc.documentID, 146 | subSubCategoriesIDs: subSubCategoriesIDs, 147 | subSubCategories: [], 148 | )); 149 | }); 150 | 151 | _categories.values.forEach((var cat) { 152 | // print('cat name: ${cat.name}'); 153 | // print('cat.subCategoriesIDs: ${cat.subCategoriesIDs}'); 154 | loadedSubCategories.forEach((subCat) { 155 | // print('subCat.id: ${subCat.id}'); 156 | if (cat.subCategoriesIDs.contains(subCat.id)) { 157 | // print( 158 | // 'cat name: ${cat.name} => add subCategory name: ${subCat.name}'); 159 | cat.subCategories.add(subCat); 160 | } 161 | }); 162 | _addOrUpdate(category: cat); 163 | // updateCategoriesArray.add(cat); 164 | }); 165 | 166 | _fetchAndSetSubSubCategories(); 167 | }); 168 | } catch (error) { 169 | print('fetchAndSetSubCategories error: $error'); 170 | throw error; 171 | } 172 | } 173 | 174 | Future _fetchAndSetSubSubCategories() async { 175 | try { 176 | print('fetchAndSetSubSubCategories from server'); 177 | final List loadedSubSubCategories = []; 178 | 179 | await databaseReference 180 | .collection(Tables.subSubCategories) 181 | .where(SubCategoryTable.isActive, isEqualTo: true) 182 | .getDocuments() 183 | .then((QuerySnapshot snapshot) { 184 | if (snapshot.documents.isEmpty) { 185 | print('fetchAndSetSubSubCategoriesFor documents: isEmpty'); 186 | return; 187 | } 188 | snapshot.documents.forEach((doc) { 189 | // print('fetchAndSetSubSubCategoriesFor doc: ${doc.documentID}'); 190 | 191 | loadedSubSubCategories.add(SubSubCategoryModal( 192 | name: doc[SubCategoryTable.name], 193 | id: doc.documentID, 194 | )); 195 | }); 196 | 197 | _categories.values.forEach((cat) { 198 | // print('cat name: ${cat.name}'); 199 | cat.subCategories.forEach((subCat) { 200 | // print('subCat.subSubCategoriesIDs: ${subCat.subSubCategoriesIDs}'); 201 | loadedSubSubCategories.forEach((var subSubCat) { 202 | // print('subCat.id: ${subCat.id}'); 203 | if (subCat.subSubCategoriesIDs.contains(subSubCat.id)) { 204 | // print( 205 | // 'cat name: ${cat.name},subCategory name: ${subCat.name} => add subSubCategory name: ${subSubCat.name}'); 206 | subCat.subSubCategories.add(subSubCat); 207 | } 208 | }); 209 | }); 210 | _addOrUpdate(category: cat); 211 | }); 212 | }); 213 | } catch (error) { 214 | print('fetchAndSetSubCategories error: $error'); 215 | throw error; 216 | } 217 | } 218 | } 219 | --------------------------------------------------------------------------------