├── 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 │ ├── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard │ └── Info.plist ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── AppFrameworkInfo.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── WorkspaceSettings.xcsettings │ │ └── IDEWorkspaceChecks.plist ├── .gitignore ├── Podfile └── Podfile.lock ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ ├── Icon-512.png │ ├── Icon-maskable-192.png │ └── Icon-maskable-512.png ├── manifest.json └── index.html ├── assets ├── fonts │ └── vazir.ttf └── images │ ├── benz.png │ ├── bmw.png │ ├── box.png │ ├── tara.png │ ├── qorfe.png │ ├── home_icon.png │ ├── home_icon2.png │ ├── besenior_logo.png │ ├── category_icon.png │ ├── category_icon2.png │ ├── bs_logo_textfield.png │ ├── person_icon2.svg │ ├── person_icon.svg │ └── amazing.svg ├── android ├── gradle.properties ├── 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 │ │ │ │ ├── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable-v21 │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── values-night │ │ │ │ │ └── styles.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ ├── example │ │ │ │ │ └── besenior_shop_course │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ │ └── besenior │ │ │ │ │ └── besenior_shop_course │ │ │ │ │ └── MainActivity.kt │ │ │ └── AndroidManifest.xml │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ └── profile │ │ │ └── AndroidManifest.xml │ └── build.gradle ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle └── build.gradle ├── macos ├── Runner │ ├── Configs │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ ├── Warnings.xcconfig │ │ └── AppInfo.xcconfig │ ├── Assets.xcassets │ │ └── AppIcon.appiconset │ │ │ ├── app_icon_128.png │ │ │ ├── app_icon_16.png │ │ │ ├── app_icon_256.png │ │ │ ├── app_icon_32.png │ │ │ ├── app_icon_512.png │ │ │ ├── app_icon_64.png │ │ │ ├── app_icon_1024.png │ │ │ └── Contents.json │ ├── AppDelegate.swift │ ├── Release.entitlements │ ├── DebugProfile.entitlements │ ├── MainFlutterWindow.swift │ └── Info.plist ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme └── Podfile ├── lib ├── common │ ├── params │ │ ├── signup_params.dart │ │ └── products_params.dart │ ├── arguments │ │ └── productsArgument.dart │ ├── blocs │ │ ├── bottom_nav_cubit │ │ │ └── bottom_nav_cubit.dart │ │ └── searchbox_cubit.dart │ ├── utils │ │ ├── custom_snackbar.dart │ │ └── prefs_operator.dart │ ├── resources │ │ └── data_state.dart │ ├── widgets │ │ ├── dot_loading_widget.dart │ │ ├── paging_loading_widget.dart │ │ ├── main_appbar.dart │ │ ├── main_wrapper.dart │ │ └── bottom_nav.dart │ └── error_handling │ │ ├── app_exception.dart │ │ └── check_exceptions.dart ├── features │ ├── feature_home │ │ ├── presentation │ │ │ ├── utils │ │ │ │ └── profile_list_model.dart │ │ │ ├── bloc │ │ │ │ ├── home_state.dart │ │ │ │ ├── home_data_status.dart │ │ │ │ └── home_cubit.dart │ │ │ ├── widgets │ │ │ │ ├── profile_list_tile.dart │ │ │ │ └── deep_links.dart │ │ │ └── screens │ │ │ │ └── profile_screen.dart │ │ ├── data │ │ │ └── data_source │ │ │ │ └── home_api_provider.dart │ │ └── repositories │ │ │ └── home_repository.dart │ ├── feature_intro │ │ ├── presentation │ │ │ ├── bloc │ │ │ │ ├── splash_cubit │ │ │ │ │ ├── connection_status.dart │ │ │ │ │ ├── splash_state.dart │ │ │ │ │ └── splash_cubit.dart │ │ │ │ └── intro_cubit │ │ │ │ │ ├── intro_state.dart │ │ │ │ │ └── intro_cubit.dart │ │ │ ├── widgets │ │ │ │ ├── get_start_btn.dart │ │ │ │ └── intro_page.dart │ │ │ └── screens │ │ │ │ ├── splash_screen.dart │ │ │ │ └── intro_main_wrapper.dart │ │ └── repositories │ │ │ └── splash_repository.dart │ ├── feature_auth │ │ ├── presentation │ │ │ ├── bloc │ │ │ │ ├── login_bloc │ │ │ │ │ ├── login_event.dart │ │ │ │ │ ├── code_check_status.dart │ │ │ │ │ ├── login_data_status.dart │ │ │ │ │ ├── login_state.dart │ │ │ │ │ └── login_bloc.dart │ │ │ │ └── signup_bloc │ │ │ │ │ ├── signup_event.dart │ │ │ │ │ ├── signup_data_status.dart │ │ │ │ │ ├── call_code_status.dart │ │ │ │ │ ├── signup_state.dart │ │ │ │ │ └── signup_bloc.dart │ │ │ └── widgets │ │ │ │ └── cutom_clippath_signup.dart │ │ ├── data │ │ │ ├── models │ │ │ │ ├── login_with_sms_model.dart │ │ │ │ ├── code_model.dart │ │ │ │ └── signup_model.dart │ │ │ └── data_source │ │ │ │ └── auth_api_provider.dart │ │ └── repositories │ │ │ └── auth_repository.dart │ └── feature_product │ │ ├── presentation │ │ ├── bloc │ │ │ ├── category_cubit │ │ │ │ ├── category_state.dart │ │ │ │ ├── category_data_status.dart │ │ │ │ └── category_cubit.dart │ │ │ └── all_poducts_cubit │ │ │ │ ├── products_data_status.dart │ │ │ │ ├── all_products_state.dart │ │ │ │ └── all_products_cubit.dart │ │ ├── screens │ │ │ ├── all_products_screen.dart │ │ │ └── category_screen.dart │ │ └── widgets │ │ │ ├── search_textfield.dart │ │ │ └── products_grid.dart │ │ ├── data │ │ ├── data_source │ │ │ ├── category_api_provider.dart │ │ │ └── product_api_provider.dart │ │ └── models │ │ │ ├── categories_model.dart │ │ │ └── all_products_model.dart │ │ └── repositories │ │ ├── category_repository.dart │ │ └── all_product_repository.dart ├── test_screen.dart ├── config │ ├── constants.dart │ ├── responsive.dart │ └── my_theme.dart ├── locator.dart └── main.dart ├── README.md ├── .gitignore ├── test └── widget_test.dart ├── .metadata ├── analysis_options.yaml └── pubspec.yaml /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/web/favicon.png -------------------------------------------------------------------------------- /assets/fonts/vazir.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/fonts/vazir.ttf -------------------------------------------------------------------------------- /assets/images/benz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/benz.png -------------------------------------------------------------------------------- /assets/images/bmw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/bmw.png -------------------------------------------------------------------------------- /assets/images/box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/box.png -------------------------------------------------------------------------------- /assets/images/tara.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/tara.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/web/icons/Icon-512.png -------------------------------------------------------------------------------- /assets/images/qorfe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/qorfe.png -------------------------------------------------------------------------------- /assets/images/home_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/home_icon.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /assets/images/home_icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/home_icon2.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /assets/images/besenior_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/besenior_logo.png -------------------------------------------------------------------------------- /assets/images/category_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/category_icon.png -------------------------------------------------------------------------------- /assets/images/category_icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/category_icon2.png -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /assets/images/bs_logo_textfield.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/assets/images/bs_logo_textfield.png -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /lib/common/params/signup_params.dart: -------------------------------------------------------------------------------- 1 | 2 | class SignUpParams { 3 | String? username; 4 | String? phoneNumber; 5 | 6 | SignUpParams(this.username, this.phoneNumber); 7 | } -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/HEAD/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mhmd435/besenior_shop_course/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/mhmd435/besenior_shop_course/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/besenior_shop_course/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.besenior_shop_course 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/besenior/besenior_shop_course/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.besenior.besenior_shop_course 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /lib/common/arguments/productsArgument.dart: -------------------------------------------------------------------------------- 1 | 2 | class ProductsArguments { 3 | final int? categoryId; 4 | final int? sellerId; 5 | final String? searchTxt; 6 | 7 | ProductsArguments({this.categoryId, this.sellerId, this.searchTxt}); 8 | } -------------------------------------------------------------------------------- /lib/common/blocs/bottom_nav_cubit/bottom_nav_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | 3 | class BottomNavCubit extends Cubit { 4 | BottomNavCubit() : super(0); 5 | 6 | changeSelectedIndex(newIndex) => emit(newIndex); 7 | } 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 6 | -------------------------------------------------------------------------------- /lib/common/blocs/searchbox_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | 3 | 4 | class SearchboxCubit extends Cubit { 5 | SearchboxCubit() : super(true); 6 | 7 | void changeVisibility(bool newValue){ 8 | emit(newValue); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/utils/profile_list_model.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class ProfileListModel { 5 | IconData iconData; 6 | String title; 7 | Function() onTap; 8 | 9 | ProfileListModel({required this.iconData,required this.title,required this.onTap}); 10 | 11 | 12 | } -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/bloc/splash_cubit/connection_status.dart: -------------------------------------------------------------------------------- 1 | part of 'splash_cubit.dart'; 2 | 3 | @immutable 4 | abstract class ConnectionStatus {} 5 | 6 | class ConnectionInitial extends ConnectionStatus {} 7 | 8 | class ConnectionOn extends ConnectionStatus {} 9 | 10 | class ConnectionOff extends ConnectionStatus {} 11 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /lib/common/params/products_params.dart: -------------------------------------------------------------------------------- 1 | 2 | class ProductsParams { 3 | int? start; 4 | int? step; 5 | int? categories; 6 | int? maxPrice; 7 | int? minPrice; 8 | String? sortBy; 9 | String? search; 10 | 11 | ProductsParams({this.start = 0, this.step = 10, this.categories, this.maxPrice, this.minPrice, this.sortBy = "date",this.search}); 12 | } -------------------------------------------------------------------------------- /lib/common/utils/custom_snackbar.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class CustomSnackBar { 5 | static showSnack(context, String message,Color color){ 6 | ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(message,style: const TextStyle(color: Colors.white, fontWeight: FontWeight.w700, fontFamily: "Vazir"),),backgroundColor: color,)); 7 | } 8 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/login_bloc/login_event.dart: -------------------------------------------------------------------------------- 1 | part of 'login_bloc.dart'; 2 | 3 | @immutable 4 | abstract class LoginEvent {} 5 | 6 | class LoadLoginSms extends LoginEvent { 7 | final String phoneNumber; 8 | LoadLoginSms(this.phoneNumber); 9 | } 10 | 11 | class LoadCodeCheck extends LoginEvent { 12 | final String code; 13 | LoadCodeCheck(this.code); 14 | } 15 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/bloc/intro_cubit/intro_state.dart: -------------------------------------------------------------------------------- 1 | part of 'intro_cubit.dart'; 2 | 3 | class IntroState { 4 | bool showGetStart; 5 | 6 | IntroState({required this.showGetStart}); 7 | 8 | IntroState copyWith({ 9 | bool? newShowGetStart, 10 | }){ 11 | return IntroState( 12 | showGetStart: newShowGetStart ?? showGetStart 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /lib/common/resources/data_state.dart: -------------------------------------------------------------------------------- 1 | 2 | abstract class DataState{ 3 | final T? data; 4 | final String? error; 5 | 6 | const DataState(this.data, this.error); 7 | } 8 | 9 | class DataSuccess extends DataState{ 10 | const DataSuccess(T data) : super(data, null); 11 | } 12 | 13 | class DataFailed extends DataState{ 14 | const DataFailed(String error) : super(null, error); 15 | } 16 | -------------------------------------------------------------------------------- /assets/images/person_icon2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/bloc/home_state.dart: -------------------------------------------------------------------------------- 1 | part of 'home_cubit.dart'; 2 | 3 | class HomeState { 4 | HomeDataStatus homeDataStatus; 5 | 6 | HomeState({ 7 | required this.homeDataStatus 8 | }); 9 | 10 | HomeState copyWith({ 11 | HomeDataStatus? newHomeDataStatus, 12 | }){ 13 | return HomeState( 14 | homeDataStatus: newHomeDataStatus ?? homeDataStatus 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/signup_bloc/signup_event.dart: -------------------------------------------------------------------------------- 1 | part of 'signup_bloc.dart'; 2 | 3 | @immutable 4 | abstract class SignupEvent {} 5 | 6 | class LoadSignUp extends SignupEvent { 7 | final SignUpParams signUpParams; 8 | 9 | LoadSignUp(this.signUpParams); 10 | } 11 | 12 | class LoadRegisterCodeCheck extends SignupEvent { 13 | final String mobile; 14 | LoadRegisterCodeCheck(this.mobile); 15 | } 16 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/bloc/intro_cubit/intro_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | part 'intro_state.dart'; 5 | 6 | class IntroCubit extends Cubit { 7 | IntroCubit() : super( 8 | IntroState( 9 | showGetStart: false, 10 | )); 11 | 12 | void changeGetStart(bool value) => emit(state.copyWith(newShowGetStart: value)); 13 | } 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /lib/test_screen.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/common/widgets/bottom_nav.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | class TestScreen extends StatelessWidget { 6 | static const routeName = "/test_screen"; 7 | 8 | TestScreen({Key? key}) : super(key: key); 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return Scaffold( 13 | appBar: AppBar(), 14 | 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/bloc/splash_cubit/splash_state.dart: -------------------------------------------------------------------------------- 1 | part of 'splash_cubit.dart'; 2 | 3 | class SplashState { 4 | ConnectionStatus connectionStatus; 5 | 6 | SplashState({required this.connectionStatus}); 7 | 8 | SplashState copyWith({ 9 | ConnectionStatus? newConnectionStatus, 10 | }){ 11 | return SplashState( 12 | connectionStatus: newConnectionStatus ?? connectionStatus 13 | ); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /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/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/bloc/category_cubit/category_state.dart: -------------------------------------------------------------------------------- 1 | part of 'category_cubit.dart'; 2 | 3 | class CategoryState { 4 | CategoryDataStatus categoryDataStatus; 5 | 6 | CategoryState({required this.categoryDataStatus}); 7 | 8 | CategoryState copyWith({ 9 | CategoryDataStatus? newCategoryDataStatus, 10 | }){ 11 | return CategoryState( 12 | categoryDataStatus: newCategoryDataStatus ?? categoryDataStatus 13 | ); 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/bloc/home_data_status.dart: -------------------------------------------------------------------------------- 1 | part of 'home_cubit.dart'; 2 | 3 | @immutable 4 | abstract class HomeDataStatus {} 5 | 6 | class HomeDataInitial extends HomeDataStatus {} 7 | 8 | class HomeDataLoading extends HomeDataStatus {} 9 | 10 | class HomeDataCompleted extends HomeDataStatus { 11 | final HomeModel homeModel; 12 | HomeDataCompleted(this.homeModel); 13 | } 14 | 15 | class HomeDataError extends HomeDataStatus { 16 | final String errorMessage; 17 | HomeDataError(this.errorMessage); 18 | } 19 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/config/constants.dart: -------------------------------------------------------------------------------- 1 | 2 | class Constants{ 3 | static const baseUrl = "https://shopbs.besenior.ir/api/v1"; 4 | // static const tapsellAppID = "gporbsshqsbpkbkmnmrranjjijddhmenocqtcagegeqgesliijajposlophsptcmfgqsif"; 5 | // static const bannerAd = "6301c89827a9cb5ebea007b0"; 6 | // static const championsBannerAd = "6301d64e9508ce4428a853d2"; 7 | // static const buildVideoAd = "6301d8bc9e17585c89adbf03"; 8 | // static const singleRuneBannerAd = "6301e0d28f178c2d6268df95"; 9 | // static const itemsBannerAd = "6301ef6e8f178c2d6268dfa3"; 10 | 11 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/login_bloc/code_check_status.dart: -------------------------------------------------------------------------------- 1 | part of 'login_bloc.dart'; 2 | 3 | @immutable 4 | abstract class CodeCheckStatus {} 5 | 6 | class CodeCheckInitial extends CodeCheckStatus {} 7 | 8 | class CodeCheckLoading extends CodeCheckStatus {} 9 | 10 | class CodeCheckCompleted extends CodeCheckStatus { 11 | final CodeModel codeModel; 12 | CodeCheckCompleted(this.codeModel); 13 | } 14 | 15 | class CodeCheckError extends CodeCheckStatus { 16 | final String errorMessage; 17 | CodeCheckError(this.errorMessage); 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/signup_bloc/signup_data_status.dart: -------------------------------------------------------------------------------- 1 | part of 'signup_bloc.dart'; 2 | 3 | @immutable 4 | abstract class SignUpDataStatus {} 5 | 6 | class SignUpDataInitial extends SignUpDataStatus {} 7 | 8 | class SignUpDataLoading extends SignUpDataStatus {} 9 | 10 | class SignUpCompleted extends SignUpDataStatus { 11 | final SignupModel signupModel; 12 | SignUpCompleted(this.signupModel); 13 | } 14 | 15 | class SignUpDataError extends SignUpDataStatus { 16 | final String errorMessage; 17 | SignUpDataError(this.errorMessage); 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/signup_bloc/call_code_status.dart: -------------------------------------------------------------------------------- 1 | part of 'signup_bloc.dart'; 2 | 3 | 4 | @immutable 5 | abstract class CallCodeStatus {} 6 | 7 | class CallCodeInitial extends CallCodeStatus {} 8 | 9 | class CallCodeLoading extends CallCodeStatus {} 10 | 11 | class CallCodeCompleted extends CallCodeStatus { 12 | final LoginWithSmsModel loginWithSmsModel; 13 | CallCodeCompleted(this.loginWithSmsModel); 14 | } 15 | 16 | class CallCodeError extends CallCodeStatus { 17 | final String errorMessage; 18 | CallCodeError(this.errorMessage); 19 | } 20 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/login_bloc/login_data_status.dart: -------------------------------------------------------------------------------- 1 | part of 'login_bloc.dart'; 2 | 3 | 4 | @immutable 5 | abstract class LoginDataStatus {} 6 | 7 | class LoginDataInitial extends LoginDataStatus {} 8 | 9 | class LoginDataLoading extends LoginDataStatus {} 10 | 11 | class LoginDataCompleted extends LoginDataStatus { 12 | final LoginWithSmsModel loginWithSmsModel; 13 | LoginDataCompleted(this.loginWithSmsModel); 14 | } 15 | 16 | class LoginDataError extends LoginDataStatus { 17 | final String errorMessage; 18 | LoginDataError(this.errorMessage); 19 | } 20 | -------------------------------------------------------------------------------- /assets/images/person_icon.svg: -------------------------------------------------------------------------------- 1 | ionicons-v5-j -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/bloc/category_cubit/category_data_status.dart: -------------------------------------------------------------------------------- 1 | part of 'category_cubit.dart'; 2 | 3 | @immutable 4 | abstract class CategoryDataStatus {} 5 | 6 | class CategoryDataInitial extends CategoryDataStatus {} 7 | 8 | class CategoryDataLoading extends CategoryDataStatus {} 9 | 10 | class CategoryDataCompleted extends CategoryDataStatus { 11 | final CategoriesModel categoriesModel; 12 | CategoryDataCompleted(this.categoriesModel); 13 | } 14 | 15 | class CategoryDataError extends CategoryDataStatus { 16 | final String errorMessage; 17 | CategoryDataError(this.errorMessage); 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # besenior_shop_course 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/bloc/all_poducts_cubit/products_data_status.dart: -------------------------------------------------------------------------------- 1 | part of 'all_products_cubit.dart'; 2 | 3 | @immutable 4 | abstract class ProductsDataStatus {} 5 | 6 | class ProductsDataInitial extends ProductsDataStatus {} 7 | 8 | class ProductsDataLoading extends ProductsDataStatus {} 9 | 10 | class ProductsDataCompleted extends ProductsDataStatus { 11 | final AllProductsModel allProductsModel; 12 | ProductsDataCompleted(this.allProductsModel); 13 | } 14 | 15 | class ProductsDataError extends ProductsDataStatus { 16 | final String errorMessage; 17 | ProductsDataError(this.errorMessage); 18 | } 19 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/login_bloc/login_state.dart: -------------------------------------------------------------------------------- 1 | part of 'login_bloc.dart'; 2 | 3 | class LoginState { 4 | LoginDataStatus loginDataStatus; 5 | CodeCheckStatus codeCheckStatus; 6 | 7 | LoginState({ 8 | required this.loginDataStatus, 9 | required this.codeCheckStatus, 10 | }); 11 | 12 | LoginState copyWith({ 13 | LoginDataStatus? newLoginDataStatus, 14 | CodeCheckStatus? newCodeCheckStatus, 15 | }){ 16 | return LoginState( 17 | loginDataStatus: newLoginDataStatus ?? loginDataStatus, 18 | codeCheckStatus: newCodeCheckStatus ?? codeCheckStatus, 19 | ); 20 | } 21 | } 22 | 23 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/signup_bloc/signup_state.dart: -------------------------------------------------------------------------------- 1 | part of 'signup_bloc.dart'; 2 | 3 | class SignupState { 4 | SignUpDataStatus signUpDataStatus; 5 | CallCodeStatus callCodeStatus; 6 | 7 | SignupState({ 8 | required this.signUpDataStatus, 9 | required this.callCodeStatus, 10 | }); 11 | 12 | SignupState copyWith({ 13 | SignUpDataStatus? newSignUpDataStatus, 14 | CallCodeStatus? newCallCodeStatus, 15 | }){ 16 | return SignupState( 17 | signUpDataStatus: newSignUpDataStatus ?? signUpDataStatus, 18 | callCodeStatus: newCallCodeStatus ?? callCodeStatus, 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /lib/common/widgets/dot_loading_widget.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:loading_animation_widget/loading_animation_widget.dart'; 4 | 5 | class DotLoadingWidget extends StatelessWidget { 6 | final double size; 7 | const DotLoadingWidget({Key? key,required this.size}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Directionality( 12 | textDirection: TextDirection.ltr, 13 | child: Center( 14 | child: LoadingAnimationWidget.prograssiveDots( 15 | size: size, 16 | color: Colors.redAccent, 17 | ), 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/common/widgets/paging_loading_widget.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:loading_animation_widget/loading_animation_widget.dart'; 4 | 5 | class PagingLoadingWidget extends StatelessWidget { 6 | final double size; 7 | const PagingLoadingWidget({Key? key,required this.size}) : super(key: key); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return Directionality( 12 | textDirection: TextDirection.ltr, 13 | child: Center( 14 | child: LoadingAnimationWidget.threeArchedCircle( 15 | size: size, 16 | color: Colors.redAccent, 17 | ), 18 | ), 19 | ); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = besenior_shop_course 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.besenior.beseniorShopCourse 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2022 com.besenior. All rights reserved. 15 | -------------------------------------------------------------------------------- /lib/features/feature_product/data/data_source/category_api_provider.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:developer'; 3 | 4 | import 'package:besenior_shop_course/common/error_handling/check_exceptions.dart'; 5 | import 'package:besenior_shop_course/config/constants.dart'; 6 | import 'package:dio/dio.dart'; 7 | 8 | class CategoryApiProvider { 9 | Dio dio; 10 | CategoryApiProvider(this.dio); 11 | 12 | dynamic callCategories() async{ 13 | try{ 14 | final response = await dio.get( 15 | "${Constants.baseUrl}/categories/tree" 16 | ); 17 | log(response.toString()); 18 | return response; 19 | }on DioError catch(e){ 20 | return CheckExceptions.response(e.response!); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/widgets/cutom_clippath_signup.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/cupertino.dart'; 3 | 4 | class CustomClipPathSignUp extends CustomClipper { 5 | 6 | @override 7 | Path getClip(Size size) { 8 | double width = size.width; 9 | double height = size.height; 10 | 11 | final path = Path(); 12 | 13 | path.lineTo(0, height - 50); 14 | path.quadraticBezierTo(width * 0.5, height, width, height - 50); 15 | path.lineTo(width, 0); 16 | path.close(); 17 | 18 | 19 | 20 | return path; 21 | } 22 | 23 | @override 24 | bool shouldReclip(covariant CustomClipper oldClipper) { 25 | // TODO: implement shouldReclip 26 | return true; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/common/widgets/main_appbar.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class MainAppbar extends StatelessWidget with PreferredSizeWidget{ 5 | final String title; 6 | const MainAppbar({Key? key, required this.title}) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | return AppBar( 11 | iconTheme: const IconThemeData( 12 | color: Colors.black 13 | ), 14 | backgroundColor: Colors.white, 15 | centerTitle: true, 16 | title: Text( 17 | title, 18 | style: const TextStyle(color: Colors.black), 19 | ), 20 | ); 21 | } 22 | 23 | @override 24 | // TODO: implement preferredSize 25 | Size get preferredSize => const Size.fromHeight(60); 26 | } 27 | -------------------------------------------------------------------------------- /lib/features/feature_home/data/data_source/home_api_provider.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:developer'; 3 | 4 | import 'package:besenior_shop_course/common/error_handling/check_exceptions.dart'; 5 | import 'package:besenior_shop_course/config/constants.dart'; 6 | import 'package:dio/dio.dart'; 7 | 8 | class HomeApiProvider { 9 | Dio dio; 10 | HomeApiProvider(this.dio); 11 | 12 | 13 | dynamic callHomeData(lat, lon) async { 14 | final response = await dio.get( 15 | "${Constants.baseUrl}/mainData", 16 | queryParameters: { 17 | "lat" : lat, 18 | "long" : lon, 19 | } 20 | ).onError((DioError error, stackTrace){ 21 | return CheckExceptions.response(error.response!); 22 | }); 23 | 24 | log(response.toString()); 25 | 26 | return response; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /lib/features/feature_intro/repositories/splash_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:io'; 3 | 4 | import 'package:connectivity_plus/connectivity_plus.dart'; 5 | 6 | class SplashRepository { 7 | 8 | Future checkConnectivity() async { 9 | 10 | // try { 11 | // final result = await InternetAddress.lookup('example.com'); 12 | // return result.isNotEmpty && result[0].rawAddress.isNotEmpty; 13 | // } on SocketException catch (_) { 14 | // return false; 15 | // } 16 | 17 | var connectivityResult = await (Connectivity().checkConnectivity()); 18 | if (connectivityResult == ConnectivityResult.mobile) { 19 | return true; 20 | } else if (connectivityResult == ConnectivityResult.wifi) { 21 | return true; 22 | }else{ 23 | return false; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import connectivity_plus 9 | import location 10 | import path_provider_foundation 11 | import shared_preferences_foundation 12 | import sqflite 13 | 14 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 15 | ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin")) 16 | LocationPlugin.register(with: registry.registrar(forPlugin: "LocationPlugin")) 17 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 18 | SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) 19 | SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) 20 | } 21 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/widgets/profile_list_tile.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class ProfileListTile extends StatelessWidget { 5 | final IconData? iconData; 6 | final String title; 7 | final Function() onTap; 8 | final bool isLast; 9 | const ProfileListTile({Key? key, this.iconData = Icons.add,required this.title, required this.onTap,required this.isLast}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return ListTile( 14 | onTap: onTap, 15 | title: Text(title, style: TextStyle(color: (isLast) ? Colors.red : Colors.grey.shade700,fontWeight: FontWeight.bold,fontSize: 14),), 16 | leading: Icon(iconData, color: (isLast) ? Colors.red : Colors.grey.shade700,), 17 | trailing: Icon(Icons.arrow_right, color: (isLast) ? Colors.red : Colors.grey.shade700,), 18 | ); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/bloc/splash_cubit/splash_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:besenior_shop_course/features/feature_intro/repositories/splash_repository.dart'; 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | part 'splash_state.dart'; 6 | part 'connection_status.dart'; 7 | 8 | class SplashCubit extends Cubit { 9 | SplashRepository splashRepository = SplashRepository(); 10 | 11 | SplashCubit() : super( 12 | SplashState( 13 | connectionStatus: ConnectionInitial() 14 | )); 15 | 16 | void checkConnectionEvent() async { 17 | emit(state.copyWith(newConnectionStatus: ConnectionInitial())); 18 | 19 | bool isConnect = await splashRepository.checkConnectivity(); 20 | 21 | if(isConnect){ 22 | emit(state.copyWith(newConnectionStatus: ConnectionOn())); 23 | }else{ 24 | emit(state.copyWith(newConnectionStatus: ConnectionOff())); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/features/feature_product/repositories/category_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/features/feature_product/data/models/categories_model.dart'; 3 | import 'package:dio/dio.dart'; 4 | 5 | import '../../../common/error_handling/app_exception.dart'; 6 | import '../../../common/error_handling/check_exceptions.dart'; 7 | import '../../../common/resources/data_state.dart'; 8 | import '../data/data_source/category_api_provider.dart'; 9 | 10 | class CategoryRepository { 11 | CategoryApiProvider apiProvider; 12 | CategoryRepository(this.apiProvider); 13 | 14 | Future> fetchCategoryData() async { 15 | try{ 16 | Response response = await apiProvider.callCategories(); 17 | final CategoriesModel categoriesModel = CategoriesModel.fromJson(response.data); 18 | return DataSuccess(categoriesModel); 19 | } on AppException catch(e){ 20 | return await CheckExceptions.getError(e); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/bloc/all_poducts_cubit/all_products_state.dart: -------------------------------------------------------------------------------- 1 | part of 'all_products_cubit.dart'; 2 | 3 | class AllProductsState { 4 | ProductsDataStatus productsDataStatus; 5 | List allProducts; 6 | int nextStart; 7 | bool isLoadingPaging; 8 | 9 | AllProductsState({ 10 | required this.productsDataStatus, 11 | required this.allProducts, 12 | required this.nextStart, 13 | required this.isLoadingPaging, 14 | }); 15 | 16 | AllProductsState copyWith({ 17 | ProductsDataStatus? newProductsDataStatus, 18 | List? newAllProducts, 19 | int? newNextStart, 20 | bool? newIsLoadingPaging, 21 | }){ 22 | return AllProductsState( 23 | productsDataStatus: newProductsDataStatus ?? productsDataStatus, 24 | allProducts: newAllProducts ?? allProducts, 25 | nextStart: newNextStart ?? nextStart, 26 | isLoadingPaging: newIsLoadingPaging ?? isLoadingPaging, 27 | ); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/features/feature_home/repositories/home_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:besenior_shop_course/common/error_handling/app_exception.dart'; 4 | import 'package:besenior_shop_course/common/error_handling/check_exceptions.dart'; 5 | import 'package:besenior_shop_course/features/feature_home/data/data_source/home_api_provider.dart'; 6 | import 'package:besenior_shop_course/features/feature_home/data/models/home_model.dart'; 7 | import 'package:dio/dio.dart'; 8 | 9 | import '../../../common/resources/data_state.dart'; 10 | 11 | class HomeRepository { 12 | HomeApiProvider apiProvider; 13 | HomeRepository(this.apiProvider); 14 | 15 | Future> fetchHomeData(lat, lon) async { 16 | try{ 17 | Response response = await apiProvider.callHomeData(lat, lon); 18 | final HomeModel homeModel = HomeModel.fromJson(response.data); 19 | return DataSuccess(homeModel); 20 | } on AppException catch(e){ 21 | return await CheckExceptions.getError(e); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "besenior_shop_course", 3 | "short_name": "besenior_shop_course", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/bloc/home_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:besenior_shop_course/common/resources/data_state.dart'; 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | import '../../data/models/home_model.dart'; 6 | import '../../repositories/home_repository.dart'; 7 | 8 | part 'home_state.dart'; 9 | part 'home_data_status.dart'; 10 | 11 | class HomeCubit extends Cubit { 12 | HomeRepository homeRepository; 13 | HomeCubit(this.homeRepository) : super(HomeState(homeDataStatus: HomeDataLoading())); 14 | 15 | Future callHomeDataEvent(lat, lon) async { 16 | emit(state.copyWith(newHomeDataStatus: HomeDataLoading())); 17 | 18 | DataState dataState = await homeRepository.fetchHomeData(lat, lon); 19 | 20 | if(dataState is DataSuccess){ 21 | /// emit completed 22 | emit(state.copyWith(newHomeDataStatus: HomeDataCompleted(dataState.data))); 23 | } 24 | 25 | if(dataState is DataFailed){ 26 | /// emit error 27 | emit(state.copyWith(newHomeDataStatus: HomeDataError(dataState.error!))); 28 | } 29 | 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/features/feature_product/data/data_source/product_api_provider.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dio/dio.dart'; 3 | 4 | import '../../../../common/error_handling/check_exceptions.dart'; 5 | import '../../../../common/params/products_params.dart'; 6 | import '../../../../config/constants.dart'; 7 | 8 | class ProductsApiProvider { 9 | Dio dio; 10 | ProductsApiProvider(this.dio); 11 | 12 | dynamic callAllProducts(ProductsParams productsParams) async { 13 | try{ 14 | 15 | print(productsParams.categories); 16 | 17 | final response = await dio.post( 18 | "${Constants.baseUrl}/products", 19 | data: { 20 | "start": productsParams.start, 21 | "step": productsParams.step, 22 | "categories": productsParams.categories, 23 | "maxPrice": productsParams.maxPrice, 24 | "minPrice": productsParams.minPrice, 25 | "sortby": productsParams.sortBy, 26 | 'search': productsParams.search 27 | }, 28 | ); 29 | 30 | print(response); 31 | return response; 32 | } on DioError catch(e) { 33 | print(e.toString()); 34 | return CheckExceptions.response(e.response!); 35 | } 36 | 37 | } 38 | } -------------------------------------------------------------------------------- /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 in the flutter_test package. 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:besenior_shop_course/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(const 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 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/bloc/category_cubit/category_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | import '../../../../../common/resources/data_state.dart'; 5 | import '../../../data/models/categories_model.dart'; 6 | import '../../../repositories/category_repository.dart'; 7 | 8 | part 'category_state.dart'; 9 | part 'category_data_status.dart'; 10 | 11 | class CategoryCubit extends Cubit { 12 | CategoryRepository categoryRepository; 13 | CategoryCubit(this.categoryRepository) : super(CategoryState(categoryDataStatus: CategoryDataLoading())); 14 | 15 | 16 | Future loadCategoryEvent() async { 17 | /// emit loading 18 | emit(state.copyWith(newCategoryDataStatus: CategoryDataLoading())); 19 | 20 | DataState dataState = await categoryRepository.fetchCategoryData(); 21 | 22 | if(dataState is DataSuccess){ 23 | /// emit completed 24 | emit(state.copyWith(newCategoryDataStatus: CategoryDataCompleted(dataState.data))); 25 | } 26 | 27 | if(dataState is DataFailed){ 28 | /// emit error 29 | emit(state.copyWith(newCategoryDataStatus: CategoryDataError(dataState.error!))); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/common/utils/prefs_operator.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import '../../locator.dart'; 4 | 5 | class PrefsOperator { 6 | late SharedPreferences sharedPreferences; 7 | PrefsOperator() { 8 | sharedPreferences = locator(); 9 | } 10 | 11 | 12 | saveUserData(token, userName, mobile) async { 13 | sharedPreferences.setString("user_token", token); 14 | sharedPreferences.setString("user_name", userName); 15 | sharedPreferences.setString("user_mobile", mobile); 16 | sharedPreferences.setBool("loggedIn", true); 17 | } 18 | 19 | getUserToken() async { 20 | final String? userToken = sharedPreferences.getString('user_token'); 21 | return userToken; 22 | } 23 | 24 | changeIntroState() async { 25 | sharedPreferences.setBool("showIntro", false); 26 | } 27 | 28 | Future getIntroState() async { 29 | return sharedPreferences.getBool("showIntro") ?? true; 30 | } 31 | 32 | Future getLoggedIn() async { 33 | return sharedPreferences.getBool("loggedIn") ?? false; 34 | } 35 | 36 | Future logout() async { 37 | sharedPreferences.clear(); 38 | sharedPreferences.setBool("showIntro", false); 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/data/models/login_with_sms_model.dart: -------------------------------------------------------------------------------- 1 | /// message : "کد با موفقیت ارسال شد" 2 | /// status : "success" 3 | /// data : {"code":963036,"hash":""} 4 | 5 | class LoginWithSmsModel { 6 | LoginWithSmsModel({ 7 | this.message, 8 | this.status, 9 | this.data,}); 10 | 11 | LoginWithSmsModel.fromJson(dynamic json) { 12 | message = json['message']; 13 | status = json['status']; 14 | data = json['data'] != null ? Data.fromJson(json['data']) : null; 15 | } 16 | String? message; 17 | String? status; 18 | Data? data; 19 | 20 | Map toJson() { 21 | final map = {}; 22 | map['message'] = message; 23 | map['status'] = status; 24 | if (data != null) { 25 | map['data'] = data?.toJson(); 26 | } 27 | return map; 28 | } 29 | 30 | } 31 | 32 | /// code : 963036 33 | /// hash : "" 34 | 35 | class Data { 36 | Data({ 37 | this.code, 38 | this.hash,}); 39 | 40 | Data.fromJson(dynamic json) { 41 | code = json['code']; 42 | hash = json['hash']; 43 | } 44 | int? code; 45 | String? hash; 46 | 47 | Map toJson() { 48 | final map = {}; 49 | map['code'] = code; 50 | map['hash'] = hash; 51 | return map; 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/data/models/code_model.dart: -------------------------------------------------------------------------------- 1 | /// access_token : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvbml5YXouc2hvcFwvYXBpXC92MVwvYXV0aFwvbG9naW5XaXRoU21zXC9jaGVjayIsImlhdCI6MTY2NTMxMjA3NywiZXhwIjoxNjY1MzE1Njc3LCJuYmYiOjE2NjUzMTIwNzcsImp0aSI6IlFJZWgxOXMzWXh0Q3lLN3ciLCJzdWIiOjksInBydiI6Ijg3ZTBhZjFlZjlmZDE1ODEyZmRlYzk3MTUzYTE0ZTBiMDQ3NTQ2YWEifQ.dY6P6LFVtw6XwB50nG_pcnruUkPo9ddvCdbaLpnkBvc" 2 | /// token_type : "bearer" 3 | /// expires_in : 3600 4 | 5 | class CodeModel { 6 | CodeModel({ 7 | this.accessToken, 8 | this.name, 9 | this.mobile, 10 | this.tokenType, 11 | this.expiresIn,}); 12 | 13 | CodeModel.fromJson(dynamic json) { 14 | accessToken = json['access_token']; 15 | name = json['name']; 16 | mobile = json['mobile']; 17 | tokenType = json['token_type']; 18 | expiresIn = json['expires_in']; 19 | } 20 | String? accessToken; 21 | String? name; 22 | String? mobile; 23 | String? tokenType; 24 | int? expiresIn; 25 | 26 | Map toJson() { 27 | final map = {}; 28 | map['access_token'] = accessToken; 29 | map['name'] = name; 30 | map['mobile'] = mobile; 31 | map['token_type'] = tokenType; 32 | map['expires_in'] = expiresIn; 33 | return map; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/screens/all_products_screen.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/features/feature_product/presentation/bloc/all_poducts_cubit/all_products_cubit.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | 6 | import '../../../../common/arguments/productsArgument.dart'; 7 | import '../../../../common/widgets/main_appbar.dart'; 8 | import '../../../../locator.dart'; 9 | import '../widgets/products_grid.dart'; 10 | 11 | class AllProductsScreen extends StatelessWidget { 12 | static const routeName = '/all_product_screen'; 13 | 14 | const AllProductsScreen({Key? key}) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | 19 | /// get categoryId 20 | final arg = ModalRoute.of(context)!.settings.arguments as ProductsArguments; 21 | 22 | return MultiBlocProvider( 23 | providers: [ 24 | BlocProvider(create: (_) => AllProductsCubit(locator())), 25 | // BlocProvider(create: (_) => FilterCubit()), 26 | ], 27 | child: Scaffold( 28 | 29 | appBar: const MainAppbar(title: 'محصولات',), 30 | 31 | body: ProductsGrid(categoryId: arg.categoryId, searchText: arg.searchTxt, sellerId: arg.sellerId,), 32 | ), 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/common/error_handling/app_exception.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dio/dio.dart'; 3 | 4 | class AppException implements Exception { 5 | final message; 6 | Response? response; 7 | 8 | AppException({required this.message,this.response}); 9 | 10 | String getMessage() { 11 | return "$message"; 12 | } 13 | } 14 | 15 | class ServerException extends AppException { 16 | ServerException({String? message}) : super(message: message ?? "مشکلی پیش آمده لطفا دوباره امتحان کنید."); 17 | } 18 | 19 | class NotFoundException extends AppException { 20 | NotFoundException({String? message}) : super(message: message ?? "صفحه مورد نظر یافت نشد."); 21 | } 22 | 23 | class DataParsingException extends AppException { 24 | DataParsingException({String? message}) : super(message: message ?? "Data has Corrupted"); 25 | } 26 | 27 | class BadRequestException extends AppException { 28 | BadRequestException({String? message,Response? response}) : super(message: message ?? "bad request exception.",response: response); 29 | } 30 | 31 | class FetchDataException extends AppException { 32 | FetchDataException({String? message}) : super(message: message ?? "please check your connection..."); 33 | } 34 | 35 | class UnauthorisedException extends AppException { 36 | UnauthorisedException({String? message}) : super(message: message ?? "token has been expired."); 37 | } 38 | -------------------------------------------------------------------------------- /lib/config/responsive.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class Responsive extends StatelessWidget { 5 | final Widget mobile; 6 | final Widget? tablet; 7 | final Widget? desktop; 8 | 9 | const Responsive({ 10 | super.key, 11 | required this.mobile, 12 | this.tablet, 13 | this.desktop, 14 | }); 15 | 16 | /// This isMobile, isTablet, isDesktop helep us later 17 | static bool isMobile(BuildContext context) => 18 | MediaQuery.of(context).size.width < 600; 19 | 20 | static bool isTablet(BuildContext context) => 21 | MediaQuery.of(context).size.width < 1100 && 22 | MediaQuery.of(context).size.width >= 600; 23 | 24 | static bool isDesktop(BuildContext context) => 25 | MediaQuery.of(context).size.width >= 1100; 26 | 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | final Size size = MediaQuery.of(context).size; 31 | /// If our width is more than 1100 then we consider it a desktop 32 | if (size.width >= 1100 && desktop != null) { 33 | return desktop!; 34 | } 35 | /// If width it less then 1100 and more then 850 we consider it as tablet 36 | else if (size.width >= 600 && tablet != null) { 37 | return tablet!; 38 | } 39 | /// Or less then that we called it mobile 40 | else { 41 | return mobile; 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/features/feature_product/repositories/all_product_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/common/error_handling/check_exceptions.dart'; 3 | import 'package:dio/dio.dart'; 4 | 5 | import '../../../common/error_handling/app_exception.dart'; 6 | import '../../../common/params/products_params.dart'; 7 | import '../../../common/resources/data_state.dart'; 8 | import '../data/data_source/product_api_provider.dart'; 9 | import '../data/models/all_products_model.dart'; 10 | 11 | class AllProductsRepository { 12 | ProductsApiProvider apiProvider; 13 | AllProductsRepository(this.apiProvider); 14 | 15 | Future fetchAllProductsData(ProductsParams productsParams) async { 16 | try{ 17 | // convert json to models class 18 | Response response = await apiProvider.callAllProducts(productsParams); 19 | final AllProductsModel allProductsModel = AllProductsModel.fromJson(response.data); 20 | return DataSuccess(allProductsModel); 21 | } on AppException catch(e){ 22 | print(e); 23 | return CheckExceptions.getError(e); 24 | } 25 | } 26 | 27 | Future> fetchAllProductsDataSearch(ProductsParams productsParams) async { 28 | Response response = await apiProvider.callAllProducts(productsParams); 29 | final AllProductsModel allProductsModel = AllProductsModel.fromJson(response.data); 30 | return allProductsModel.data![0].products!; 31 | } 32 | } -------------------------------------------------------------------------------- /lib/common/error_handling/check_exceptions.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dio/dio.dart'; 3 | import '../resources/data_state.dart'; 4 | import 'app_exception.dart'; 5 | 6 | class CheckExceptions { 7 | 8 | static dynamic response(Response response) { 9 | switch (response.statusCode) { 10 | case 200: 11 | return response; 12 | case 400: 13 | throw BadRequestException(response: response); 14 | case 401: 15 | throw UnauthorisedException(); 16 | case 404: 17 | throw NotFoundException(); 18 | case 500: 19 | throw ServerException(); 20 | default: 21 | throw FetchDataException(message: "${response.statusCode}fetch exception"); 22 | } 23 | } 24 | 25 | static dynamic getError(AppException appException) async { 26 | switch (appException.runtimeType) { 27 | /// return error came from server 28 | case BadRequestException: 29 | return DataFailed(appException.message); 30 | 31 | case NotFoundException: 32 | return DataFailed(appException.message); 33 | /// get refresh token and call api again 34 | case UnauthorisedException: 35 | return DataFailed(appException.message); 36 | 37 | /// server error 38 | case ServerException: 39 | return DataFailed(appException.message); 40 | 41 | /// dio or timeout and etc error 42 | case FetchDataException: 43 | return DataFailed(appException.message); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.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 flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled. 5 | 6 | version: 7 | revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 17 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 18 | - platform: android 19 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 20 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 21 | - platform: ios 22 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 23 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 24 | - platform: macos 25 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 26 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 27 | - platform: web 28 | create_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 29 | base_revision: e3c29ec00c9c825c891d75054c63fcc46454dca1 30 | 31 | # User provided section 32 | 33 | # List of Local paths (relative to this file) that should be 34 | # ignored by the migrate tool. 35 | # 36 | # Files that are not part of the templates will be ignored by default. 37 | unmanaged_files: 38 | - 'lib/main.dart' 39 | - 'ios/Runner.xcodeproj/project.pbxproj' 40 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/widgets/deep_links.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:cached_network_image/cached_network_image.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:shimmer/shimmer.dart'; 5 | 6 | import '../../../../common/widgets/dot_loading_widget.dart'; 7 | import '../../../../config/responsive.dart'; 8 | 9 | class DeepLinks extends StatelessWidget { 10 | final String image; 11 | final String title; 12 | const DeepLinks({Key? key, required this.image, required this.title}) : super(key: key); 13 | 14 | @override 15 | Widget build(BuildContext context) { 16 | var width = MediaQuery.of(context).size.width; 17 | 18 | 19 | return Column( 20 | crossAxisAlignment: CrossAxisAlignment.center, 21 | children: [ 22 | SizedBox( 23 | width: 70, 24 | child: ClipRRect( 25 | borderRadius: BorderRadius.circular(15), 26 | child: CachedNetworkImage( 27 | imageUrl: image, 28 | placeholder: (context, string){ 29 | return Shimmer.fromColors( 30 | baseColor: Colors.white, 31 | highlightColor: Colors.grey, 32 | child: const SizedBox(height: 50,width: 50,)); 33 | }, 34 | fit: BoxFit.cover, 35 | useOldImageOnUrlChange: true, 36 | ), 37 | ), 38 | ), 39 | const SizedBox(height: 5,), 40 | Text(title, style: TextStyle(fontFamily: 'Vazir', fontSize: Responsive.isMobile(context) ? 11 : 18),) 41 | ], 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/features/feature_home/presentation/screens/profile_screen.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/features/feature_home/presentation/widgets/profile_list_tile.dart'; 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | import '../utils/profile_list_model.dart'; 7 | 8 | class ProfileScreen extends StatelessWidget { 9 | ProfileScreen({Key? key}) : super(key: key); 10 | 11 | List profileList = [ 12 | ProfileListModel(iconData: Icons.person, title: "حساب کاربری شخصی", onTap: (){print("item1");}), 13 | ProfileListModel(iconData: Icons.shopping_bag_outlined, title: "حساب کاربری فروشگاهی", onTap: (){print("item2");}), 14 | ProfileListModel(iconData: CupertinoIcons.archivebox_fill, title: "سفارشات", onTap: (){print("item3");}), 15 | ProfileListModel(iconData: Icons.home_work, title: "آدرس من", onTap: (){print("item4");}), 16 | ProfileListModel(iconData: Icons.support_agent, title: "پشتیبانی", onTap: (){print("item5");}), 17 | ProfileListModel(iconData: Icons.exit_to_app, title: "خروج از حساب", onTap: (){print("item6");}), 18 | ]; 19 | 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Column( 24 | mainAxisAlignment: MainAxisAlignment.center, 25 | children: [ 26 | ListView.builder( 27 | shrinkWrap: true, 28 | itemCount: profileList.length, 29 | itemBuilder: (context, index){ 30 | return ProfileListTile( 31 | iconData: profileList[index].iconData, 32 | title: profileList[index].title, 33 | onTap: profileList[index].onTap, 34 | isLast: (index == profileList.length - 1) ? true : false 35 | ); 36 | } 37 | ), 38 | ], 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/widgets/get_start_btn.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class GetStartBtn extends StatefulWidget { 5 | final String text; 6 | final Function onTap; 7 | const GetStartBtn({Key? key, required this.text, required this.onTap}) : super(key: key); 8 | 9 | @override 10 | State createState() => _GetStartBtnState(); 11 | } 12 | 13 | class _GetStartBtnState extends State with TickerProviderStateMixin{ 14 | 15 | late AnimationController fadeController; 16 | late Animation fadeAnimation; 17 | 18 | @override 19 | void initState() { 20 | // TODO: implement initState 21 | super.initState(); 22 | 23 | fadeController = AnimationController( 24 | value: 0, 25 | duration: const Duration(milliseconds: 2000), 26 | vsync: this, 27 | ); 28 | fadeAnimation = CurvedAnimation(parent: fadeController, curve: Curves.ease); 29 | fadeController.forward(); 30 | } 31 | 32 | @override 33 | void dispose() { 34 | fadeController.dispose(); 35 | 36 | // TODO: implement dispose 37 | super.dispose(); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return FadeTransition( 43 | opacity: fadeAnimation, 44 | child: SizedBox( 45 | height: 45, 46 | width: 120, 47 | child: ElevatedButton( 48 | style: ElevatedButton.styleFrom( 49 | backgroundColor: Colors.amber, 50 | shape: RoundedRectangleBorder( 51 | borderRadius: BorderRadius.circular(20), // <-- Radius 52 | ), 53 | ), 54 | onPressed: (){ 55 | widget.onTap(); 56 | }, 57 | child: Text(widget.text, style: const TextStyle(fontSize: 17,color: Colors.black,fontWeight: FontWeight.w500, fontFamily: 'Vazir'),),), 58 | ), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/config/my_theme.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | class MyThemes { 5 | static final darkTheme = ThemeData( 6 | textTheme: const TextTheme( 7 | titleMedium: TextStyle(fontFamily: "Vazir",fontSize: 20,fontWeight: FontWeight.bold,), 8 | bodyMedium: TextStyle(fontFamily: "Vazir",fontSize: 15,fontWeight: FontWeight.w400), 9 | ), 10 | // highlightColor: Colors.indigo, 11 | // backgroundColor: Colors.black, 12 | // canvasColor: Colors.grey, 13 | // unselectedWidgetColor: Colors.white70, 14 | // primaryColorLight: Color.fromRGBO(252, 178, 98, 1), 15 | // scaffoldBackgroundColor: Colors.grey.shade900, 16 | // primaryColor: Colors.amber.shade800, 17 | // indicatorColor: Colors.amber, 18 | // secondaryHeaderColor: Color.fromRGBO(176, 106, 2, 1), 19 | // iconTheme: IconThemeData(color: Colors.amber.shade800), 20 | // textSelectionTheme: const TextSelectionThemeData( 21 | // cursorColor: Colors.red, 22 | // selectionColor: Colors.green, 23 | // selectionHandleColor: Colors.blue, 24 | // ) 25 | // colorScheme: const ColorScheme.dark() 26 | ); 27 | 28 | static final lightTheme = ThemeData( 29 | textTheme: const TextTheme( 30 | titleMedium: TextStyle(fontFamily: "Vazir",fontSize: 20,fontWeight: FontWeight.bold), 31 | bodyMedium: TextStyle(fontFamily: "Vazir",fontSize: 15,fontWeight: FontWeight.w400), 32 | ), 33 | // highlightColor: Colors.indigo, 34 | // backgroundColor: Colors.black, 35 | // unselectedWidgetColor: Colors.black, 36 | // primaryColorLight: Color.fromRGBO(252, 178, 98, 1), 37 | // scaffoldBackgroundColor: Colors.white, 38 | // primaryColor: Colors.amber.shade800, 39 | // indicatorColor: Colors.amber, 40 | // secondaryHeaderColor: Color.fromRGBO(176, 106, 2, 1), 41 | // iconTheme: IconThemeData(color: Colors.amber.shade800), 42 | // 43 | // // colorScheme: const ColorScheme.light() 44 | ); 45 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/data/models/signup_model.dart: -------------------------------------------------------------------------------- 1 | /// message : "ثبت نام شما با موفقیت انجام شد" 2 | /// status : "success" 3 | /// data : {"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvbml5YXouc2hvcFwvYXBpXC92MVwvcmVnaXN0ZXIiLCJpYXQiOjE2NjUzMDM0NTksImV4cCI6MTY2NTMwNzA1OSwibmJmIjoxNjY1MzAzNDU5LCJqdGkiOiJ3SGpaOXk1SGdTaU5hSHJqIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.dRWrY2LQ5JppeGcKWoyAx--QUORBkNuz2JcGjPO84oU"} 4 | 5 | class SignupModel { 6 | SignupModel({ 7 | this.message, 8 | this.status, 9 | this.data,}); 10 | 11 | SignupModel.fromJson(dynamic json) { 12 | message = json['message']; 13 | status = json['status']; 14 | data = json['data'] != null ? Data.fromJson(json['data']) : null; 15 | } 16 | String? message; 17 | String? status; 18 | Data? data; 19 | 20 | Map toJson() { 21 | final map = {}; 22 | map['message'] = message; 23 | map['status'] = status; 24 | if (data != null) { 25 | map['data'] = data?.toJson(); 26 | } 27 | return map; 28 | } 29 | 30 | } 31 | 32 | /// token : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczpcL1wvbml5YXouc2hvcFwvYXBpXC92MVwvcmVnaXN0ZXIiLCJpYXQiOjE2NjUzMDM0NTksImV4cCI6MTY2NTMwNzA1OSwibmJmIjoxNjY1MzAzNDU5LCJqdGkiOiJ3SGpaOXk1SGdTaU5hSHJqIiwic3ViIjoxMCwicHJ2IjoiODdlMGFmMWVmOWZkMTU4MTJmZGVjOTcxNTNhMTRlMGIwNDc1NDZhYSJ9.dRWrY2LQ5JppeGcKWoyAx--QUORBkNuz2JcGjPO84oU" 33 | 34 | class Data { 35 | Data({ 36 | this.token, 37 | this.name, 38 | this.mobile,}); 39 | 40 | Data.fromJson(dynamic json) { 41 | token = json['token']; 42 | name = json['name']; 43 | mobile = json['mobile']; 44 | } 45 | String? token; 46 | String? name; 47 | String? mobile; 48 | 49 | Map toJson() { 50 | final map = {}; 51 | map['token'] = token; 52 | map['name'] = name; 53 | map['mobile'] = mobile; 54 | return map; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 11 | 19 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | besenior_shop_course 33 | 34 | 35 | 39 | 40 | 41 | 42 | 43 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/bloc/all_poducts_cubit/all_products_cubit.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | import '../../../../../common/params/products_params.dart'; 5 | import '../../../../../common/resources/data_state.dart'; 6 | import '../../../data/models/all_products_model.dart'; 7 | import '../../../repositories/all_product_repository.dart'; 8 | 9 | part 'all_products_state.dart'; 10 | part 'products_data_status.dart'; 11 | 12 | class AllProductsCubit extends Cubit { 13 | AllProductsRepository allProductsRepository; 14 | AllProductsCubit(this.allProductsRepository) : super(AllProductsState( 15 | productsDataStatus: ProductsDataLoading(), 16 | allProducts: [], 17 | nextStart: 0, 18 | isLoadingPaging: false 19 | )); 20 | 21 | 22 | Future loadProductsData(ProductsParams productsParams) async { 23 | /// emit loading 24 | /// if it is first time emit main loading 25 | /// if it is paging time emit paging loading 26 | if(state.nextStart == 0){ 27 | emit(state.copyWith(newProductsDataStatus: ProductsDataLoading())); 28 | }else{ 29 | emit(state.copyWith(newIsLoadingPaging: true)); 30 | } 31 | 32 | /// change next start before api call 33 | productsParams.start = state.nextStart; 34 | DataState dataState = await allProductsRepository.fetchAllProductsData(productsParams); 35 | 36 | if(dataState is DataSuccess){ 37 | AllProductsModel allProductsModel = dataState.data; 38 | 39 | /// emit completed 40 | emit(state.copyWith( 41 | newProductsDataStatus: ProductsDataCompleted(allProductsModel), 42 | newAllProducts: state.allProducts..addAll(allProductsModel.data![0].products!), 43 | newNextStart: allProductsModel.data![0].nextStart, 44 | newIsLoadingPaging: false, 45 | )); 46 | } 47 | 48 | if(dataState is DataFailed){ 49 | /// emit error 50 | emit(state.copyWith( 51 | newProductsDataStatus: ProductsDataError(dataState.error!), 52 | newIsLoadingPaging: false 53 | )); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Besenior Shop Course 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | besenior_shop_course 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | NSLocationAlwaysUsageDescription 30 | we need user location for set his ecommerce 31 | NSLocationWhenInUseUsageDescription 32 | we need user location for set his ecommerce 33 | UIApplicationSupportsIndirectInputEvents 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/login_bloc/login_bloc.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:meta/meta.dart'; 4 | import '../../../../../common/resources/data_state.dart'; 5 | import '../../../data/models/code_model.dart'; 6 | import '../../../data/models/login_with_sms_model.dart'; 7 | import '../../../repositories/auth_repository.dart'; 8 | 9 | part 'login_event.dart'; 10 | part 'login_state.dart'; 11 | part 'login_data_status.dart'; 12 | part 'code_check_status.dart'; 13 | 14 | class LoginBloc extends Bloc { 15 | AuthRepository authRepository; 16 | LoginBloc(this.authRepository) : super( 17 | LoginState( 18 | loginDataStatus: LoginDataInitial(), 19 | codeCheckStatus: CodeCheckInitial(), 20 | )) { 21 | 22 | 23 | on((event, emit) async { 24 | /// emit loading 25 | emit( 26 | state.copyWith( 27 | newLoginDataStatus: LoginDataLoading(), 28 | )); 29 | 30 | DataState dataState = await authRepository.fetchLoginSms(event.phoneNumber); 31 | 32 | if(dataState is DataSuccess){ 33 | /// emit completed 34 | emit(state.copyWith( 35 | newLoginDataStatus: LoginDataCompleted(dataState.data), 36 | )); 37 | } 38 | 39 | if(dataState is DataFailed){ 40 | /// emit error 41 | emit(state.copyWith( 42 | newLoginDataStatus: LoginDataError(dataState.error!), 43 | )); 44 | } 45 | }); 46 | 47 | on((event, emit) async { 48 | /// emit loading 49 | emit( 50 | state.copyWith( 51 | newCodeCheckStatus: CodeCheckLoading(), 52 | )); 53 | 54 | DataState dataState = await authRepository.fetchCodeCheckData(event.code); 55 | 56 | if(dataState is DataSuccess){ 57 | /// emit completed 58 | emit(state.copyWith( 59 | newCodeCheckStatus: CodeCheckCompleted(dataState.data), 60 | )); 61 | } 62 | 63 | if(dataState is DataFailed){ 64 | /// emit error 65 | emit(state.copyWith( 66 | newCodeCheckStatus: CodeCheckError(dataState.error!), 67 | )); 68 | } 69 | }); 70 | 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/features/feature_auth/presentation/bloc/signup_bloc/signup_bloc.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:bloc/bloc.dart'; 3 | import 'package:meta/meta.dart'; 4 | import '../../../../../common/params/signup_params.dart'; 5 | import '../../../../../common/resources/data_state.dart'; 6 | import '../../../data/models/login_with_sms_model.dart'; 7 | import '../../../data/models/signup_model.dart'; 8 | import '../../../repositories/auth_repository.dart'; 9 | 10 | part 'signup_event.dart'; 11 | part 'signup_state.dart'; 12 | part 'signup_data_status.dart'; 13 | part 'call_code_status.dart'; 14 | 15 | 16 | class SignupBloc extends Bloc { 17 | AuthRepository authRepository; 18 | SignupBloc(this.authRepository) : super( 19 | SignupState( 20 | signUpDataStatus: SignUpDataInitial(), 21 | callCodeStatus: CallCodeInitial(), 22 | )) { 23 | 24 | on((event, emit) async { 25 | /// emit loading 26 | emit( 27 | state.copyWith( 28 | newSignUpDataStatus: SignUpDataLoading(), 29 | )); 30 | 31 | DataState dataState = await authRepository.fetchSignUpData(event.signUpParams); 32 | 33 | if(dataState is DataSuccess){ 34 | /// emit completed 35 | emit(state.copyWith( 36 | newSignUpDataStatus: SignUpCompleted(dataState.data), 37 | )); 38 | } 39 | 40 | if(dataState is DataFailed){ 41 | /// emit error 42 | emit(state.copyWith( 43 | newSignUpDataStatus: SignUpDataError(dataState.error!), 44 | )); 45 | } 46 | 47 | }); 48 | 49 | on((event, emit) async { 50 | /// emit loading 51 | emit( 52 | state.copyWith( 53 | newCallCodeStatus: CallCodeLoading(), 54 | )); 55 | 56 | DataState dataState = await authRepository.fetchRegisterCodeCheckData(event.mobile); 57 | 58 | if(dataState is DataSuccess){ 59 | /// emit completed 60 | emit(state.copyWith( 61 | newCallCodeStatus: CallCodeCompleted(dataState.data), 62 | )); 63 | } 64 | 65 | if(dataState is DataFailed){ 66 | /// emit error 67 | emit(state.copyWith( 68 | newCallCodeStatus: CallCodeError(dataState.error!), 69 | )); 70 | } 71 | }); 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /lib/locator.dart: -------------------------------------------------------------------------------- 1 | 2 | 3 | import 'package:besenior_shop_course/common/utils/prefs_operator.dart'; 4 | import 'package:besenior_shop_course/features/feature_home/data/data_source/home_api_provider.dart'; 5 | import 'package:besenior_shop_course/features/feature_home/repositories/home_repository.dart'; 6 | import 'package:besenior_shop_course/features/feature_product/data/data_source/category_api_provider.dart'; 7 | import 'package:besenior_shop_course/features/feature_product/data/data_source/product_api_provider.dart'; 8 | import 'package:besenior_shop_course/features/feature_product/repositories/all_product_repository.dart'; 9 | import 'package:besenior_shop_course/features/feature_product/repositories/category_repository.dart'; 10 | import 'package:dio/dio.dart'; 11 | import 'package:get_it/get_it.dart'; 12 | import 'package:shared_preferences/shared_preferences.dart'; 13 | 14 | import 'features/feature_auth/data/data_source/auth_api_provider.dart'; 15 | import 'features/feature_auth/presentation/bloc/login_bloc/login_bloc.dart'; 16 | import 'features/feature_auth/presentation/bloc/signup_bloc/signup_bloc.dart'; 17 | import 'features/feature_auth/repositories/auth_repository.dart'; 18 | 19 | GetIt locator = GetIt.instance; 20 | 21 | Future initLocator() async{ 22 | locator.registerSingleton(Dio()); 23 | 24 | SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); 25 | locator.registerSingleton(sharedPreferences); 26 | locator.registerSingleton(PrefsOperator()); 27 | 28 | 29 | /// api provider 30 | locator.registerSingleton(HomeApiProvider(locator())); 31 | locator.registerSingleton(CategoryApiProvider(locator())); 32 | locator.registerSingleton(ProductsApiProvider(locator())); 33 | locator.registerSingleton(AuthApiProvider(locator())); 34 | 35 | /// repository 36 | locator.registerSingleton(HomeRepository(locator())); 37 | locator.registerSingleton(CategoryRepository(locator())); 38 | locator.registerSingleton(AllProductsRepository(locator())); 39 | locator.registerSingleton(AuthRepository(locator())); 40 | 41 | 42 | /// bloc 43 | locator.registerSingleton(SignupBloc(locator())); 44 | locator.registerSingleton(LoginBloc(locator())); 45 | 46 | } -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - connectivity_plus (0.0.1): 3 | - Flutter 4 | - ReachabilitySwift 5 | - Flutter (1.0.0) 6 | - flutter_keyboard_visibility (0.0.1): 7 | - Flutter 8 | - FMDB (2.7.5): 9 | - FMDB/standard (= 2.7.5) 10 | - FMDB/standard (2.7.5) 11 | - location (0.0.1): 12 | - Flutter 13 | - path_provider_foundation (0.0.1): 14 | - Flutter 15 | - FlutterMacOS 16 | - ReachabilitySwift (5.0.0) 17 | - shared_preferences_foundation (0.0.1): 18 | - Flutter 19 | - FlutterMacOS 20 | - sqflite (0.0.2): 21 | - Flutter 22 | - FMDB (>= 2.7.5) 23 | 24 | DEPENDENCIES: 25 | - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) 26 | - Flutter (from `Flutter`) 27 | - flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`) 28 | - location (from `.symlinks/plugins/location/ios`) 29 | - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) 30 | - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`) 31 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 32 | 33 | SPEC REPOS: 34 | trunk: 35 | - FMDB 36 | - ReachabilitySwift 37 | 38 | EXTERNAL SOURCES: 39 | connectivity_plus: 40 | :path: ".symlinks/plugins/connectivity_plus/ios" 41 | Flutter: 42 | :path: Flutter 43 | flutter_keyboard_visibility: 44 | :path: ".symlinks/plugins/flutter_keyboard_visibility/ios" 45 | location: 46 | :path: ".symlinks/plugins/location/ios" 47 | path_provider_foundation: 48 | :path: ".symlinks/plugins/path_provider_foundation/ios" 49 | shared_preferences_foundation: 50 | :path: ".symlinks/plugins/shared_preferences_foundation/ios" 51 | sqflite: 52 | :path: ".symlinks/plugins/sqflite/ios" 53 | 54 | SPEC CHECKSUMS: 55 | connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e 56 | Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 57 | flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 58 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 59 | location: 3a2eed4dd2fab25e7b7baf2a9efefe82b512d740 60 | path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 61 | ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 62 | shared_preferences_foundation: 297b3ebca31b34ec92be11acd7fb0ba932c822ca 63 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 64 | 65 | PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 66 | 67 | COCOAPODS: 1.11.2 68 | -------------------------------------------------------------------------------- /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 flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.besenior.besenior_shop_course" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion 19 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /lib/common/widgets/main_wrapper.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/common/widgets/bottom_nav.dart'; 3 | import 'package:besenior_shop_course/features/feature_home/presentation/screens/home_screen.dart'; 4 | import 'package:besenior_shop_course/features/feature_home/presentation/screens/profile_screen.dart'; 5 | import 'package:besenior_shop_course/features/feature_product/presentation/screens/category_screen.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | import '../../features/feature_product/presentation/widgets/search_textfield.dart'; 9 | import '../../features/feature_product/repositories/all_product_repository.dart'; 10 | import '../../locator.dart'; 11 | 12 | class MainWrapper extends StatelessWidget { 13 | static const routeName = "/main_wrapper"; 14 | 15 | MainWrapper({Key? key}) : super(key: key); 16 | 17 | final TextEditingController searchController = TextEditingController(); 18 | 19 | PageController pageController = PageController(); 20 | 21 | List topLevelScreens = [ 22 | HomeScreen(), 23 | CategoryScreen(), 24 | ProfileScreen(), 25 | Container(color: Colors.green,), 26 | ]; 27 | 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | bottomNavigationBar: BottomNav(controller: pageController), 33 | 34 | body: SafeArea( 35 | child: Column( 36 | children: [ 37 | const SizedBox(height: 10,), 38 | 39 | /// search Box 40 | Container( 41 | decoration: BoxDecoration( 42 | color: Colors.white, 43 | boxShadow: [ 44 | BoxShadow( 45 | blurRadius: 2, 46 | color: Colors.grey.shade400, 47 | offset: const Offset(0, 3) 48 | ) 49 | ] 50 | ), 51 | child: Padding( 52 | padding: const EdgeInsets.only(left: 10.0,right: 10, bottom: 10), 53 | child: SearchTextField(controller: searchController, allProductsRepository: locator(),), 54 | ), 55 | ), 56 | const SizedBox(height: 10,), 57 | 58 | Expanded( 59 | child: PageView( 60 | controller: pageController, 61 | children: topLevelScreens, 62 | ), 63 | ), 64 | ], 65 | ), 66 | ), 67 | ); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/widgets/intro_page.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:delayed_widget/delayed_widget.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:shimmer/shimmer.dart'; 5 | 6 | class IntroPage extends StatelessWidget { 7 | final String title; 8 | final String description; 9 | final String image; 10 | const IntroPage({Key? key, 11 | required this.title, 12 | required this.description, 13 | required this.image, 14 | }) : super(key: key); 15 | 16 | @override 17 | Widget build(BuildContext context) { 18 | 19 | var textTheme = Theme.of(context).textTheme; 20 | /// get device size 21 | var height = MediaQuery.of(context).size.height; 22 | var width = MediaQuery.of(context).size.width; 23 | 24 | return Column( 25 | crossAxisAlignment: CrossAxisAlignment.start, 26 | children: [ 27 | 28 | /// image 29 | SizedBox( 30 | width: width, 31 | height: height * 0.6, 32 | child: DelayedWidget( 33 | delayDuration: const Duration(milliseconds: 200),// Not required 34 | animationDuration: const Duration(seconds: 1),// Not required 35 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM,// 36 | child: Image.asset(image)) 37 | ), 38 | const SizedBox(height: 20,), 39 | 40 | Padding( 41 | padding: const EdgeInsets.symmetric(horizontal: 25.0), 42 | child: DelayedWidget( 43 | delayDuration: const Duration(milliseconds: 400),// Not required 44 | animationDuration: const Duration(seconds: 1),// Not required 45 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM,// Not required 46 | child: Text(title, style: textTheme.titleMedium,), 47 | ), 48 | ), 49 | const SizedBox(height: 10,), 50 | Padding( 51 | padding: const EdgeInsets.symmetric(horizontal: 25.0), 52 | child: DelayedWidget( 53 | delayDuration: const Duration(milliseconds: 600),// Not required 54 | animationDuration: const Duration(seconds: 1),// Not required 55 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM,// Not required 56 | child: Shimmer.fromColors( 57 | baseColor: Colors.black, 58 | highlightColor: Colors.grey, 59 | child: Text(description, style: textTheme.bodyMedium)) 60 | ), 61 | ), 62 | ], 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /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/features/feature_auth/data/data_source/auth_api_provider.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'dart:io'; 3 | 4 | import 'package:android_sms_retriever/android_sms_retriever.dart'; 5 | import 'package:dio/dio.dart'; 6 | import '../../../../common/error_handling/check_exceptions.dart'; 7 | import '../../../../common/params/signup_params.dart'; 8 | import '../../../../config/constants.dart'; 9 | 10 | class AuthApiProvider { 11 | Dio dio; 12 | AuthApiProvider(this.dio); 13 | 14 | dynamic callSignUp(SignUpParams signUpParams) async { 15 | try{ 16 | final response = await dio.post( 17 | "${Constants.baseUrl}/register", 18 | queryParameters: { 19 | "name" : signUpParams.username, 20 | "mobile" : signUpParams.phoneNumber 21 | } 22 | ); 23 | print(response.toString()); 24 | 25 | return response; 26 | 27 | }on DioError catch(e){ 28 | return CheckExceptions.response(e.response!); 29 | } 30 | } 31 | 32 | dynamic callLoginWithSms(phoneNumber) async { 33 | if(Platform.isAndroid){ 34 | String? sign = await AndroidSmsRetriever.getAppSignature(); 35 | print("app sign : " + sign!); 36 | } 37 | 38 | 39 | try{ 40 | final response = await dio.post( 41 | "${Constants.baseUrl}/auth/loginWithSms", 42 | queryParameters: { 43 | "mobile" : phoneNumber, 44 | if(Platform.isAndroid) 45 | 'hash': (await AndroidSmsRetriever.getAppSignature()) 46 | } 47 | ); 48 | print(response.toString()); 49 | 50 | return response; 51 | 52 | }on DioError catch(e){ 53 | return CheckExceptions.response(e.response!); 54 | } 55 | } 56 | 57 | dynamic callCodeCheck(code) async { 58 | try{ 59 | final response = await dio.post( 60 | "${Constants.baseUrl}/auth/loginWithSms/check", 61 | queryParameters: { 62 | "code" : code, 63 | } 64 | ); 65 | print(response.toString()); 66 | 67 | return response; 68 | 69 | } on DioError catch(e){ 70 | return CheckExceptions.response(e.response!); 71 | } 72 | } 73 | 74 | dynamic callRegisterCodeCheck(mobile) async { 75 | try{ 76 | final response = await dio.post( 77 | "${Constants.baseUrl}/auth/sendcode", 78 | queryParameters: { 79 | "mobile" : mobile, 80 | if(Platform.isAndroid) 81 | 'hash': (await AndroidSmsRetriever.getAppSignature()) 82 | } 83 | ); 84 | print(response.toString()); 85 | 86 | return response; 87 | 88 | }on DioError catch(e){ 89 | return CheckExceptions.response(e.response!); 90 | } 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /lib/features/feature_auth/repositories/auth_repository.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:dio/dio.dart'; 3 | import '../../../common/error_handling/app_exception.dart'; 4 | import '../../../common/error_handling/check_exceptions.dart'; 5 | import '../../../common/params/signup_params.dart'; 6 | import '../../../common/resources/data_state.dart'; 7 | import '../data/data_source/auth_api_provider.dart'; 8 | import '../data/models/code_model.dart'; 9 | import '../data/models/login_with_sms_model.dart'; 10 | import '../data/models/signup_model.dart'; 11 | 12 | class AuthRepository { 13 | AuthApiProvider apiProvider; 14 | AuthRepository(this.apiProvider); 15 | 16 | Future> fetchSignUpData(SignUpParams signUpParams) async { 17 | try{ 18 | Response response = await apiProvider.callSignUp(signUpParams); 19 | if(response.data['status'].toString() == "success"){ 20 | final SignupModel signupModel = SignupModel.fromJson(response.data); 21 | return DataSuccess(signupModel); 22 | }else{ 23 | return DataFailed(response.data['message']); 24 | } 25 | } on AppException catch(e){ 26 | return await CheckExceptions.getError(e); 27 | } 28 | } 29 | 30 | 31 | Future> fetchLoginSms(phoneNumber) async { 32 | try{ 33 | Response response = await apiProvider.callLoginWithSms(phoneNumber); 34 | if(response.data['status'].toString() != "error"){ 35 | // convert json to models class 36 | final LoginWithSmsModel loginWithSmsModel = LoginWithSmsModel.fromJson(response.data); 37 | return DataSuccess(loginWithSmsModel); 38 | }else{ 39 | return DataFailed(response.data['message']); 40 | } 41 | } on AppException catch(e){ 42 | return CheckExceptions.getError(e); 43 | } 44 | } 45 | 46 | Future> fetchCodeCheckData(code) async { 47 | try{ 48 | Response response = await apiProvider.callCodeCheck(code); 49 | final CodeModel codeModel = CodeModel.fromJson(response.data); 50 | return DataSuccess(codeModel); 51 | } on AppException catch(e){ 52 | return CheckExceptions.getError(e); 53 | } 54 | } 55 | 56 | Future> fetchRegisterCodeCheckData(mobile) async { 57 | try{ 58 | Response response = await apiProvider.callRegisterCodeCheck(mobile); 59 | if(response.data['status'].toString() == "success"){ 60 | // convert json to models class 61 | final LoginWithSmsModel loginWithSmsModel = LoginWithSmsModel.fromJson(response.data); 62 | return DataSuccess(loginWithSmsModel); 63 | }else{ 64 | return DataFailed(response.data["message"]); 65 | } 66 | } on AppException catch(e){ 67 | return CheckExceptions.getError(e); 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:besenior_shop_course/common/blocs/bottom_nav_cubit/bottom_nav_cubit.dart'; 4 | import 'package:besenior_shop_course/config/my_theme.dart'; 5 | import 'package:besenior_shop_course/features/feature_auth/presentation/screens/mobile_signup_screen.dart'; 6 | import 'package:besenior_shop_course/features/feature_intro/presentation/screens/intro_main_wrapper.dart'; 7 | import 'package:besenior_shop_course/features/feature_intro/presentation/screens/splash_screen.dart'; 8 | import 'package:besenior_shop_course/features/feature_product/presentation/screens/all_products_screen.dart'; 9 | import 'package:besenior_shop_course/test_screen.dart'; 10 | import 'package:flutter/material.dart'; 11 | import 'package:flutter_bloc/flutter_bloc.dart'; 12 | import 'package:flutter_localizations/flutter_localizations.dart'; 13 | 14 | import 'common/widgets/main_wrapper.dart'; 15 | import 'features/feature_auth/presentation/bloc/login_bloc/login_bloc.dart'; 16 | import 'features/feature_auth/presentation/bloc/signup_bloc/signup_bloc.dart'; 17 | import 'features/feature_intro/presentation/bloc/splash_cubit/splash_cubit.dart'; 18 | import 'locator.dart'; 19 | 20 | Future main() async { 21 | WidgetsFlutterBinding.ensureInitialized(); 22 | 23 | HttpOverrides.global = MyHttpOverrides(); 24 | 25 | await initLocator(); 26 | 27 | runApp(MultiBlocProvider( 28 | providers: [ 29 | BlocProvider(create: (_)=> SplashCubit()), 30 | BlocProvider(create: (_)=> BottomNavCubit()), 31 | BlocProvider(create: (_) => locator()), 32 | BlocProvider(create: (_) => locator()), 33 | ], 34 | child: const MyApp() 35 | )); 36 | } 37 | 38 | class MyApp extends StatelessWidget { 39 | const MyApp({Key? key}) : super(key: key); 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return MaterialApp( 44 | themeMode: ThemeMode.light, 45 | theme: MyThemes.lightTheme, 46 | darkTheme: MyThemes.darkTheme, 47 | initialRoute: "/", 48 | locale: const Locale("fa",""), 49 | localizationsDelegates: const [ 50 | GlobalMaterialLocalizations.delegate, 51 | GlobalWidgetsLocalizations.delegate, 52 | GlobalCupertinoLocalizations.delegate, 53 | ], 54 | supportedLocales: const [ 55 | Locale("en",""), 56 | Locale("fa",""), 57 | ], 58 | routes: { 59 | IntroMainWrapper.routeName: (context)=> IntroMainWrapper(), 60 | TestScreen.routeName: (context)=> TestScreen(), 61 | MainWrapper.routeName: (context)=> MainWrapper(), 62 | MobileSignUpScreen.routeName: (context)=> MobileSignUpScreen(), 63 | AllProductsScreen.routeName: (context)=> AllProductsScreen(), 64 | }, 65 | debugShowCheckedModeBanner: false, 66 | title: 'besenior shop', 67 | // home: MobileSignUpScreen(), 68 | home: SplashScreen(), 69 | ); 70 | } 71 | } 72 | 73 | class MyHttpOverrides extends HttpOverrides{ 74 | @override 75 | HttpClient createHttpClient(SecurityContext? context){ 76 | return super.createHttpClient(context) 77 | ..badCertificateCallback = (X509Certificate cert, String host, int port)=> true; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/features/feature_product/data/models/categories_model.dart: -------------------------------------------------------------------------------- 1 | /// message : "با موفقیت دریافت شد" 2 | /// status : "success" 3 | /// data : [{"id":1,"title":"محصولات ارگانیک","img":"https://niyaz.shop/images/image-not-found.png","childs":[{"id":2,"title":"میوه ارگانیک","img":"https://niyaz.shop/images/image-not-found.png","childs":null},{"id":3,"title":"سبزیجات ارگانیک","img":"https://niyaz.shop/images/image-not-found.png","childs":null}]},{"id":4,"title":"میوه خشک","img":"https://niyaz.shop/images/image-not-found.png","childs":null}] 4 | 5 | class CategoriesModel { 6 | CategoriesModel({ 7 | this.message, 8 | this.status, 9 | this.data,}); 10 | 11 | CategoriesModel.fromJson(dynamic json) { 12 | message = json['message']; 13 | status = json['status']; 14 | if (json['data'] != null) { 15 | data = []; 16 | json['data'].forEach((v) { 17 | data?.add(Data.fromJson(v)); 18 | }); 19 | } 20 | } 21 | String? message; 22 | String? status; 23 | List? data; 24 | 25 | Map toJson() { 26 | final map = {}; 27 | map['message'] = message; 28 | map['status'] = status; 29 | if (data != null) { 30 | map['data'] = data?.map((v) => v.toJson()).toList(); 31 | } 32 | return map; 33 | } 34 | 35 | } 36 | 37 | /// id : 1 38 | /// title : "محصولات ارگانیک" 39 | /// img : "https://niyaz.shop/images/image-not-found.png" 40 | /// childs : [{"id":2,"title":"میوه ارگانیک","img":"https://niyaz.shop/images/image-not-found.png","childs":null},{"id":3,"title":"سبزیجات ارگانیک","img":"https://niyaz.shop/images/image-not-found.png","childs":null}] 41 | 42 | class Data { 43 | Data({ 44 | this.id, 45 | this.title, 46 | this.img, 47 | this.icon, 48 | this.childs,}); 49 | 50 | Data.fromJson(dynamic json) { 51 | id = json['id']; 52 | title = json['title']; 53 | img = json['img']; 54 | icon = json['icon']; 55 | if (json['childs'] != null) { 56 | childs = []; 57 | json['childs'].forEach((v) { 58 | childs?.add(Childs.fromJson(v)); 59 | }); 60 | } 61 | } 62 | int? id; 63 | String? title; 64 | String? img; 65 | String? icon; 66 | List? childs; 67 | 68 | Map toJson() { 69 | final map = {}; 70 | map['id'] = id; 71 | map['title'] = title; 72 | map['img'] = img; 73 | map['icon'] = icon; 74 | if (childs != null) { 75 | map['childs'] = childs?.map((v) => v.toJson()).toList(); 76 | } 77 | return map; 78 | } 79 | 80 | } 81 | 82 | /// id : 2 83 | /// title : "میوه ارگانیک" 84 | /// img : "https://niyaz.shop/images/image-not-found.png" 85 | /// childs : null 86 | 87 | class Childs { 88 | Childs({ 89 | this.id, 90 | this.title, 91 | this.img, 92 | this.childs,}); 93 | 94 | Childs.fromJson(dynamic json) { 95 | id = json['id']; 96 | title = json['title']; 97 | img = json['img']; 98 | childs = json['childs']; 99 | } 100 | int? id; 101 | String? title; 102 | String? img; 103 | dynamic childs; 104 | 105 | Map toJson() { 106 | final map = {}; 107 | map['id'] = id; 108 | map['title'] = title; 109 | map['img'] = img; 110 | map['childs'] = childs; 111 | return map; 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /macos/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/features/feature_product/data/models/all_products_model.dart: -------------------------------------------------------------------------------- 1 | /// message : "با موفقیت دریافت شد" 2 | /// status : "success" 3 | /// data : [{"products":[{"id":120,"image":"https://niyaz.shop/uploads/products/ازگیل-16633545536048089.png","name":"ازگیل","price":"42,000","priceBeforDiscount":"0","discount":0,"callStatus":0,"specialDiscount":0,"star":3,"category":"محصولات ارگانیک"}],"count":81,"nextStart":15,"more":true}] 4 | 5 | class AllProductsModel { 6 | AllProductsModel({ 7 | this.message, 8 | this.status, 9 | this.data,}); 10 | 11 | AllProductsModel.fromJson(dynamic json) { 12 | message = json['message']; 13 | status = json['status']; 14 | if (json['data'] != null) { 15 | data = []; 16 | json['data'].forEach((v) { 17 | data?.add(Data.fromJson(v)); 18 | }); 19 | } 20 | } 21 | String? message; 22 | String? status; 23 | List? data; 24 | 25 | Map toJson() { 26 | final map = {}; 27 | map['message'] = message; 28 | map['status'] = status; 29 | if (data != null) { 30 | map['data'] = data?.map((v) => v.toJson()).toList(); 31 | } 32 | return map; 33 | } 34 | 35 | } 36 | 37 | /// products : [{"id":120,"image":"https://niyaz.shop/uploads/products/ازگیل-16633545536048089.png","name":"ازگیل","price":"42,000","priceBeforDiscount":"0","discount":0,"callStatus":0,"specialDiscount":0,"star":3,"category":"محصولات ارگانیک"}] 38 | /// count : 81 39 | /// nextStart : 15 40 | /// more : true 41 | 42 | class Data { 43 | Data({ 44 | this.products, 45 | this.count, 46 | this.nextStart, 47 | this.more,}); 48 | 49 | Data.fromJson(dynamic json) { 50 | if (json['products'] != null) { 51 | products = []; 52 | json['products'].forEach((v) { 53 | products?.add(Products.fromJson(v)); 54 | }); 55 | } 56 | count = json['count']; 57 | nextStart = json['nextStart']; 58 | more = json['more']; 59 | } 60 | List? products; 61 | int? count; 62 | int? nextStart; 63 | bool? more; 64 | 65 | Map toJson() { 66 | final map = {}; 67 | if (products != null) { 68 | map['products'] = products?.map((v) => v.toJson()).toList(); 69 | } 70 | map['count'] = count; 71 | map['nextStart'] = nextStart; 72 | map['more'] = more; 73 | return map; 74 | } 75 | 76 | } 77 | 78 | /// id : 120 79 | /// image : "https://niyaz.shop/uploads/products/ازگیل-16633545536048089.png" 80 | /// name : "ازگیل" 81 | /// price : "42,000" 82 | /// priceBeforDiscount : "0" 83 | /// discount : 0 84 | /// callStatus : 0 85 | /// specialDiscount : 0 86 | /// star : 3 87 | /// category : "محصولات ارگانیک" 88 | 89 | class Products { 90 | Products({ 91 | this.id, 92 | this.image, 93 | this.name, 94 | this.price, 95 | this.priceBeforDiscount, 96 | this.discount, 97 | this.callStatus, 98 | this.specialDiscount, 99 | this.star, 100 | this.category,}); 101 | 102 | Products.fromJson(dynamic json) { 103 | id = json['id']; 104 | image = json['image']; 105 | name = json['name']; 106 | price = json['price']; 107 | priceBeforDiscount = json['priceBeforDiscount']; 108 | discount = json['discount']; 109 | callStatus = json['callStatus']; 110 | specialDiscount = json['specialDiscount']; 111 | star = json['star']; 112 | category = json['category']; 113 | } 114 | int? id; 115 | String? image; 116 | String? name; 117 | String? price; 118 | String? priceBeforDiscount; 119 | int? discount; 120 | int? callStatus; 121 | int? specialDiscount; 122 | int? star; 123 | String? category; 124 | 125 | Map toJson() { 126 | final map = {}; 127 | map['id'] = id; 128 | map['image'] = image; 129 | map['name'] = name; 130 | map['price'] = price; 131 | map['priceBeforDiscount'] = priceBeforDiscount; 132 | map['discount'] = discount; 133 | map['callStatus'] = callStatus; 134 | map['specialDiscount'] = specialDiscount; 135 | map['star'] = star; 136 | map['category'] = category; 137 | return map; 138 | } 139 | 140 | } -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/screens/splash_screen.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/common/widgets/main_wrapper.dart'; 3 | import 'package:besenior_shop_course/features/feature_intro/presentation/bloc/splash_cubit/splash_cubit.dart'; 4 | import 'package:besenior_shop_course/features/feature_intro/presentation/screens/intro_main_wrapper.dart'; 5 | import 'package:delayed_widget/delayed_widget.dart'; 6 | import 'package:flutter/material.dart'; 7 | import 'package:flutter_bloc/flutter_bloc.dart'; 8 | import 'package:loading_animation_widget/loading_animation_widget.dart'; 9 | import '../../../../common/utils/prefs_operator.dart'; 10 | import '../../../../locator.dart'; 11 | import '../../../../test_screen.dart'; 12 | 13 | class SplashScreen extends StatefulWidget { 14 | const SplashScreen({Key? key}) : super(key: key); 15 | 16 | @override 17 | State createState() => _SplashScreenState(); 18 | } 19 | 20 | class _SplashScreenState extends State { 21 | 22 | @override 23 | void initState() { 24 | // TODO: implement initState 25 | super.initState(); 26 | // gotoHome(); 27 | 28 | BlocProvider.of(context).checkConnectionEvent(); 29 | } 30 | 31 | 32 | @override 33 | Widget build(BuildContext context) { 34 | 35 | var width = MediaQuery.of(context).size.width; 36 | return Scaffold( 37 | body: Container( 38 | width: width, 39 | color: Colors.white, 40 | child: Column( 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | children: [ 43 | Expanded( 44 | child: DelayedWidget( 45 | delayDuration: const Duration(milliseconds: 200), 46 | animationDuration: const Duration(milliseconds: 1000), 47 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM, 48 | child: Image.asset('assets/images/besenior_logo.png',width: width * 0.8,))), 49 | 50 | 51 | BlocConsumer( 52 | builder: (context, state){ 53 | /// if user is online 54 | if(state.connectionStatus is ConnectionInitial || state.connectionStatus is ConnectionOn){ 55 | return Directionality( 56 | textDirection: TextDirection.ltr, 57 | child: LoadingAnimationWidget.prograssiveDots( 58 | color: Colors.red, 59 | size: 50, 60 | ), 61 | ); 62 | } 63 | 64 | /// if user is offline 65 | if(state.connectionStatus is ConnectionOff){ 66 | return Row( 67 | mainAxisAlignment: MainAxisAlignment.center, 68 | children: [ 69 | const Text('به اینترنت متصل نیستید!', style: TextStyle(color: Colors.red, fontWeight: FontWeight.w500, fontFamily: "vazir"),), 70 | IconButton( 71 | splashColor: Colors.red, 72 | onPressed: (){ 73 | /// check that we are online or not 74 | BlocProvider.of(context).checkConnectionEvent(); 75 | }, 76 | icon: const Icon(Icons.autorenew, color: Colors.red,)) 77 | ], 78 | ); 79 | } 80 | 81 | /// default value 82 | return Container(); 83 | 84 | }, 85 | listener: (context, state){ 86 | if(state.connectionStatus is ConnectionOn){ 87 | gotoHome(); 88 | } 89 | } 90 | ), 91 | const SizedBox(height: 30,), 92 | 93 | ], 94 | ), 95 | ), 96 | ); 97 | } 98 | 99 | Future gotoHome() async { 100 | PrefsOperator prefsOperator = locator(); 101 | var shouldShowIntro = await prefsOperator.getIntroState(); 102 | 103 | return Future.delayed(const Duration(seconds: 3),(){ 104 | 105 | if(shouldShowIntro){ 106 | Navigator.pushNamedAndRemoveUntil(context, IntroMainWrapper.routeName,ModalRoute.withName("intro_main_wrapper"),); 107 | }else{ 108 | Navigator.pushNamedAndRemoveUntil(context, MainWrapper.routeName, ModalRoute.withName("main_wrapper"),); 109 | } 110 | }); 111 | 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: besenior_shop_course 2 | description: A new Flutter project. 3 | 4 | # The following line prevents the package from being accidentally published to 5 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 6 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 7 | 8 | # The following defines the version and build number for your application. 9 | # A version number is three numbers separated by dots, like 1.2.43 10 | # followed by an optional build number separated by a +. 11 | # Both the version and the builder number may be overridden in flutter 12 | # build by specifying --build-name and --build-number, respectively. 13 | # In Android, build-name is used as versionName while build-number used as versionCode. 14 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning 15 | # In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. 16 | # Read more about iOS versioning at 17 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html 18 | # In Windows, build-name is used as the major, minor, and patch parts 19 | # of the product and file versions while build-number is used as the build suffix. 20 | version: 1.0.0+1 21 | 22 | environment: 23 | sdk: '>=2.18.1 <3.0.0' 24 | 25 | # Dependencies specify other packages that your package needs in order to work. 26 | # To automatically upgrade your package dependencies to the latest versions 27 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 28 | # dependencies can be manually updated by changing the version numbers below to 29 | # the latest version available on pub.dev. To see which dependencies have newer 30 | # versions available, run `flutter pub outdated`. 31 | dependencies: 32 | flutter: 33 | sdk: flutter 34 | 35 | 36 | # The following adds the Cupertino Icons font to your application. 37 | # Use with the CupertinoIcons class for iOS style icons. 38 | cupertino_icons: ^1.0.2 39 | delayed_widget: ^1.1.2 40 | flutter_bloc: ^8.0.1 41 | bloc: ^8.0.3 42 | connectivity_plus: ^3.0.2 43 | loading_animation_widget: ^1.2.0+3 44 | shimmer: ^2.0.0 45 | smooth_page_indicator: ^0.3.0-nullsafety.0 46 | get_it: ^7.2.0 47 | dio: ^4.0.6 48 | shared_preferences: ^2.0.15 49 | flutter_svg: ^1.1.5 50 | font_awesome_flutter: ^10.2.1 51 | badges: ^2.0.3 52 | cached_network_image: ^3.2.2 53 | location: ^4.4.0 54 | flutter_typeahead: ^3.2.0 55 | flutter_rating_bar: ^4.0.1 56 | android_sms_retriever: ^1.3.2 57 | persian_number_utility: ^1.1.2 58 | 59 | 60 | 61 | dev_dependencies: 62 | flutter_test: 63 | sdk: flutter 64 | flutter_localizations: # Add this line 65 | sdk: flutter 66 | 67 | # The "flutter_lints" package below contains a set of recommended lints to 68 | # encourage good coding practices. The lint set provided by the package is 69 | # activated in the `analysis_options.yaml` file located at the root of your 70 | # package. See that file for information about deactivating specific lint 71 | # rules and activating additional ones. 72 | flutter_lints: ^2.0.0 73 | 74 | # For information on the generic Dart part of this file, see the 75 | # following page: https://dart.dev/tools/pub/pubspec 76 | 77 | # The following section is specific to Flutter packages. 78 | flutter: 79 | 80 | # The following line ensures that the Material Icons font is 81 | # included with your application, so that you can use the icons in 82 | # the material Icons class. 83 | uses-material-design: true 84 | 85 | # To add assets to your application, add an assets section, like this: 86 | assets: 87 | - assets/images/ 88 | # - images/a_dot_ham.jpeg 89 | 90 | # An image asset can refer to one or more resolution-specific "variants", see 91 | # https://flutter.dev/assets-and-images/#resolution-aware 92 | 93 | # For details regarding adding assets from package dependencies, see 94 | # https://flutter.dev/assets-and-images/#from-packages 95 | 96 | # To add custom fonts to your application, add a fonts section here, 97 | # in this "flutter" section. Each entry in this list should have a 98 | # "family" key with the font family name, and a "fonts" key with a 99 | # list giving the asset and other descriptors for the font. For 100 | # example: 101 | fonts: 102 | - family: Vazir 103 | fonts: 104 | - asset: assets/fonts/vazir.ttf 105 | # - asset: fonts/Schyler-Italic.ttf 106 | # style: italic 107 | # - family: Trajan Pro 108 | # fonts: 109 | # - asset: fonts/TrajanPro.ttf 110 | # - asset: fonts/TrajanPro_Bold.ttf 111 | # weight: 700 112 | # 113 | # For details regarding fonts from package dependencies, 114 | # see https://flutter.dev/custom-fonts/#from-packages 115 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/screens/category_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:besenior_shop_course/features/feature_product/data/models/categories_model.dart'; 2 | import 'package:besenior_shop_course/features/feature_product/presentation/bloc/category_cubit/category_cubit.dart'; 3 | import 'package:cached_network_image/cached_network_image.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_bloc/flutter_bloc.dart'; 6 | 7 | import '../../../../common/arguments/productsArgument.dart'; 8 | import '../../../../common/widgets/dot_loading_widget.dart'; 9 | import '../../../../locator.dart'; 10 | import 'all_products_screen.dart'; 11 | 12 | class CategoryScreen extends StatelessWidget { 13 | const CategoryScreen({Key? key}) : super(key: key); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return BlocProvider( 18 | create: (context) => CategoryCubit(locator()), 19 | child: Builder( 20 | builder: (context) { 21 | 22 | /// call api 23 | BlocProvider.of(context).loadCategoryEvent(); 24 | 25 | 26 | return BlocBuilder( 27 | builder: (context, state) { 28 | /// loading 29 | if(state.categoryDataStatus is CategoryDataLoading){ 30 | return const Center(child: DotLoadingWidget(size: 30)); 31 | } 32 | 33 | if(state.categoryDataStatus is CategoryDataCompleted){ 34 | CategoryDataCompleted categoryDataCompleted = state.categoryDataStatus as CategoryDataCompleted; 35 | CategoriesModel categoriesModel = categoryDataCompleted.categoriesModel; 36 | 37 | 38 | return ListView.separated( 39 | padding: const EdgeInsets.only(top: 20), 40 | itemBuilder: (context, index){ 41 | Data categoryData = categoriesModel.data![index]; 42 | /// text 43 | return GestureDetector( 44 | onTap: (){ 45 | /// goto All products screen 46 | Navigator.pushNamed(context, AllProductsScreen.routeName, arguments: ProductsArguments(categoryId: categoryData.id!),); 47 | }, 48 | child: Padding( 49 | padding: const EdgeInsets.symmetric(horizontal: 15.0), 50 | child: Container( 51 | height: 60, 52 | decoration: BoxDecoration( 53 | color: Colors.white, 54 | borderRadius: BorderRadius.circular(20), 55 | // boxShadow: const [ 56 | // BoxShadow( 57 | // blurRadius: 10, 58 | // offset: Offset(5, 5), 59 | // color: Colors.grey 60 | // ) 61 | // ] 62 | ), 63 | child: Padding( 64 | padding: const EdgeInsets.symmetric(horizontal: 15.0), 65 | child: Row( 66 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 67 | children: [ 68 | Row( 69 | children: [ 70 | ClipRRect( 71 | borderRadius: BorderRadius.circular(20), 72 | child: CachedNetworkImage( 73 | imageUrl: categoryData.icon!, 74 | errorWidget: (context, string, dynamic){ 75 | return const Icon(Icons.error,color: Colors.black,); 76 | }, 77 | width: 40, 78 | height: 40, 79 | fit: BoxFit.cover, 80 | useOldImageOnUrlChange: true, 81 | ), 82 | ), 83 | const SizedBox(width: 5,), 84 | Text(categoryData.title!,style: const TextStyle(color: Colors.black, fontFamily: 'Vazir',fontWeight: FontWeight.bold),), 85 | ], 86 | ), 87 | const Icon(Icons.arrow_back_ios_new, size: 15,color: Colors.blueAccent,), 88 | ], 89 | ), 90 | ), 91 | ), 92 | ), 93 | ); 94 | }, 95 | separatorBuilder: (context, index){ 96 | return SizedBox(height: 10); 97 | }, 98 | itemCount: categoriesModel.data!.length, 99 | ); 100 | } 101 | 102 | 103 | if(state.categoryDataStatus is CategoryDataError){ 104 | final CategoryDataError categoryDataError = state.categoryDataStatus as CategoryDataError; 105 | 106 | return Center( 107 | child: Column( 108 | mainAxisAlignment: MainAxisAlignment.center, 109 | children: [ 110 | Text(categoryDataError.errorMessage,style: const TextStyle(color: Colors.white),), 111 | const SizedBox(height: 10,), 112 | ElevatedButton( 113 | style: ElevatedButton.styleFrom(backgroundColor: Colors.amber.shade800), 114 | onPressed: (){ 115 | /// call all data again 116 | BlocProvider.of(context).loadCategoryEvent(); 117 | }, 118 | child: const Text("تلاش دوباره"),) 119 | ], 120 | ), 121 | ); 122 | } 123 | return Container(); 124 | }, 125 | ); 126 | } 127 | ), 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/common/widgets/bottom_nav.dart: -------------------------------------------------------------------------------- 1 | import 'package:badges/badges.dart'; 2 | import 'package:flutter/cupertino.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_svg/flutter_svg.dart'; 6 | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; 7 | import 'package:shared_preferences/shared_preferences.dart'; 8 | 9 | import '../blocs/bottom_nav_cubit/bottom_nav_cubit.dart'; 10 | 11 | // import '../../features/feature_auth/presentation/screens/mobile_signup_screen.dart'; 12 | 13 | class BottomNav extends StatelessWidget { 14 | final PageController controller; 15 | 16 | const BottomNav({Key? key, required this.controller}) : super(key: key); 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | var primaryColor = Theme.of(context).primaryColor; 21 | TextTheme textTheme = Theme.of(context).textTheme; 22 | 23 | return BottomAppBar( 24 | shape: const CircularNotchedRectangle(), 25 | notchMargin: 5, 26 | color: Colors.white, 27 | child: SizedBox( 28 | height: 72, 29 | child: BlocBuilder( 30 | builder: (context, int state) { 31 | return Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 33 | children: [ 34 | SizedBox( 35 | width: MediaQuery.of(context).size.width / 2, 36 | height: 72, 37 | child: Row( 38 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 39 | children: [ 40 | Column( 41 | children: [ 42 | IconButton( 43 | onPressed: () { 44 | /// change selected index 45 | BlocProvider.of(context).changeSelectedIndex(0); 46 | controller.animateToPage(0, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); 47 | }, 48 | icon: Image.asset( 49 | state == 0 ? "assets/images/home_icon.png" : "assets/images/home_icon2.png", 50 | color: state == 0 ? Colors.red : Colors.grey.shade700, 51 | ) 52 | ), 53 | Text( 54 | 'بیسینیور', 55 | style: TextStyle(fontSize: 14,fontFamily: 'Yekan',color: Colors.grey.shade700), 56 | ), 57 | ], 58 | ), 59 | Column( 60 | children: [ 61 | IconButton( 62 | onPressed: () { 63 | BlocProvider.of(context).changeSelectedIndex(1); 64 | controller.animateToPage(1, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); 65 | }, 66 | icon: Image.asset( 67 | state == 1 68 | ? "assets/images/category_icon.png" 69 | : "assets/images/category_icon2.png" , 70 | color: state == 1 ? Colors.red : Colors.grey.shade700, 71 | width: 40, 72 | ) 73 | ), 74 | Text( 75 | 'دسته بندی', 76 | style: TextStyle(fontSize: 14,fontFamily: 'Yekan',color: Colors.grey.shade700), 77 | ), 78 | ], 79 | ), 80 | ], 81 | ), 82 | ), 83 | SizedBox( 84 | width: MediaQuery.of(context).size.width / 2, 85 | height: 72, 86 | child: Row( 87 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 88 | children: [ 89 | Column( 90 | children: [ 91 | IconButton( 92 | onPressed: () async { 93 | BlocProvider.of(context).changeSelectedIndex(2); 94 | controller.animateToPage(2, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); 95 | }, 96 | icon: SvgPicture.asset(state == 2 ? "assets/images/person_icon.svg" : "assets/images/person_icon2.svg", color: state == 2 ? Colors.red : Colors.grey.shade700,width: 48,), 97 | ), 98 | Text( 99 | 'حساب کاربری', 100 | style: TextStyle(fontSize: 14,fontFamily: 'Yekan',color: Colors.grey.shade700), 101 | ), 102 | ], 103 | ), 104 | Column( 105 | children: [ 106 | IconButton( 107 | onPressed: () async { 108 | BlocProvider.of(context).changeSelectedIndex(3); 109 | controller.animateToPage(3, duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); 110 | }, 111 | icon: FaIcon( 112 | state == 3 113 | ? Icons.shopping_cart 114 | : Icons.shopping_cart_outlined, 115 | color: state == 3 ? Colors.red : Colors.grey.shade700, 116 | size: 27, 117 | ) 118 | ), 119 | Text( 120 | 'سبد خرید', 121 | style: TextStyle(fontSize: 14,fontFamily: 'Yekan',color: Colors.grey.shade700), 122 | ), 123 | ], 124 | ), 125 | ], 126 | ), 127 | ), 128 | ], 129 | ); 130 | }, 131 | ), 132 | ), 133 | ); 134 | } 135 | 136 | 137 | Future getDataFromPrefs() async { 138 | // Obtain shared preferences. 139 | final prefs = await SharedPreferences.getInstance(); 140 | final bool loggedIn = prefs.getBool('user_loggedIn') ?? false; 141 | 142 | return loggedIn; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /lib/features/feature_intro/presentation/screens/intro_main_wrapper.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/common/utils/prefs_operator.dart'; 3 | import 'package:besenior_shop_course/common/widgets/main_wrapper.dart'; 4 | import 'package:besenior_shop_course/features/feature_intro/presentation/bloc/intro_cubit/intro_cubit.dart'; 5 | import 'package:besenior_shop_course/features/feature_intro/presentation/widgets/intro_page.dart'; 6 | import 'package:besenior_shop_course/test_screen.dart'; 7 | import 'package:delayed_widget/delayed_widget.dart'; 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_bloc/flutter_bloc.dart'; 10 | import 'package:smooth_page_indicator/smooth_page_indicator.dart'; 11 | 12 | import '../../../../locator.dart'; 13 | import '../widgets/get_start_btn.dart'; 14 | 15 | class IntroMainWrapper extends StatelessWidget { 16 | static const routeName = "/intro_main_wrapper"; 17 | IntroMainWrapper({Key? key}) : super(key: key); 18 | 19 | final PageController pageController = PageController(); 20 | 21 | final List introPages = [ 22 | const IntroPage(title: 'تخصص حرف اول رو میزنه!', description: 'اپلیکیشن تخصصی خرید و فروش انواع قطعات یدکی خودروهای داخلی و خارجی با ضمانت اصالت کالا و نازلترین قیمت',image: "assets/images/benz.png",), 23 | const IntroPage(title: 'آسان خرید و فروش کن!', description: 'خرید و فروش سریع و آسان همراه با تیم پشتیبانی قوی',image: "assets/images/bmw.png",), 24 | const IntroPage(title: 'همه چی اینجا هست!', description: 'ثبت قطعات کمیاب و خرید و فروش عمده تنها با یک کلیک',image: "assets/images/tara.png",), 25 | ]; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | // final args = ModalRoute.of(context)!.settings.arguments as String; 30 | /// get device size 31 | var height = MediaQuery.of(context).size.height; 32 | var width = MediaQuery.of(context).size.width; 33 | 34 | return BlocProvider( 35 | create: (context) => IntroCubit(), 36 | child: Builder( 37 | builder: (context) { 38 | return Scaffold( 39 | body: Stack( 40 | children: [ 41 | /// image 42 | Positioned( 43 | top: 0, 44 | child: Container( 45 | decoration: const BoxDecoration( 46 | color: Colors.amber, 47 | borderRadius: BorderRadius.only(bottomRight: Radius.circular(150)) 48 | ), 49 | width: width, 50 | height: height * 0.6, 51 | ) 52 | ), 53 | 54 | Positioned( 55 | bottom: height * 0.1, 56 | child: SizedBox( 57 | width: width, 58 | height: height * 0.9, 59 | child: PageView( 60 | onPageChanged: (index){ 61 | if(index == 2){ 62 | BlocProvider.of(context).changeGetStart(true); 63 | }else{ 64 | BlocProvider.of(context).changeGetStart(false); 65 | } 66 | }, 67 | controller: pageController, 68 | children: introPages, 69 | ) 70 | )), 71 | 72 | /// GetStarted Btn 73 | Positioned( 74 | bottom: height * 0.07, 75 | right: 30, 76 | child: BlocBuilder( 77 | builder: (context, state) { 78 | if(state.showGetStart){ 79 | return GetStartBtn( 80 | text: 'شروع کنید', 81 | onTap: (){ 82 | PrefsOperator prefsOperator = locator(); 83 | prefsOperator.changeIntroState(); 84 | 85 | /// goto home screen 86 | Navigator.pushNamedAndRemoveUntil(context, MainWrapper.routeName, ModalRoute.withName("main_wrapper"),); 87 | }, 88 | ); 89 | }else{ 90 | return DelayedWidget( 91 | delayDuration: const Duration(milliseconds: 500),// Not required 92 | animationDuration: const Duration(seconds: 1),// Not required 93 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM,// Not required 94 | child: GetStartBtn( 95 | text: 'ورق بزن', 96 | onTap: (){ 97 | if(pageController.page!.toInt() < 2){ 98 | if(pageController.page!.toInt() == 1){ 99 | BlocProvider.of(context).changeGetStart(true); 100 | } 101 | 102 | pageController.animateToPage( 103 | pageController.page!.toInt() + 1, 104 | duration: const Duration(milliseconds: 400), 105 | curve: Curves.easeIn 106 | ); 107 | } 108 | }, 109 | ), 110 | ); 111 | } 112 | 113 | }, 114 | ), 115 | ), 116 | 117 | Positioned( 118 | bottom: height * 0.07, 119 | left: 30, 120 | child: DelayedWidget( 121 | delayDuration: const Duration(milliseconds: 300),// Not required 122 | animationDuration: const Duration(seconds: 1),// Not required 123 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM,// Not required 124 | child: SmoothPageIndicator( 125 | controller: pageController, 126 | count: 3, 127 | effect: ExpandingDotsEffect( 128 | dotWidth: 10, 129 | dotHeight: 10, 130 | spacing: 5, 131 | activeDotColor: Colors.amber 132 | ), 133 | ) 134 | ), 135 | ) 136 | ], 137 | ), 138 | ); 139 | } 140 | ), 141 | ); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/widgets/search_textfield.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:besenior_shop_course/common/blocs/searchbox_cubit.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_bloc/flutter_bloc.dart'; 5 | import 'package:flutter_typeahead/flutter_typeahead.dart'; 6 | 7 | import '../../../../common/params/products_params.dart'; 8 | import '../../data/models/all_products_model.dart'; 9 | import '../../repositories/all_product_repository.dart'; 10 | 11 | 12 | class SearchTextField extends StatelessWidget { 13 | final TextEditingController controller; 14 | final AllProductsRepository allProductsRepository; 15 | SearchTextField({Key? key,required this.controller,required this.allProductsRepository}) : super(key: key); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return BlocProvider( 20 | create: (context) => SearchboxCubit(), 21 | child: Builder( 22 | builder: (context) { 23 | return Material( 24 | child: Stack( 25 | alignment: AlignmentDirectional.center, 26 | children: [ 27 | /// textfield 28 | SizedBox( 29 | height: 40, 30 | child: TypeAheadField( 31 | noItemsFoundBuilder: (context){ 32 | return ListTile( 33 | title: Text("محصولی یافت نشد."), 34 | ); 35 | }, 36 | textFieldConfiguration: TextFieldConfiguration( 37 | onTap: () { 38 | BlocProvider.of(context).changeVisibility(false); 39 | 40 | if (controller.text[controller.text.length - 1] != ' ') { 41 | controller.text = (controller.text + ' '); 42 | } 43 | if (controller.selection ==TextSelection.fromPosition( 44 | TextPosition(offset: 45 | controller.text.length - 1))) { 46 | } 47 | }, 48 | onSubmitted: (String prefix) { 49 | // Navigator.pushNamed( 50 | // context, 51 | // AllProductsScreen.routeName, 52 | // arguments: ProductsArguments(searchTxt: prefix),); 53 | }, 54 | controller: controller, 55 | style: const TextStyle(color: Colors.black,fontSize: 14,fontWeight: FontWeight.w400), 56 | decoration: InputDecoration( 57 | fillColor: Colors.grey.shade200, 58 | filled: true, 59 | contentPadding: EdgeInsets.all(3), 60 | prefixIcon: Container( 61 | child: IconButton( 62 | icon: const Icon(Icons.search,color: Colors.grey,), 63 | onPressed: () { 64 | // Navigator.pushNamed( 65 | // context, 66 | // AllProductsScreen.routeName, 67 | // arguments: ProductsArguments(searchTxt: controller.text),); 68 | }, 69 | ) 70 | ), 71 | enabledBorder:OutlineInputBorder( 72 | borderRadius: BorderRadius.circular(15.0), 73 | borderSide: const BorderSide( 74 | color: Colors.transparent, 75 | width: 0.0, 76 | ), 77 | ), 78 | border: OutlineInputBorder( 79 | borderRadius: BorderRadius.circular(15.0), 80 | borderSide: const BorderSide( 81 | color: Colors.blue, 82 | width: 0.0, 83 | ), 84 | ), 85 | focusedBorder: OutlineInputBorder( 86 | borderRadius: BorderRadius.circular(15.0), 87 | borderSide: const BorderSide( 88 | color: Colors.blue, 89 | width: 2.0, 90 | ), 91 | ),), 92 | ), 93 | suggestionsCallback: (String prefix){ 94 | return allProductsRepository.fetchAllProductsDataSearch(ProductsParams(step: 6,search: prefix)); 95 | }, 96 | itemBuilder: (context, Products model){ 97 | return ListTile( 98 | title: Text(model.name!,style: const TextStyle(color: Colors.black,fontSize: 14,fontWeight: FontWeight.w400),), 99 | ); 100 | }, 101 | onSuggestionSelected: (Products products){ 102 | controller.text = products.name!; 103 | // Navigator.pushNamed( 104 | // context, 105 | // AllProductsScreen.routeName, 106 | // arguments: ProductsArguments(searchTxt: products.name!),); 107 | } 108 | ), 109 | ), 110 | 111 | /// text and logo 112 | IgnorePointer( 113 | child: BlocBuilder( 114 | builder: (context, state) { 115 | return Visibility( 116 | visible: state, 117 | child: Padding( 118 | padding: const EdgeInsets.only(right: 50.0), 119 | child: Row( 120 | children: [ 121 | const Text('جستوجو در', style: TextStyle(fontSize: 17,fontFamily: "Yekan",color: Colors.grey, fontWeight: FontWeight.w200),), 122 | ColorFiltered( 123 | colorFilter: ColorFilter.mode(Colors.grey.shade800, BlendMode.srcIn), 124 | child: Image.asset("assets/images/bs_logo_textfield.png",height: 28,)), 125 | ], 126 | ), 127 | ), 128 | ); 129 | }, 130 | ), 131 | ) 132 | ], 133 | ), 134 | ); 135 | } 136 | ), 137 | ); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /assets/images/amazing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /lib/features/feature_product/presentation/widgets/products_grid.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_bloc/flutter_bloc.dart'; 4 | import 'package:persian_number_utility/persian_number_utility.dart'; 5 | import '../../../../../common/widgets/dot_loading_widget.dart'; 6 | import 'package:cached_network_image/cached_network_image.dart'; 7 | import 'package:delayed_widget/delayed_widget.dart'; 8 | 9 | import '../../../../common/params/products_params.dart'; 10 | import '../../../../common/widgets/paging_loading_widget.dart'; 11 | import '../../data/models/all_products_model.dart'; 12 | import '../bloc/all_poducts_cubit/all_products_cubit.dart'; 13 | 14 | class ProductsGrid extends StatelessWidget { 15 | final int? categoryId; 16 | final int? sellerId; 17 | final String? searchText; 18 | ProductsGrid({Key? key, this.categoryId, this.sellerId, this.searchText}) : super(key: key); 19 | 20 | final ScrollController scrollController = ScrollController(); 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | 25 | setupScrollController(context); 26 | /// call api for data 27 | BlocProvider.of(context).loadProductsData(ProductsParams(categories: categoryId,search: searchText ?? "")); 28 | 29 | return BlocBuilder( 30 | builder: (context, state) { 31 | 32 | if(state.productsDataStatus is ProductsDataLoading){ 33 | return const Center(child: DotLoadingWidget(size: 30)); 34 | } 35 | 36 | if(state.productsDataStatus is ProductsDataCompleted){ 37 | ProductsDataCompleted productsDataCompleted = state.productsDataStatus as ProductsDataCompleted; 38 | AllProductsModel allProductsModel = productsDataCompleted.allProductsModel; 39 | List allProducts = state.allProducts; 40 | 41 | return RefreshIndicator( 42 | onRefresh: () async { 43 | // BlocProvider.of(context).add(ResetNextStartEvent()); 44 | BlocProvider.of(context).loadProductsData(ProductsParams(categories: categoryId,search: searchText ?? "")); 45 | }, 46 | child: Padding( 47 | padding: const EdgeInsets.symmetric(horizontal: 10.0), 48 | child: Column( 49 | children: [ 50 | const SizedBox(height: 10,), 51 | /// filter btn 52 | SizedBox( 53 | height: 60, 54 | width: double.infinity, 55 | child: ElevatedButton( 56 | style: ElevatedButton.styleFrom( 57 | backgroundColor: Colors.white, 58 | elevation: 3, 59 | shadowColor: Colors.redAccent, 60 | shape: RoundedRectangleBorder( 61 | borderRadius: BorderRadius.circular(10) 62 | ) 63 | ), 64 | onPressed: (){ 65 | // showFilterBottomSheet(context); 66 | }, 67 | child: Row( 68 | mainAxisAlignment: MainAxisAlignment.center, 69 | children: const [ 70 | Text('فیلتر', style: TextStyle(fontFamily: 'Vazir', color: Colors.black),), 71 | Icon(Icons.filter_list_alt,color: Colors.black), 72 | ], 73 | ) 74 | ), 75 | ), 76 | 77 | const SizedBox(height: 10), 78 | 79 | /// all product gridview or no products for show 80 | (allProducts.isNotEmpty) 81 | ? Expanded( 82 | child: GridView.builder( 83 | controller: scrollController, 84 | padding: const EdgeInsets.only(top: 10), 85 | itemCount: allProducts.length, 86 | // itemCount: allProductsModel.data![0].products!.length, 87 | gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( 88 | crossAxisCount: 2, 89 | crossAxisSpacing: 8.0, 90 | mainAxisSpacing: 8.0, 91 | childAspectRatio: 0.65 92 | ), 93 | itemBuilder: (BuildContext context, int index){ 94 | final productImage = allProducts[index].image; 95 | final productName = allProducts[index].name; 96 | final productCategoryName = allProducts[index].category; 97 | final productDiscount = allProducts[index].discount; 98 | final productPrice = allProducts[index].price; 99 | final productPriceBeforeDiscount = allProducts[index].priceBeforDiscount; 100 | 101 | return GestureDetector( 102 | onTap: (){ 103 | /// goto All products screen 104 | // Navigator.pushNamed(context, ProductDetailScreen.routeName, arguments: ProductDetailArguments(allProducts[index].id!),); 105 | }, 106 | child: Container( 107 | decoration: BoxDecoration( 108 | color: Colors.white, 109 | borderRadius: BorderRadius.circular(20), 110 | boxShadow: const [ 111 | BoxShadow( 112 | blurRadius: 5, 113 | offset: Offset(2, 2), 114 | color: Colors.grey 115 | ) 116 | ] 117 | ), 118 | child: Padding( 119 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 120 | child: Column( 121 | crossAxisAlignment: CrossAxisAlignment.start, 122 | children: [ 123 | 124 | /// product image 125 | ClipRRect( 126 | borderRadius: BorderRadius.circular(20), 127 | child: CachedNetworkImage( 128 | imageUrl: productImage!, 129 | placeholder: (context, string){ 130 | return const SizedBox( 131 | height: 100, 132 | ); 133 | }, 134 | errorWidget: (context, string, dynamic){ 135 | return Icon(Icons.error,color: Colors.black,); 136 | }, 137 | fit: BoxFit.cover, 138 | useOldImageOnUrlChange: true, 139 | ), 140 | ), 141 | 142 | /// product name 143 | Text(productName!, style: const TextStyle(fontFamily: 'Vazir',color: Colors.black, fontWeight: FontWeight.bold,fontSize: 12),), 144 | Text(productCategoryName!, style: const TextStyle(fontFamily: 'Vazir',color: Colors.grey, fontWeight: FontWeight.bold,fontSize: 12),), 145 | SizedBox(height: 10,), 146 | 147 | /// product price and discount 148 | Row( 149 | children: [ 150 | 151 | /// discount red container 152 | (productDiscount != 0) 153 | ? Container( 154 | width: 40, 155 | height: 30, 156 | decoration: BoxDecoration( 157 | color: Colors.red, 158 | borderRadius: BorderRadius.circular(20) 159 | ), 160 | child: Center(child: Text("${productDiscount.toString().toPersianDigit()}%", style: const TextStyle(fontFamily: 'Vazir',color: Colors.white, fontWeight: FontWeight.bold,fontSize: 13),)), 161 | ) 162 | : Container(), 163 | 164 | const Spacer(), 165 | 166 | Row( 167 | crossAxisAlignment: CrossAxisAlignment.start, 168 | children: [ 169 | Column( 170 | children: [ 171 | Text(productPrice.toString().toPersianDigit(), style: const TextStyle(fontFamily: 'Vazir',color: Colors.black, fontWeight: FontWeight.bold,fontSize: 13),), 172 | (productPriceBeforeDiscount != "0") 173 | ? Text(productPriceBeforeDiscount!.toPersianDigit(), style: const TextStyle(fontFamily: 'Vazir',color: Colors.black, fontWeight: FontWeight.bold,fontSize: 11,decoration: TextDecoration.lineThrough),) 174 | : Container(), 175 | ], 176 | ), 177 | SizedBox(width: 5,), 178 | const Text('تومان', style: TextStyle(fontFamily: 'Vazir',color: Colors.black, fontWeight: FontWeight.bold,fontSize: 10),), 179 | ], 180 | ), 181 | ], 182 | ), 183 | ], 184 | ), 185 | ), 186 | ), 187 | ); 188 | }, 189 | ), 190 | ) 191 | : const Expanded(child: Center(child: Text('محصولی برای نمایش وجود ندارد', style: TextStyle(fontFamily: 'Vazir', color: Colors.black)),)), 192 | 193 | 194 | /// paging loading 195 | (state.isLoadingPaging) 196 | ? Padding( 197 | padding: const EdgeInsets.symmetric(vertical: 15.0), 198 | child: DelayedWidget( 199 | delayDuration: const Duration(milliseconds: 100),// Not required 200 | animationDuration: const Duration(milliseconds: 500),// Not required 201 | animation: DelayedAnimations.SLIDE_FROM_BOTTOM, 202 | child: const PagingLoadingWidget(size: 40) 203 | ), 204 | ) 205 | : Container(), 206 | ], 207 | ), 208 | ), 209 | ); 210 | } 211 | 212 | if(state.productsDataStatus is ProductsDataError){ 213 | final ProductsDataError productsDataError = state.productsDataStatus as ProductsDataError; 214 | 215 | return Center( 216 | child: Column( 217 | mainAxisAlignment: MainAxisAlignment.center, 218 | children: [ 219 | Text(productsDataError.errorMessage,style: const TextStyle(color: Colors.black),), 220 | const SizedBox(height: 10,), 221 | ElevatedButton( 222 | style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent), 223 | onPressed: (){ 224 | /// call all data again 225 | BlocProvider.of(context).loadProductsData(ProductsParams(categories: categoryId,search: searchText ?? "")); 226 | 227 | }, 228 | child: const Text("تلاس دوباره"),) 229 | ], 230 | ), 231 | ); 232 | } 233 | 234 | return Container(); 235 | }, 236 | ); 237 | } 238 | 239 | void setupScrollController(BuildContext context){ 240 | scrollController.addListener(() { 241 | if(scrollController.position.atEdge){ 242 | if(scrollController.position.pixels != 0){ 243 | BlocProvider.of(context).loadProductsData(ProductsParams(categories: categoryId,search: searchText ?? "")); 244 | } 245 | } 246 | }); 247 | } 248 | 249 | // void showFilterBottomSheet(ct) { 250 | // showModalBottomSheet( 251 | // context: ct, 252 | // isScrollControlled: true, 253 | // shape: const RoundedRectangleBorder( 254 | // borderRadius: BorderRadius.only(topRight: Radius.circular(20),topLeft: Radius.circular(20)), 255 | // ), 256 | // builder: (context){ 257 | // return BlocProvider.value( 258 | // value: BlocProvider.of(ct), 259 | // child: BlocProvider.value( 260 | // value: BlocProvider.of(ct), 261 | // child: FilterBottomSheet(categoryId: categoryId,)), 262 | // ); 263 | // }); 264 | // } 265 | } 266 | --------------------------------------------------------------------------------