├── src ├── s1.png ├── s3.jpg ├── s2.jpeg ├── s4.jpeg ├── s5.jpeg └── revenue model.pptx ├── lib ├── bloc │ ├── base_bloc.dart │ ├── location_bloc.dart │ ├── injector.dart │ ├── provider.dart │ ├── home_bloc.dart │ ├── inpiration_bloc.dart │ ├── stylist_detail_bloc.dart │ ├── salon_detail_bloc.dart │ ├── profile_bloc.dart │ ├── add_profile_detail_bloc.dart │ ├── product_list_bloc.dart │ ├── area_bloc.dart │ ├── service_bloc.dart │ └── salon_bloc.dart ├── utils │ ├── app_utils.dart │ ├── snackbar_utils.dart │ ├── log_utils.dart │ └── dialog_utils.dart ├── widget │ ├── view_all_button.dart │ ├── custom_progress_indicator.dart │ ├── gender_widget.dart │ ├── promotion_card.dart │ ├── bottom_bar_item.dart │ ├── select_age_field.dart │ ├── select_gender_field.dart │ ├── home │ │ ├── category_widget.dart │ │ └── category_container.dart │ ├── product_card.dart │ ├── CityWidget.dart │ ├── area_widget.dart │ ├── book_card.dart │ ├── stylist_card.dart │ ├── feed_card.dart │ └── salon_card.dart ├── network_utils.dart ├── data │ ├── model │ │ ├── promotion_model.g.dart │ │ ├── area_model.g.dart │ │ ├── category_model.g.dart │ │ ├── inspiraton_model.g.dart │ │ ├── area_model.dart │ │ ├── salon_model.g.dart │ │ ├── promotion_model.dart │ │ ├── inspiraton_model.dart │ │ ├── category_model.dart │ │ ├── product_list_model.g.dart │ │ ├── stylist_model.g.dart │ │ ├── salon_detail_model.dart │ │ ├── salon_model.dart │ │ ├── product_list_model.dart │ │ ├── stylist_model.dart │ │ ├── service_model.g.dart │ │ ├── service_model.dart │ │ ├── salon_detail_model.g.dart │ │ ├── stylist_detail_model.dart │ │ └── stylist_detail_model.g.dart │ ├── network │ │ └── api_endpoint.dart │ └── local │ │ └── SharedPrefsHelper.dart ├── feed_model.g.dart ├── feed_model.dart ├── pages │ ├── location_page.dart │ ├── tab │ │ ├── feed_tab.dart │ │ └── inspiration_tab.dart │ ├── product_page.dart │ ├── home_page.dart │ ├── add_profile_detail_page.dart │ ├── profile_page.dart │ ├── salon_service_page.dart │ ├── area_page.dart │ ├── salon_detail_page.dart │ └── login_page.dart ├── main.dart └── api_helper.dart ├── assets ├── images │ ├── feed.png │ ├── logo.jpg │ ├── logo.png │ ├── splash.jpeg │ └── location.png └── fonts │ ├── MyIcons.ttf │ ├── nunitoBold.ttf │ ├── nunitoSemiBold.ttf │ ├── whitneybookbas.otf │ └── sourcesanspro-regular.otf ├── android ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── app │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── splash.png │ │ │ │ └── launch_background.xml │ │ │ ├── drawable-hdpi │ │ │ │ ├── hastag.png │ │ │ │ └── splash.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── hastag.png │ │ │ │ └── splash.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── hastag.png │ │ │ │ └── splash.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── hastag.png │ │ │ │ └── splash.png │ │ │ ├── drawable-xxxhdpi │ │ │ │ ├── hastag.png │ │ │ │ └── splash.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── salon │ │ │ └── MainActivity.java │ └── build.gradle ├── gradle.properties ├── .gitignore ├── settings.gradle ├── build.gradle ├── gradlew.bat └── gradlew ├── ios ├── Flutter │ ├── Debug.xcconfig │ ├── Release.xcconfig │ ├── flutter_export_environment.sh │ └── AppFrameworkInfo.plist ├── Runner │ ├── AppDelegate.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 │ ├── main.m │ ├── AppDelegate.m │ ├── Info.plist │ └── Base.lproj │ │ ├── Main.storyboard │ │ └── LaunchScreen.storyboard ├── Runner.xcworkspace │ └── contents.xcworkspacedata ├── Runner.xcodeproj │ ├── project.xcworkspace │ │ └── contents.xcworkspacedata │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── .gitignore └── Podfile ├── .metadata ├── .gitignore ├── README.md └── pubspec.yaml /src/s1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/src/s1.png -------------------------------------------------------------------------------- /src/s3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/src/s3.jpg -------------------------------------------------------------------------------- /lib/bloc/base_bloc.dart: -------------------------------------------------------------------------------- 1 | abstract class BaseBloc { 2 | void dispose(); 3 | } 4 | -------------------------------------------------------------------------------- /src/s2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/src/s2.jpeg -------------------------------------------------------------------------------- /src/s4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/src/s4.jpeg -------------------------------------------------------------------------------- /src/s5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/src/s5.jpeg -------------------------------------------------------------------------------- /assets/images/feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/images/feed.png -------------------------------------------------------------------------------- /assets/images/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/images/logo.jpg -------------------------------------------------------------------------------- /assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/images/logo.png -------------------------------------------------------------------------------- /src/revenue model.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/src/revenue model.pptx -------------------------------------------------------------------------------- /assets/fonts/MyIcons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/fonts/MyIcons.ttf -------------------------------------------------------------------------------- /assets/images/splash.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/images/splash.jpeg -------------------------------------------------------------------------------- /assets/fonts/nunitoBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/fonts/nunitoBold.ttf -------------------------------------------------------------------------------- /assets/images/location.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/images/location.png -------------------------------------------------------------------------------- /assets/fonts/nunitoSemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/fonts/nunitoSemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/whitneybookbas.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/fonts/whitneybookbas.otf -------------------------------------------------------------------------------- /assets/fonts/sourcesanspro-regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/assets/fonts/sourcesanspro-regular.otf -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable/splash.png -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/hastag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-hdpi/hastag.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-hdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/hastag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-mdpi/hastag.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-mdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/hastag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-xhdpi/hastag.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-xhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/hastag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-xxhdpi/hastag.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-xxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/hastag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-xxxhdpi/hastag.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/drawable-xxxhdpi/splash.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/HEAD/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apgapg/DotMyStyle_App/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/apgapg/DotMyStyle_App/HEAD/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | /.idea/ 12 | -------------------------------------------------------------------------------- /lib/utils/app_utils.dart: -------------------------------------------------------------------------------- 1 | bool get isDebug { 2 | bool inDebugMode = false; 3 | assert(inDebugMode = true); 4 | return inDebugMode; 5 | } 6 | 7 | class AppUtils { 8 | //Check if debug mode 9 | 10 | } 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/utils/snackbar_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SnackbarUtils { 4 | static void show(GlobalKey scaffoldKey,String text) { 5 | scaffoldKey.currentState.showSnackBar(new SnackBar(content: Text(text),)); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /.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 and should not be manually edited. 5 | 6 | version: 7 | revision: e4b989bf3dbefc61f11bce298d16f92ebd9cde41 8 | channel: dev 9 | -------------------------------------------------------------------------------- /lib/widget/view_all_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ViewAll extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return Text( 7 | "VIEW ALL", 8 | style: TextStyle(fontWeight: FontWeight.w700, letterSpacing: 1.2, color: Colors.red, fontSize: 12.0), 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lib/widget/custom_progress_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CustomProgressIndicator extends StatelessWidget { 4 | @override 5 | Widget build(BuildContext context) { 6 | return new CircularProgressIndicator( 7 | valueColor: new AlwaysStoppedAnimation(Colors.white), 8 | strokeWidth: 0.5, 9 | ); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /lib/utils/log_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:salon/utils/app_utils.dart'; 2 | 3 | /// Prints a string representation of the object to the console. 4 | /// 5 | /// Prints only in 'debug' mode 6 | printLog(dynamic o, {bool upload = false, StackTrace stackTrace}) { 7 | if (isDebug) { 8 | print(o.toString() ?? ""); 9 | } 10 | /*if (upload) { 11 | CrashUtils.logError(error: o.toString(), stackTrace: stackTrace); 12 | }*/ 13 | } 14 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /lib/bloc/location_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:rxdart/rxdart.dart'; 2 | import 'package:salon/api_helper.dart'; 3 | import 'package:salon/bloc/base_bloc.dart'; 4 | 5 | class LocationBloc extends BaseBloc { 6 | final ApiHelper apiHelper; 7 | BehaviorSubject _dataController; 8 | 9 | LocationBloc(this.apiHelper) { 10 | _dataController = new BehaviorSubject(); 11 | initData(); 12 | } 13 | 14 | @override 15 | void dispose() { 16 | _dataController.close(); 17 | } 18 | 19 | void initData() { 20 | apiHelper.getLocationData(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lib/network_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:http/http.dart' as http; 2 | import 'package:meta/meta.dart'; 3 | 4 | class NetworkUtils { 5 | static isReqSuccess({@required String tag, @required http.Response response}) { 6 | print("Network Request: " + tag + ": " + "ResponseBody: " + response.body.toString()); 7 | if (response.statusCode < 200 || response.statusCode >= 300) { 8 | print("Network Request: " + tag + ": " "ResponseErrorCode: " + response.statusCode.toString()); 9 | return false; 10 | } else { 11 | return true; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/bloc/injector.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/api_helper.dart'; 3 | 4 | class Injector extends InheritedWidget { 5 | final ApiHelper apiHelper = new ApiHelper(); 6 | 7 | Injector({ 8 | Key key, 9 | @required Widget child, 10 | }) : super(key: key, child: child); 11 | 12 | static Injector of(BuildContext context) => context.inheritFromWidgetOfExactType(Injector); 13 | 14 | @override 15 | bool updateShouldNotify(Injector oldWidget) => false; 16 | 17 | ApiHelper getApiHelper() { 18 | return apiHelper; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.1.2' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /lib/data/model/promotion_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'promotion_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | PromotionItem _$PromotionItemFromJson(Map json) { 10 | return PromotionItem(json['id'] as String, json['image'] as String); 11 | } 12 | 13 | Map _$PromotionItemToJson(PromotionItem instance) => 14 | {'id': instance.id, 'image': instance.promotion_image}; 15 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /lib/data/model/area_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'area_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | AreaItem _$AreaItemFromJson(Map json) { 10 | $checkKeys(json, requiredKeys: const ['id', 'location']); 11 | return AreaItem(json['id'] as String, json['location'] as String); 12 | } 13 | 14 | Map _$AreaItemToJson(AreaItem instance) => {'id': instance.id, 'location': instance.location}; 15 | -------------------------------------------------------------------------------- /lib/bloc/provider.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/base_bloc.dart'; 3 | 4 | class Provider extends InheritedWidget { 5 | final T bloc; 6 | final Widget child; 7 | 8 | Provider({@required this.child, @required this.bloc}); 9 | 10 | @override 11 | bool updateShouldNotify(InheritedWidget oldWidget) { 12 | return false; 13 | } 14 | 15 | static T of(BuildContext context) { 16 | final type = _typeOf>(); 17 | Provider provider = context.inheritFromWidgetOfExactType(type); 18 | return provider.bloc; 19 | } 20 | 21 | static Type _typeOf() => T; 22 | } 23 | -------------------------------------------------------------------------------- /lib/widget/gender_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class GenderLabel extends StatelessWidget { 4 | final String genderType; 5 | 6 | GenderLabel(this.genderType); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | assert(genderType != null && genderType.isNotEmpty); 11 | return Container( 12 | height: 28.0, 13 | padding: EdgeInsets.symmetric(horizontal: 8.0), 14 | alignment: Alignment.center, 15 | decoration: BoxDecoration( 16 | borderRadius: BorderRadius.all( 17 | Radius.circular(8.0), 18 | ), 19 | ), 20 | child: Text(genderType), 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/data/network/api_endpoint.dart: -------------------------------------------------------------------------------- 1 | class ApiEndpoint { 2 | static const sendOtp = "login/otp/sendotp"; 3 | static const verifyOtp = "login/otp/verifyotp"; 4 | static const feeds = "dot/feeds"; 5 | static const experts = "dot/stylist"; 6 | static const expertsDetail = "dot/experts/"; 7 | static const salon = "dot/salons"; 8 | static const promotion = "dot/promotions"; 9 | static const inspiration = "dot/inspirations"; 10 | static const salonDetail = "dot/salons/"; 11 | static const service = "dot/services/"; 12 | static const productList = "dot/products/"; 13 | static const categoryList = "dot/categories"; 14 | 15 | static const area = "dot/locations"; 16 | } 17 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /lib/widget/promotion_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/data/model/promotion_model.dart'; 3 | 4 | class PromotionCard extends StatefulWidget { 5 | final PromotionItem item; 6 | 7 | PromotionCard(this.item); 8 | 9 | _PromotionCardState createState() => _PromotionCardState(); 10 | } 11 | 12 | class _PromotionCardState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | return Container( 16 | padding: EdgeInsets.symmetric(horizontal: 4.0), 17 | margin: EdgeInsets.all(2.0), 18 | child: Image.network( 19 | widget.item.promotion_image, 20 | fit: BoxFit.fitHeight, 21 | ), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/data/model/category_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'category_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CategoryItem _$CategoryItemFromJson(Map json) { 10 | return CategoryItem(json['_id'] as String, json['image'] as String, 11 | json['category'] as String); 12 | } 13 | 14 | Map _$CategoryItemToJson(CategoryItem instance) => 15 | { 16 | '_id': instance.id, 17 | 'image': instance.image, 18 | 'category': instance.category 19 | }; 20 | -------------------------------------------------------------------------------- /lib/data/model/inspiraton_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'inspiraton_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | InspirationItem _$InspirationItemFromJson(Map json) { 10 | return InspirationItem(json['id'] as String, json['category'] as String, 11 | json['image'] as String); 12 | } 13 | 14 | Map _$InspirationItemToJson(InspirationItem instance) => 15 | { 16 | 'id': instance.id, 17 | 'category': instance.category, 18 | 'image': instance.url 19 | }; 20 | -------------------------------------------------------------------------------- /ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/ayushpgupta/development/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/ayushpgupta/StudioProjects/DotMyStyle_App" 5 | export "FLUTTER_TARGET=lib/main.dart" 6 | export "FLUTTER_BUILD_DIR=build" 7 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 8 | export "OTHER_LDFLAGS=$(inherited) -framework Flutter" 9 | export "FLUTTER_FRAMEWORK_DIR=/Users/ayushpgupta/development/flutter/bin/cache/artifacts/engine/ios" 10 | export "FLUTTER_BUILD_NAME=1.0.0" 11 | export "FLUTTER_BUILD_NUMBER=1" 12 | export "DART_OBFUSCATION=false" 13 | export "TRACK_WIDGET_CREATION=false" 14 | export "TREE_SHAKE_ICONS=false" 15 | -------------------------------------------------------------------------------- /lib/feed_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'feed_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | FeedItem _$FeedItemFromJson(Map json) { 10 | return FeedItem( 11 | json['id'] as String, 12 | json['description'] as String, 13 | json['image'] as String, 14 | (json['tags'] as List)?.map((e) => e as String)?.toList()); 15 | } 16 | 17 | Map _$FeedItemToJson(FeedItem instance) => { 18 | 'id': instance.id, 19 | 'description': instance.description, 20 | 'image': instance.feed_url, 21 | 'tags': instance.tags 22 | }; 23 | -------------------------------------------------------------------------------- /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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Exceptions to above rules. 44 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 45 | -------------------------------------------------------------------------------- /lib/data/model/area_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'area_model.g.dart'; 4 | 5 | class AreaModel { 6 | List list; 7 | 8 | AreaModel._internal(this.list); 9 | 10 | factory AreaModel.fromJson(dynamic json) { 11 | return AreaModel.fromMapList(list: json as List); 12 | } 13 | 14 | factory AreaModel.fromMapList({List list}) { 15 | final items = list.cast>().map((Map item) { 16 | return AreaItem.fromJson(item); 17 | }).toList(); 18 | 19 | return AreaModel._internal(items); 20 | } 21 | } 22 | 23 | @JsonSerializable() 24 | class AreaItem { 25 | @JsonKey(required: true) 26 | final String id; 27 | @JsonKey(required: true) 28 | final String location; 29 | 30 | AreaItem(this.id, this.location); 31 | 32 | factory AreaItem.fromJson(Map json) => _$AreaItemFromJson(json); 33 | } 34 | -------------------------------------------------------------------------------- /lib/data/model/salon_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'salon_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SalonItem _$SalonItemFromJson(Map json) { 10 | return SalonItem( 11 | json['id'] as String, 12 | json['name'] as String, 13 | json['address'] as String, 14 | json['image'] as String, 15 | json['gender_type'] as String ?? 'Unisex', 16 | json['location'] as String); 17 | } 18 | 19 | Map _$SalonItemToJson(SalonItem instance) => { 20 | 'id': instance.id, 21 | 'name': instance.name, 22 | 'address': instance.address, 23 | 'image': instance.image, 24 | 'gender_type': instance.genderType, 25 | 'location': instance.group 26 | }; 27 | -------------------------------------------------------------------------------- /lib/bloc/home_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/network/api_endpoint.dart'; 7 | import 'package:salon/feed_model.dart'; 8 | 9 | class FeedBloc extends BaseBloc { 10 | BehaviorSubject> feedController; 11 | 12 | FeedBloc() { 13 | feedController = new BehaviorSubject(); 14 | initData(); 15 | } 16 | 17 | void initData() async { 18 | NetworkResponse _networkResponse = await apiHelper.getWithAuth1(endpoint: ApiEndpoint.feeds); 19 | if (_networkResponse.isSuccess) { 20 | FeedModel feedModel = FeedModel.fromJson(json.decode(_networkResponse.response.body)); 21 | feedController.add(feedModel.feedList); 22 | } else { 23 | print("Some error"); 24 | } 25 | } 26 | 27 | @override 28 | void dispose() { 29 | feedController.close(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/data/model/promotion_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'promotion_model.g.dart'; 4 | 5 | class PromotionModel { 6 | List list; 7 | 8 | PromotionModel._internal(this.list); 9 | 10 | factory PromotionModel.fromJson(dynamic json) { 11 | return PromotionModel.fromMapList(list: json as List); 12 | } 13 | 14 | factory PromotionModel.fromMapList({List list}) { 15 | final items = list.cast>().map((Map item) { 16 | return PromotionItem.fromJson(item); 17 | }).toList(); 18 | 19 | return PromotionModel._internal(items); 20 | } 21 | } 22 | 23 | @JsonSerializable() 24 | class PromotionItem { 25 | final String id; 26 | @JsonKey(name: "image") 27 | 28 | final String promotion_image; 29 | 30 | PromotionItem(this.id,this.promotion_image); 31 | 32 | factory PromotionItem.fromJson(Map json) => _$PromotionItemFromJson(json); 33 | } 34 | -------------------------------------------------------------------------------- /lib/data/model/inspiraton_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'inspiraton_model.g.dart'; 4 | 5 | class InspirationModel { 6 | List list; 7 | 8 | InspirationModel._internal(this.list); 9 | 10 | factory InspirationModel.fromJson(dynamic json) { 11 | return InspirationModel.fromMapList(list: json as List); 12 | } 13 | 14 | factory InspirationModel.fromMapList({List list}) { 15 | final items = list.cast>().map((Map item) { 16 | return InspirationItem.fromJson(item); 17 | }).toList(); 18 | 19 | return InspirationModel._internal(items); 20 | } 21 | } 22 | 23 | @JsonSerializable() 24 | class InspirationItem { 25 | final String id; 26 | final String category; 27 | @JsonKey(name:"image") 28 | final String url; 29 | 30 | InspirationItem(this.id,this.category,this.url); 31 | 32 | factory InspirationItem.fromJson(Map json) => _$InspirationItemFromJson(json); 33 | } 34 | -------------------------------------------------------------------------------- /lib/feed_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:meta/meta.dart'; 3 | 4 | part 'feed_model.g.dart'; 5 | 6 | class FeedModel { 7 | List feedList; 8 | 9 | FeedModel._internal(this.feedList); 10 | 11 | factory FeedModel.fromJson(dynamic json) { 12 | return FeedModel.fromMapList(list: json as List); 13 | } 14 | 15 | factory FeedModel.fromMapList({List list}) { 16 | final items = list.cast>().map((Map item) { 17 | return FeedItem.fromJson(item); 18 | }).toList(); 19 | 20 | return FeedModel._internal(items); 21 | } 22 | } 23 | 24 | @JsonSerializable() 25 | class FeedItem { 26 | final String id; 27 | final String description; 28 | @JsonKey(name: "image") 29 | 30 | final String feed_url; 31 | final List tags; 32 | FeedItem(this.id,this.description,this.feed_url,this.tags); 33 | 34 | factory FeedItem.fromJson(Map json) => _$FeedItemFromJson(json); 35 | } 36 | -------------------------------------------------------------------------------- /lib/data/model/category_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'category_model.g.dart'; 4 | 5 | class CategoryModel { 6 | List categoryList; 7 | 8 | CategoryModel._internal(this.categoryList); 9 | 10 | factory CategoryModel.fromJson(dynamic json) { 11 | return CategoryModel.fromMapList(list: json as List); 12 | } 13 | 14 | factory CategoryModel.fromMapList({List list}) { 15 | final items = 16 | list.cast>().map((Map item) { 17 | return CategoryItem.fromJson(item); 18 | }).toList(); 19 | 20 | return CategoryModel._internal(items); 21 | } 22 | } 23 | 24 | @JsonSerializable() 25 | class CategoryItem { 26 | @JsonKey(name: "_id") 27 | final String id; 28 | final String image; 29 | final String category; 30 | 31 | CategoryItem(this.id, this.image, this.category); 32 | 33 | factory CategoryItem.fromJson(Map json) => 34 | _$CategoryItemFromJson(json); 35 | } 36 | -------------------------------------------------------------------------------- /lib/bloc/inpiration_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/model/inspiraton_model.dart'; 7 | import 'package:salon/data/network/api_endpoint.dart'; 8 | import 'package:salon/network_utils.dart'; 9 | 10 | class InspirationBloc extends BaseBloc { 11 | final BehaviorSubject> list = new BehaviorSubject(); 12 | 13 | void initData() async { 14 | try { 15 | var response = await apiHelper.getWithAuth(endpoint: ApiEndpoint.inspiration); 16 | if (NetworkUtils.isReqSuccess(tag: ApiEndpoint.inspiration, response: response)) { 17 | var model = InspirationModel.fromJson(json.decode(response.body)); 18 | list.add(model.list); 19 | } else { 20 | print("Some error"); 21 | } 22 | } catch (e) { 23 | print(e); 24 | } 25 | } 26 | 27 | @override 28 | void dispose() { 29 | print("abc"); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/data/model/product_list_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'product_list_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ProductItem _$ProductItemFromJson(Map json) { 10 | return ProductItem( 11 | json['id'] as String, 12 | json['name'] as String, 13 | json['image'] as String, 14 | json['description'] as String, 15 | json['categoryModel'] == null 16 | ? null 17 | : CategoryModel.fromJson( 18 | json['categoryModel'] as Map)); 19 | } 20 | 21 | Map _$ProductItemToJson(ProductItem instance) => 22 | { 23 | 'id': instance.id, 24 | 'name': instance.name, 25 | 'image': instance.image, 26 | 'description': instance.description, 27 | 'categoryModel': instance.categoryModel 28 | }; 29 | -------------------------------------------------------------------------------- /lib/data/model/stylist_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'stylist_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | StylistItem _$StylistItemFromJson(Map json) { 10 | return StylistItem( 11 | json['id'] as String, 12 | json['name'] as String, 13 | json['about'] as String, 14 | json['gender_type'] as String ?? 'Unisex', 15 | json['tagline'] as String, 16 | json['profile_picture'] as String, 17 | json['phone'] as String); 18 | } 19 | 20 | Map _$StylistItemToJson(StylistItem instance) => 21 | { 22 | 'id': instance.id, 23 | 'name': instance.name, 24 | 'about': instance.about, 25 | 'gender_type': instance.genderType, 26 | 'tagline': instance.tagline, 27 | 'profile_picture': instance.profilePicture, 28 | 'phone': instance.phone 29 | }; 30 | -------------------------------------------------------------------------------- /lib/pages/location_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/injector.dart'; 3 | import 'package:salon/bloc/location_bloc.dart'; 4 | 5 | class LocationPage extends StatefulWidget { 6 | @override 7 | LocationPageState createState() { 8 | return new LocationPageState(); 9 | } 10 | } 11 | 12 | class LocationPageState extends State { 13 | LocationBloc bloc; 14 | 15 | @override 16 | void didChangeDependencies() { 17 | bloc = new LocationBloc(Injector.of(context).getApiHelper()); 18 | super.didChangeDependencies(); 19 | } 20 | 21 | @override 22 | Widget build(BuildContext context) { 23 | return Scaffold( 24 | appBar: AppBar( 25 | title: Text( 26 | "Select Location", 27 | style: TextStyle(color: Colors.blueGrey[700]), 28 | ), 29 | iconTheme: IconThemeData( 30 | color: Colors.blueGrey[700], 31 | ), 32 | elevation: 2.0, 33 | backgroundColor: Colors.white, 34 | ), 35 | body: Container(), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/data/model/salon_detail_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'salon_detail_model.g.dart'; 4 | 5 | @JsonSerializable() 6 | class SalonDetailModel { 7 | @JsonKey(required: true,disallowNullValue: true) 8 | final String id; 9 | @JsonKey(required: true,disallowNullValue: true) 10 | final String name; 11 | @JsonKey(required: true,disallowNullValue: true) 12 | final String address; 13 | final String image; 14 | final List categories; 15 | 16 | SalonDetailModel(this.id, this.name, this.address, this.image, this.categories); 17 | 18 | factory SalonDetailModel.fromJson(Map json) => _$SalonDetailModelFromJson(json); 19 | } 20 | 21 | @JsonSerializable() 22 | class CategoryModel { 23 | @JsonKey(required: true, disallowNullValue: true) 24 | final String id; 25 | @JsonKey(required: true, disallowNullValue: true) 26 | final String category; 27 | 28 | CategoryModel(this.id, this.category); 29 | 30 | factory CategoryModel.fromJson(Map json) => _$CategoryModelFromJson(json); 31 | } 32 | -------------------------------------------------------------------------------- /lib/bloc/stylist_detail_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/model/stylist_detail_model.dart'; 7 | import 'package:salon/data/network/api_endpoint.dart'; 8 | import 'package:salon/network_utils.dart'; 9 | 10 | class StylistDetailBloc implements BaseBloc { 11 | final BehaviorSubject stylistDetail = new BehaviorSubject(); 12 | 13 | void initData(String id) async { 14 | try { 15 | var response = await apiHelper.getWithAuth(endpoint: ApiEndpoint.expertsDetail + id); 16 | if (NetworkUtils.isReqSuccess(tag: ApiEndpoint.expertsDetail, response: response)) { 17 | StylistDetailModel model = StylistDetailModel.fromJson(json.decode(response.body)); 18 | stylistDetail.sink.add(model); 19 | } else { 20 | print("Some error"); 21 | } 22 | } catch (e) { 23 | print(e); 24 | } 25 | } 26 | 27 | @override 28 | void dispose() { 29 | stylistDetail.close(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/bloc/salon_detail_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/model/salon_detail_model.dart'; 7 | import 'package:salon/data/network/api_endpoint.dart'; 8 | import 'package:salon/network_utils.dart'; 9 | 10 | class SalonDetailBloc implements BaseBloc { 11 | final BehaviorSubject salonDetail = new BehaviorSubject(); 12 | 13 | void initData(String id) async { 14 | try { 15 | var response = 16 | await apiHelper.getWithAuth(endpoint: ApiEndpoint.salonDetail + id); 17 | if (NetworkUtils.isReqSuccess( 18 | tag: ApiEndpoint.salonDetail, response: response)) { 19 | SalonDetailModel model = 20 | SalonDetailModel.fromJson(json.decode(response.body)); 21 | salonDetail.sink.add(model); 22 | } else { 23 | print("Some error"); 24 | } 25 | } catch (e) { 26 | print(e); 27 | } 28 | } 29 | 30 | @override 31 | void dispose() { 32 | salonDetail.close(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/data/model/salon_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'salon_model.g.dart'; 4 | 5 | class SalonModel { 6 | List salonList; 7 | 8 | SalonModel._internal(this.salonList); 9 | 10 | factory SalonModel.fromJson(dynamic json) { 11 | return SalonModel.fromMapList(list: json as List); 12 | } 13 | 14 | factory SalonModel.fromMapList({List list}) { 15 | final items = list.cast>().map((Map item) { 16 | return SalonItem.fromJson(item); 17 | }).toList(); 18 | 19 | return SalonModel._internal(items); 20 | } 21 | } 22 | 23 | @JsonSerializable() 24 | class SalonItem { 25 | final String id; 26 | final String name; 27 | final String address; 28 | final String image; 29 | @JsonKey(defaultValue: "Unisex",name: "gender_type") 30 | final String genderType; 31 | @JsonKey(name: "location") 32 | final String group; 33 | 34 | SalonItem(this.id, this.name, this.address, this.image, this.genderType, this.group); 35 | 36 | factory SalonItem.fromJson(Map json) => _$SalonItemFromJson(json); 37 | } 38 | -------------------------------------------------------------------------------- /lib/data/model/product_list_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:salon/data/model/salon_detail_model.dart'; 3 | 4 | part 'product_list_model.g.dart'; 5 | 6 | class ProductListModel { 7 | List list; 8 | 9 | ProductListModel._internal(this.list); 10 | 11 | factory ProductListModel.fromJson(dynamic json) { 12 | return ProductListModel.fromMapList(list: json as List); 13 | } 14 | 15 | factory ProductListModel.fromMapList({List list}) { 16 | final items = list.cast>().map((Map item) { 17 | return ProductItem.fromJson(item); 18 | }).toList(); 19 | 20 | return ProductListModel._internal(items); 21 | } 22 | } 23 | 24 | @JsonSerializable() 25 | class ProductItem { 26 | final String id; 27 | final String name; 28 | final String image; 29 | final String description; 30 | final CategoryModel categoryModel; 31 | 32 | ProductItem(this.id, this.name, this.image, this.description, this.categoryModel); 33 | 34 | factory ProductItem.fromJson(Map json) => _$ProductItemFromJson(json); 35 | } 36 | -------------------------------------------------------------------------------- /lib/bloc/profile_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/network/api_endpoint.dart'; 7 | import 'package:salon/feed_model.dart'; 8 | import 'package:salon/network_utils.dart'; 9 | 10 | class ProfileBloc extends BaseBloc { 11 | BehaviorSubject> profileController; 12 | 13 | ProfileBloc() { 14 | profileController = new BehaviorSubject(); 15 | initData(); 16 | } 17 | 18 | void initData() async { 19 | try { 20 | /* var response = await apiHelper.getWithAuth(endpoint: ApiEndpoint.feeds); 21 | if (NetworkUtils.isReqSuccess(tag: ApiEndpoint.feeds, response: response)) { 22 | print(response.body); 23 | 24 | FeedModel feedModel = FeedModel.fromJson(json.decode(response.body)); 25 | profileController.add(feedModel.feedList); 26 | } else { 27 | print("Some error"); 28 | }*/ 29 | } catch (e) { 30 | print(e); 31 | } 32 | } 33 | 34 | @override 35 | void dispose() { 36 | profileController.close(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/bloc/add_profile_detail_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/base_bloc.dart'; 3 | import 'package:salon/widget/select_age_field.dart'; 4 | import 'package:salon/widget/select_gender_field.dart'; 5 | 6 | class AddProfileDetailBloc extends BaseBloc implements ISelectGender, ISelectAge { 7 | TextEditingController nameController; 8 | 9 | String genderValue; 10 | 11 | String ageValue; 12 | 13 | final formKey = new GlobalKey(); 14 | 15 | AddProfileDetailBloc() { 16 | nameController = new TextEditingController(); 17 | } 18 | 19 | @override 20 | void dispose() {} 21 | 22 | @override 23 | String getGenderValue() { 24 | return this.genderValue; 25 | } 26 | 27 | @override 28 | void setGenderValue(String value) { 29 | this.genderValue = value; 30 | } 31 | 32 | @override 33 | String getAgeValue() { 34 | return this.ageValue; 35 | } 36 | 37 | @override 38 | void setAgeValue(String value) { 39 | this.ageValue = value; 40 | } 41 | 42 | void onSubmit() { 43 | if (formKey.currentState.validate()) { 44 | print(getGenderValue()); 45 | print(getAgeValue()); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /lib/data/model/stylist_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'stylist_model.g.dart'; 4 | 5 | class StylistModel { 6 | List stylistList; 7 | 8 | StylistModel._internal(this.stylistList); 9 | 10 | factory StylistModel.fromJson(dynamic json) { 11 | return StylistModel.fromMapList(list: json as List); 12 | } 13 | 14 | factory StylistModel.fromMapList({List list}) { 15 | final items = list.cast>().map((Map item) { 16 | return StylistItem.fromJson(item); 17 | }).toList(); 18 | 19 | return StylistModel._internal(items); 20 | } 21 | } 22 | 23 | @JsonSerializable() 24 | class StylistItem { 25 | final String id; 26 | final String name; 27 | final String about; 28 | @JsonKey(defaultValue: "Unisex", name: "gender_type") 29 | final String genderType; 30 | final String tagline; 31 | @JsonKey(name: "profile_picture") 32 | final String profilePicture; 33 | final String phone; 34 | 35 | StylistItem(this.id, this.name, this.about, this.genderType, this.tagline, this.profilePicture, this.phone); 36 | 37 | factory StylistItem.fromJson(Map json) => _$StylistItemFromJson(json); 38 | } 39 | -------------------------------------------------------------------------------- /lib/widget/bottom_bar_item.dart: -------------------------------------------------------------------------------- 1 | 2 | import 'package:flutter/material.dart'; 3 | 4 | typedef void OnItemClick(String item); 5 | 6 | class BottomBarItem extends StatelessWidget { 7 | final String text; 8 | 9 | final bool selected; 10 | 11 | final IconData rss_feed; 12 | 13 | final OnItemClick onBottomBarItemTap; 14 | 15 | BottomBarItem(this.text, this.selected, this.rss_feed, this.onBottomBarItemTap); 16 | 17 | @override 18 | Widget build(BuildContext context) { 19 | return Flexible( 20 | flex: 1, 21 | child: GestureDetector( 22 | onTap: () { 23 | onBottomBarItemTap(text); 24 | }, 25 | child: Center( 26 | child: Column( 27 | mainAxisAlignment: MainAxisAlignment.center, 28 | children: [ 29 | Icon( 30 | rss_feed, 31 | size: 20.0, 32 | color: selected ? Colors.blueAccent : Colors.grey[700], 33 | ), 34 | SizedBox( 35 | height: 2.0, 36 | ), 37 | Text( 38 | text, 39 | style: TextStyle(fontWeight: FontWeight.w300, fontSize: 12.0, color: selected ? Colors.blueAccent : Colors.grey[700]), 40 | ) 41 | ], 42 | ), 43 | ), 44 | ), 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/data/model/service_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'service_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | ServiceItem _$ServiceItemFromJson(Map json) { 10 | $checkKeys(json, 11 | requiredKeys: const ['id', 'name', 'price', 'salon', 'category'], 12 | disallowNullValues: const ['id', 'name', 'price', 'salon', 'category']); 13 | return ServiceItem( 14 | json['id'] as String, 15 | json['name'] as String, 16 | json['price'] as int, 17 | json['salon'] as String, 18 | json['category'] == null 19 | ? null 20 | : CategoryModel.fromJson(json['category'] as Map)); 21 | } 22 | 23 | Map _$ServiceItemToJson(ServiceItem instance) { 24 | var val = {}; 25 | 26 | void writeNotNull(String key, dynamic value) { 27 | if (value != null) { 28 | val[key] = value; 29 | } 30 | } 31 | 32 | writeNotNull('id', instance.id); 33 | writeNotNull('name', instance.name); 34 | writeNotNull('price', instance.price); 35 | writeNotNull('salon', instance.salon); 36 | writeNotNull('category', instance.categoryItem); 37 | return val; 38 | } 39 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | import 'package:salon/bloc/injector.dart'; 4 | import 'package:salon/data/local/SharedPrefsHelper.dart'; 5 | import 'package:salon/pages/home_page.dart'; 6 | import 'package:salon/pages/login_page.dart'; 7 | 8 | Future main() async { 9 | SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); 10 | await SharedPrefsHelper().initialize(); 11 | 12 | runApp(new MyApp()); 13 | } 14 | 15 | class MyApp extends StatelessWidget { 16 | // This widget is the root of your application. 17 | @override 18 | Widget build(BuildContext context) { 19 | return Injector( 20 | child: new MaterialApp( 21 | title: 'DotMyStyle', 22 | debugShowCheckedModeBanner: false, 23 | theme: new ThemeData( 24 | //backgroundColor: Color(0xFFE6E7E8), 25 | backgroundColor: Colors.white, 26 | primarySwatch: Colors.blue, 27 | //fontFamily: 'sourcesanspro', 28 | fontFamily: 'whitneybookbas', 29 | scaffoldBackgroundColor: Colors.grey[100], 30 | iconTheme: IconThemeData(color: Colors.blueGrey[700]), 31 | primaryIconTheme: IconThemeData(color: Colors.blueGrey[700]), 32 | ), 33 | home: prefsHelper.isLogin ? HomePage() : LoginPage(), 34 | // home: AreaPage(), 35 | ), 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/widget/select_age_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SelectAgeField extends StatefulWidget { 4 | final ISelectAge age; 5 | 6 | SelectAgeField(this.age); 7 | 8 | @override 9 | SelectAgeFieldState createState() { 10 | return new SelectAgeFieldState(); 11 | } 12 | } 13 | 14 | class SelectAgeFieldState extends State { 15 | final List _list = ["0-20", "20-30", "30-40", "40+"]; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | widget.age.setAgeValue(_list[0]); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container( 26 | child: FormField( 27 | builder: (context) { 28 | return Column( 29 | children: _list 30 | .map((item) => RadioListTile( 31 | title: Text(item), 32 | value: item, 33 | groupValue: widget.age.getAgeValue(), 34 | onChanged: _onChange, 35 | )) 36 | .toList()); 37 | }, 38 | ), 39 | ); 40 | } 41 | 42 | void _onChange(String value) { 43 | setState(() { 44 | widget.age.setAgeValue(value); 45 | }); 46 | } 47 | } 48 | 49 | abstract class ISelectAge { 50 | String getAgeValue(); 51 | 52 | void setAgeValue(String value); 53 | } 54 | -------------------------------------------------------------------------------- /lib/bloc/product_list_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/model/product_list_model.dart'; 7 | import 'package:salon/data/network/api_endpoint.dart'; 8 | import 'package:salon/utils/log_utils.dart'; 9 | 10 | class ProductListBloc extends BaseBloc { 11 | BehaviorSubject> _productSubject; 12 | 13 | ProductListBloc() { 14 | _productSubject = new BehaviorSubject(); 15 | initData(); 16 | } 17 | 18 | get productStream => _productSubject.stream; 19 | 20 | void initData() async { 21 | var _res = await apiHelper.getWithAuth(endpoint: ApiEndpoint.productList); 22 | NetworkResponse _response = apiHelper.parseResponse(_res); 23 | if (_response.isSuccess) { 24 | printLog(_response.message); 25 | ProductListModel _model = ProductListModel.fromJson(json.decode(_response.response.body)); 26 | _productSubject.sink.add(_model.list); 27 | //SalonDetailModel model = 28 | // SalonDetailModel.fromJson(json.decode(response.body)); 29 | // serviceDetail.sink.add(model); 30 | } else { 31 | printLog(_response.message); 32 | _productSubject.sink.addError(_response.message); 33 | 34 | } 35 | } 36 | 37 | @override 38 | void dispose() { 39 | _productSubject.close(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/data/model/service_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | import 'package:meta/meta.dart'; 3 | import 'package:salon/data/model/salon_detail_model.dart'; 4 | 5 | part 'service_model.g.dart'; 6 | 7 | class ServiceModel { 8 | List list; 9 | 10 | ServiceModel._internal(this.list); 11 | 12 | factory ServiceModel.fromJson(dynamic json) { 13 | return ServiceModel.fromMapList(list: json as List); 14 | } 15 | 16 | factory ServiceModel.fromMapList({List list}) { 17 | final items = 18 | list.cast>().map((Map item) { 19 | return ServiceItem.fromJson(item); 20 | }).toList(); 21 | 22 | return ServiceModel._internal(items); 23 | } 24 | } 25 | 26 | @JsonSerializable() 27 | class ServiceItem { 28 | @JsonKey(required: true, disallowNullValue: true) 29 | final String id; 30 | @JsonKey(required: true, disallowNullValue: true) 31 | final String name; 32 | @JsonKey(required: true, disallowNullValue: true) 33 | final int price; 34 | @JsonKey(required: true, disallowNullValue: true) 35 | final String salon; 36 | @JsonKey(name: "category",required: true, disallowNullValue: true) 37 | final CategoryModel categoryItem; 38 | ServiceItem(this.id, this.name, this.price, this.salon, this.categoryItem); 39 | 40 | factory ServiceItem.fromJson(Map json) => 41 | _$ServiceItemFromJson(json); 42 | } 43 | -------------------------------------------------------------------------------- /lib/pages/tab/feed_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/home_bloc.dart'; 3 | import 'package:salon/feed_model.dart'; 4 | import 'package:salon/utils/dialog_utils.dart'; 5 | import 'package:salon/widget/feed_card.dart'; 6 | 7 | class FeedTab extends StatefulWidget { 8 | _FeedTabState createState() => _FeedTabState(); 9 | } 10 | 11 | class _FeedTabState extends State { 12 | FeedBloc _bloc; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | _bloc = new FeedBloc(); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Container( 23 | child: StreamBuilder( 24 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 25 | if (snapshot.hasData) { 26 | return ListView.builder( 27 | padding: EdgeInsets.symmetric(vertical: 4.0), 28 | itemBuilder: (context, index) { 29 | return FeedCard(snapshot.data.elementAt(index)); 30 | }, 31 | itemCount: snapshot.data.length, 32 | ); 33 | } else if (snapshot.hasError) { 34 | return Center( 35 | child: Text("Some error..."), 36 | ); 37 | } else { 38 | return DialogUtils.showCircularProgressBar(); 39 | } 40 | }, 41 | stream: _bloc.feedController.stream, 42 | ), 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/bloc/area_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/model/area_model.dart'; 7 | import 'package:salon/data/network/api_endpoint.dart'; 8 | import 'package:salon/network_utils.dart'; 9 | 10 | class AreaBloc extends BaseBloc { 11 | static AreaBloc _instance = AreaBloc._internal(); 12 | BehaviorSubject> areaController; 13 | 14 | factory AreaBloc() { 15 | if (_instance == null) return _instance = AreaBloc._internal(); 16 | return _instance; 17 | } 18 | 19 | AreaBloc._internal() { 20 | areaController = new BehaviorSubject(); 21 | initData(); 22 | } 23 | 24 | void initData() async { 25 | try { 26 | var response = await apiHelper.getWithAuth(endpoint: ApiEndpoint.area); 27 | if (NetworkUtils.isReqSuccess(tag: ApiEndpoint.promotion, response: response)) { 28 | print(response.body); 29 | var model = AreaModel.fromJson(json.decode(response.body)); 30 | assert(model.list.isNotEmpty); 31 | areaController.add(model.list); 32 | } else { 33 | areaController.addError("Something went wrong. ErrorCode: " + response.statusCode.toString()); 34 | } 35 | } catch (e) { 36 | print(e); 37 | areaController.addError("Something went wrong."); 38 | } 39 | } 40 | 41 | @override 42 | void dispose() { 43 | areaController.close(); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/bloc/service_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/model/service_model.dart'; 7 | import 'package:salon/data/network/api_endpoint.dart'; 8 | import 'package:salon/network_utils.dart'; 9 | 10 | class ServiceBloc extends BaseBloc { 11 | final BehaviorSubject serviceDetail = new BehaviorSubject(); 12 | 13 | void initData(String salonId, String categoryId) async { 14 | try { 15 | assert(salonId != null && salonId.isNotEmpty); 16 | var response; 17 | if (categoryId != null && categoryId.isNotEmpty) 18 | response = await apiHelper.getWithAuth(endpoint: ApiEndpoint.service + salonId + "?category=" + categoryId); 19 | else 20 | response = await apiHelper.getWithAuth(endpoint: ApiEndpoint.service + salonId); 21 | 22 | if (NetworkUtils.isReqSuccess(tag: ApiEndpoint.salonDetail, response: response)) { 23 | //SalonDetailModel model = 24 | // SalonDetailModel.fromJson(json.decode(response.body)); 25 | // serviceDetail.sink.add(model); 26 | ServiceModel model=ServiceModel.fromJson(json.decode(response.body)); 27 | serviceDetail.sink.add(model); 28 | } else { 29 | print("Some error"); 30 | } 31 | } catch (e) { 32 | print(e); 33 | } 34 | } 35 | 36 | @override 37 | void dispose() { 38 | serviceDetail.close(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/widget/select_gender_field.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SelectGenderField extends StatefulWidget { 4 | final ISelectGender gender; 5 | 6 | SelectGenderField(this.gender); 7 | 8 | @override 9 | SelectGenderFieldState createState() { 10 | return new SelectGenderFieldState(); 11 | } 12 | } 13 | 14 | class SelectGenderFieldState extends State { 15 | final List _list = ["Male", "Female"]; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | widget.gender.setGenderValue(_list[0]); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Container( 26 | child: FormField( 27 | builder: (context) { 28 | return Row( 29 | children: [ 30 | Container(width: 100.0, child: RadioListTile(title: Icon(Icons.person), value: _list[0], groupValue: widget.gender.getGenderValue(), onChanged: _onChange)), 31 | Container(width: 100.0, child: RadioListTile(title: Icon(Icons.person_add), value: _list[1], groupValue: widget.gender.getGenderValue(), onChanged: _onChange)), 32 | ], 33 | ); 34 | }, 35 | ), 36 | ); 37 | } 38 | 39 | void _onChange(String value) { 40 | setState(() { 41 | widget.gender.setGenderValue(value); 42 | }); 43 | } 44 | } 45 | 46 | abstract class ISelectGender { 47 | String getGenderValue(); 48 | 49 | void setGenderValue(String value); 50 | } 51 | -------------------------------------------------------------------------------- /lib/widget/home/category_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CategoryWidget extends StatefulWidget { 5 | final String text; 6 | final String imageUrl; 7 | 8 | CategoryWidget(this.text, {this.imageUrl = ""}); 9 | 10 | @override 11 | CategoryWidgetState createState() { 12 | return new CategoryWidgetState(); 13 | } 14 | } 15 | 16 | class CategoryWidgetState extends State 17 | with SingleTickerProviderStateMixin { 18 | @override 19 | Widget build(BuildContext context) { 20 | return Container( 21 | width: 76.0, 22 | alignment: Alignment.center, 23 | child: new Column( 24 | mainAxisSize: MainAxisSize.min, 25 | children: [ 26 | Container( 27 | height: 68.0, 28 | width: 68.0, 29 | child: Center( 30 | child: ClipOval( 31 | child: CachedNetworkImage( 32 | imageUrl: widget.imageUrl, 33 | height: 56.0, 34 | width: 56.0, 35 | fit: BoxFit.cover, 36 | placeholder: (_, __) => Container( 37 | height: 56.0, 38 | width: 56.0, 39 | color: Colors.blueGrey[100], 40 | ), 41 | ), 42 | ), 43 | ), 44 | ), 45 | Text( 46 | widget.text, 47 | style: TextStyle(fontSize: 12.0), 48 | ), 49 | ], 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/data/local/SharedPrefsHelper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:shared_preferences/shared_preferences.dart'; 4 | 5 | SharedPrefsHelper prefsHelper = new SharedPrefsHelper(); 6 | 7 | class SharedPrefsHelper { 8 | static final SharedPrefsHelper _instance = SharedPrefsHelper._internal(); 9 | 10 | SharedPreferences _prefs; 11 | 12 | final String PREFS_TOKEN = "TOKEN"; 13 | final String PREFS_IS_LOGIN = "ISLOGIN"; 14 | 15 | final String PREFS_SELECTED_AREA_ID = "selectedareaid"; 16 | final String PREFS_SELECTED_AREA_NAME = "selectedareaname"; 17 | 18 | set selectedAreaId(value) => _prefs.setString(PREFS_SELECTED_AREA_ID, value); 19 | 20 | get selectedAreaId => _prefs.getString(PREFS_SELECTED_AREA_ID); 21 | 22 | set selectedAreaName(value) => _prefs.setString(PREFS_SELECTED_AREA_NAME, value); 23 | 24 | get selectedAreaName => _prefs.getString(PREFS_SELECTED_AREA_NAME); 25 | 26 | set token(value) => _prefs.setString(PREFS_TOKEN, value); 27 | 28 | get token => _prefs.getString(PREFS_TOKEN); 29 | 30 | set isLogin(value) => _prefs.setBool(PREFS_IS_LOGIN, value); 31 | 32 | get isLogin => _prefs.getBool(PREFS_IS_LOGIN) ?? false; 33 | 34 | factory SharedPrefsHelper() { 35 | return _instance; 36 | } 37 | 38 | SharedPrefsHelper._internal(); 39 | 40 | Future initialize() async { 41 | _prefs = await SharedPreferences.getInstance(); 42 | return 0; 43 | } 44 | 45 | void checkIsInitialize() { 46 | assert(_prefs != null, "Call prefsHelper.initialize first before using Shared Prefs"); 47 | } 48 | 49 | void clear() { 50 | _prefs.clear(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/utils/dialog_utils.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class DialogUtils { 4 | static void showProgressBar(BuildContext context, String text) { 5 | showDialog( 6 | context: context, 7 | barrierDismissible: false, 8 | builder: (context) => new Dialog( 9 | child: new Container( 10 | height: 80.0, 11 | padding: const EdgeInsets.all(20.0), 12 | child: new Row( 13 | crossAxisAlignment: CrossAxisAlignment.center, 14 | children: [ 15 | new Padding( 16 | padding: const EdgeInsets.only(left: 8.0), 17 | child: new CircularProgressIndicator( 18 | valueColor: 19 | new AlwaysStoppedAnimation(Colors.blue), 20 | strokeWidth: 1.0, 21 | ), 22 | ), 23 | new Padding( 24 | padding: const EdgeInsets.only(left: 24.0), 25 | child: new Text( 26 | text, 27 | style: new TextStyle( 28 | color: Colors.grey[700], fontSize: 14.0), 29 | ), 30 | ), 31 | ], 32 | ), 33 | ), 34 | ), 35 | ); 36 | } 37 | 38 | static Widget showCircularProgressBar() { 39 | return new Center( 40 | child: new CircularProgressIndicator( 41 | valueColor: new AlwaysStoppedAnimation(Colors.blue), 42 | strokeWidth: 1.0, 43 | )); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DOT. 2 | 3 | This is the official repo of the mobile application of DotMyStyle. 4 | This app is build using Flutter Sdk. 5 | 6 | ## Concept 7 | 8 | The idea of DotMyStyle was to connect unique salons, spa, barbers, beautician of Delhi NCR, Gurgaon and Noida. There would be two apps one for consumer and for clients. 9 | 10 | The backend was built on JAVA Spring framework, simultaneously another backend was being build on NodeJS Express. Both the backend had same database MongoDB hosted on mLab. 11 | 12 | JAVA code was hosted on AWS while NodeJS code was hosted on Heroku. 13 | 14 | NodeJS backend code can be found here: [DotMyStyle_backend](https://github.com/apgapg/DotMyStyle_backend) 15 | 16 | Revenue Model can be find here: [revenue model.pptx](https://github.com/apgapg/DotMyStyle_App/blob/master/src/revenue%20model.pptx) 17 | ## Sample Screenshots: 18 | 19 | Different screens on app include: Splash screen, Login screen, Location screen, Home screen, Feed screen, Salon Detail screen etc 20 | 21 | DotMyStyle_App DotMyStyle_App DotMyStyle_App DotMyStyle_App DotMyStyle_App 22 | 23 | 24 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | salon 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /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/pages/product_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/product_list_bloc.dart'; 3 | import 'package:salon/data/model/product_list_model.dart'; 4 | import 'package:salon/utils/dialog_utils.dart'; 5 | import 'package:salon/widget/product_card.dart'; 6 | 7 | class ProductListTab extends StatefulWidget { 8 | ProductListTab(); 9 | 10 | _ProductListTabState createState() => _ProductListTabState(); 11 | } 12 | 13 | class _ProductListTabState extends State { 14 | ProductListBloc _bloc; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | _bloc = new ProductListBloc(); 20 | } 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return Scaffold( 25 | body: StreamBuilder( 26 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 27 | if (snapshot.hasData) { 28 | return CustomScrollView( 29 | slivers: [ 30 | SliverPadding( 31 | padding: EdgeInsets.all(4.0), 32 | sliver: SliverGrid( 33 | delegate: SliverChildBuilderDelegate((context, index) { 34 | return ProductCard(snapshot.data.elementAt(index)); 35 | }, childCount: snapshot.data.length), 36 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.9), 37 | ), 38 | ), 39 | ], 40 | ); 41 | } else if (snapshot.hasError) { 42 | return Center( 43 | child: Text("Some error..."), 44 | ); 45 | } else { 46 | return DialogUtils.showCircularProgressBar(); 47 | } 48 | }, 49 | stream: _bloc.productStream, 50 | ), 51 | ); 52 | } 53 | 54 | @override 55 | void dispose() { 56 | _bloc.dispose(); 57 | super.dispose(); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /lib/data/model/salon_detail_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'salon_detail_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | SalonDetailModel _$SalonDetailModelFromJson(Map json) { 10 | $checkKeys(json, 11 | requiredKeys: const ['id', 'name', 'address'], 12 | disallowNullValues: const ['id', 'name', 'address']); 13 | return SalonDetailModel( 14 | json['id'] as String, 15 | json['name'] as String, 16 | json['address'] as String, 17 | json['image'] as String, 18 | (json['categories'] as List) 19 | ?.map((e) => e == null 20 | ? null 21 | : CategoryModel.fromJson(e as Map)) 22 | ?.toList()); 23 | } 24 | 25 | Map _$SalonDetailModelToJson(SalonDetailModel instance) { 26 | var val = {}; 27 | 28 | void writeNotNull(String key, dynamic value) { 29 | if (value != null) { 30 | val[key] = value; 31 | } 32 | } 33 | 34 | writeNotNull('id', instance.id); 35 | writeNotNull('name', instance.name); 36 | writeNotNull('address', instance.address); 37 | val['image'] = instance.image; 38 | val['categories'] = instance.categories; 39 | return val; 40 | } 41 | 42 | CategoryModel _$CategoryModelFromJson(Map json) { 43 | $checkKeys(json, 44 | requiredKeys: const ['id', 'category'], 45 | disallowNullValues: const ['id', 'category']); 46 | return CategoryModel(json['id'] as String, json['category'] as String); 47 | } 48 | 49 | Map _$CategoryModelToJson(CategoryModel instance) { 50 | var val = {}; 51 | 52 | void writeNotNull(String key, dynamic value) { 53 | if (value != null) { 54 | val[key] = value; 55 | } 56 | } 57 | 58 | writeNotNull('id', instance.id); 59 | writeNotNull('category', instance.category); 60 | return val; 61 | } 62 | -------------------------------------------------------------------------------- /lib/widget/product_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/data/model/product_list_model.dart'; 3 | 4 | class ProductCard extends StatefulWidget { 5 | final ProductItem item; 6 | 7 | ProductCard(this.item); 8 | 9 | @override 10 | ProductCardState createState() { 11 | return new ProductCardState(); 12 | } 13 | } 14 | 15 | class ProductCardState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return Container( 19 | child: GestureDetector( 20 | onTap: () { 21 | // onSalonItemTap(widget.item); 22 | }, 23 | child: Card( 24 | elevation: 2.0, 25 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), 26 | child: Container( 27 | child: Column( 28 | crossAxisAlignment: CrossAxisAlignment.start, 29 | children: [ 30 | Expanded( 31 | child: Image.network( 32 | widget.item.image, 33 | fit: BoxFit.fitHeight, 34 | )), 35 | // Expanded( 36 | // child: Container(color: Colors.grey[300]), 37 | // ), 38 | Padding( 39 | padding: const EdgeInsets.all(8.0), 40 | child: Column( 41 | crossAxisAlignment: CrossAxisAlignment.start, 42 | mainAxisSize: MainAxisSize.min, 43 | children: [ 44 | Text( 45 | widget.item.name, 46 | style: TextStyle(fontWeight: FontWeight.w600), 47 | ), 48 | SizedBox( 49 | height: 2.0, 50 | ), 51 | 52 | ], 53 | ), 54 | ), 55 | 56 | // Image.network(widget.item.Salon_u), 57 | ], 58 | ), 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /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 from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.salon" 37 | minSdkVersion 16 38 | targetSdkVersion 27 39 | versionCode 1 40 | versionName "0.0.1" 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | implementation 'swarajsaaj:otpreader:1.1' 62 | implementation 'com.android.support:support-v4:+' 63 | 64 | } 65 | -------------------------------------------------------------------------------- /lib/api_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:http/http.dart' as http; 4 | import 'package:salon/data/local/SharedPrefsHelper.dart'; 5 | 6 | ApiHelper apiHelper = new ApiHelper(); 7 | 8 | class ApiHelper { 9 | // static const baseUrl = "https://dotmystyle.com/api/v1/"; 10 | static const baseUrl = "https://dotmystyle.herokuapp.com/api/v1/"; 11 | 12 | static final ApiHelper _instance = new ApiHelper._internal(); 13 | 14 | factory ApiHelper() { 15 | return _instance; 16 | } 17 | 18 | ApiHelper._internal(); 19 | 20 | Future getWithoutAuth(String url) async { 21 | return await http.get(baseUrl + url); 22 | } 23 | 24 | Future postWithoutAuth(String url, String body) async { 25 | Map map = new Map(); 26 | map.putIfAbsent("Content-Type", () => "application/json"); 27 | return await http.post(baseUrl + url, body: body, headers: map); 28 | } 29 | 30 | Future getWithAuth({String endpoint}) async { 31 | return await http.get(baseUrl + endpoint, headers: getAuthTokenHeader()); 32 | } 33 | 34 | Future getWithAuth1({String endpoint}) async { 35 | var _raw = 36 | await http.get(baseUrl + endpoint, headers: getAuthTokenHeader()); 37 | return parseResponse(_raw); 38 | } 39 | 40 | Map getAuthTokenHeader() { 41 | Map _map = new Map(); 42 | _map.putIfAbsent("Authorization", () => "Bearer " + prefsHelper.token); 43 | return _map; 44 | } 45 | 46 | NetworkResponse parseResponse(http.Response response) { 47 | print("NetworkReq URL: " + response.request.url.toString()); 48 | print("NetworkReq STATUS: " + response.statusCode.toString()); 49 | print("NetworkReq BODY: " + response.body.toString()); 50 | return NetworkResponse(response); 51 | } 52 | 53 | void getLocationData() {} 54 | } 55 | 56 | class NetworkResponse { 57 | http.Response response; 58 | int statusCode; 59 | bool isSuccess; 60 | String message; 61 | 62 | NetworkResponse(this.response) { 63 | this.statusCode = response.statusCode; 64 | this.message = response.body; 65 | if (response.statusCode < 200 || response.statusCode >= 300) { 66 | this.isSuccess = false; 67 | } else 68 | this.isSuccess = true; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/widget/CityWidget.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | class CityWidget extends StatefulWidget { 5 | final String text; 6 | 7 | final String imageUrl; 8 | 9 | CityWidget(this.text, {this.imageUrl = ""}); 10 | 11 | @override 12 | CityWidgetState createState() { 13 | return new CityWidgetState(); 14 | } 15 | } 16 | 17 | class CityWidgetState extends State 18 | with SingleTickerProviderStateMixin { 19 | Animation animation; 20 | AnimationController controller; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | /* controller = AnimationController(duration: const Duration(milliseconds: 90), vsync: this); 26 | animation = Tween(begin: 56.0, end: 60.0).animate(controller) 27 | ..addListener(() { 28 | setState(() {}); 29 | }); 30 | controller.addStatusListener((status) async { 31 | if (status == AnimationStatus.completed) { 32 | controller.reverse(); 33 | } else if (status == AnimationStatus.dismissed) { 34 | await Future.delayed(Duration(milliseconds: 1400)); 35 | controller.forward(); 36 | } 37 | }); 38 | if (widget.animate) controller.forward();*/ 39 | } 40 | 41 | @override 42 | void dispose() { 43 | controller.dispose(); 44 | 45 | super.dispose(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Container( 51 | width: 76.0, 52 | alignment: Alignment.center, 53 | child: new Column( 54 | mainAxisSize: MainAxisSize.min, 55 | children: [ 56 | Container( 57 | height: 68.0, 58 | width: 68.0, 59 | child: Center( 60 | child: ClipOval( 61 | child: CachedNetworkImage( 62 | imageUrl: widget.imageUrl, 63 | height: 56.0, 64 | width: 56.0, 65 | fit: BoxFit.cover, 66 | placeholder: (_, __) => Container( 67 | height: 56.0, 68 | width: 56.0, 69 | color: Colors.blueGrey[100], 70 | ), 71 | ), 72 | ), 73 | ), 74 | ), 75 | Text( 76 | widget.text, 77 | style: TextStyle(fontSize: 12.0), 78 | ), 79 | ], 80 | ), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /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/widget/area_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AreaWidget extends StatefulWidget { 4 | final String text; 5 | 6 | final bool animate; 7 | 8 | AreaWidget(this.text, {this.animate = false}); 9 | 10 | @override 11 | AreaWidgetState createState() { 12 | return new AreaWidgetState(); 13 | } 14 | } 15 | 16 | class AreaWidgetState extends State with SingleTickerProviderStateMixin { 17 | Animation animation; 18 | AnimationController controller; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | controller = AnimationController(duration: const Duration(milliseconds: 90), vsync: this); 24 | animation = Tween(begin: 56.0, end: 60.0).animate(controller) 25 | ..addListener(() { 26 | setState(() {}); 27 | }); 28 | controller.addStatusListener((status) async { 29 | if (status == AnimationStatus.completed) { 30 | controller.reverse(); 31 | } else if (status == AnimationStatus.dismissed) { 32 | await Future.delayed(Duration(milliseconds: 1400)); 33 | controller.forward(); 34 | } 35 | }); 36 | if (widget.animate) controller.forward(); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | controller.dispose(); 42 | 43 | super.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Container( 49 | width: 84.0, 50 | alignment: Alignment.center, 51 | child: new Column( 52 | mainAxisSize: MainAxisSize.min, 53 | children: [ 54 | Container( 55 | height: 68.0, 56 | width: 68.0, 57 | child: Container( 58 | height: animation.value, 59 | width: animation.value, 60 | decoration: BoxDecoration( 61 | shape: BoxShape.circle, 62 | color: widget.animate ? Colors.redAccent : Colors.blueGrey[100], 63 | ), 64 | child: widget.animate 65 | ? Center( 66 | child: Icon( 67 | Icons.my_location, 68 | size: 28.0, 69 | color: Colors.white, 70 | ), 71 | ) 72 | : Center(), 73 | ), 74 | alignment: Alignment.center, 75 | ), 76 | Text( 77 | widget.text, 78 | style: TextStyle(fontSize: 12.0), 79 | ), 80 | ], 81 | ), 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 9 | 10 | 11 | 12 | 17 | 24 | 31 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/data/model/stylist_detail_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'stylist_detail_model.g.dart'; 4 | 5 | @JsonSerializable() 6 | class StylistDetailModel { 7 | final String id; 8 | final String name; 9 | final String about; 10 | @JsonKey(name: "gender_type") 11 | final String genderType; 12 | final String tagline; 13 | @JsonKey(name: "profile_picture") 14 | final String profilePicture; 15 | @JsonKey(name: "offered_services") 16 | final List offeredServices; 17 | final Experience experience; 18 | final String phone; 19 | 20 | StylistDetailModel({this.id, this.name, this.about, this.genderType, this.tagline, this.profilePicture, this.offeredServices, this.experience, this.phone}); 21 | 22 | factory StylistDetailModel.fromJson(Map json) => _$StylistDetailModelFromJson(json); 23 | } 24 | 25 | @JsonSerializable() 26 | class OfferedServices { 27 | String category; 28 | List services; 29 | 30 | OfferedServices({this.category, this.services}); 31 | 32 | factory OfferedServices.fromJson(Map json) => _$OfferedServicesFromJson(json); 33 | } 34 | 35 | @JsonSerializable() 36 | class Services { 37 | final String sId; 38 | final String title; 39 | @JsonKey(name: "product_brand") 40 | final String productBrand; 41 | @JsonKey(name: "average_time") 42 | final String averageTime; 43 | final String category; 44 | final Pricing pricing; 45 | 46 | Services({this.sId, this.title, this.productBrand, this.averageTime, this.category, this.pricing}); 47 | 48 | factory Services.fromJson(Map json) => _$ServicesFromJson(json); 49 | } 50 | 51 | @JsonSerializable() 52 | class Pricing { 53 | @JsonKey(name: "base_price") 54 | final int basePrice; 55 | @JsonKey(name: "sale_price") 56 | final int salePrice; 57 | final int discount; 58 | @JsonKey(name: "is_dot_exclusive") 59 | final bool isDotExclusive; 60 | 61 | Pricing({this.basePrice, this.salePrice, this.discount, this.isDotExclusive}); 62 | 63 | factory Pricing.fromJson(Map json) => _$PricingFromJson(json); 64 | } 65 | 66 | @JsonSerializable() 67 | class Experience { 68 | @JsonKey(name: "year_experience", required: true, nullable: false) 69 | final int yearExperience; 70 | @JsonKey(name: "work_profile") 71 | final List workProfile; 72 | 73 | Experience({this.yearExperience, this.workProfile}); 74 | 75 | factory Experience.fromJson(Map json) => _$ExperienceFromJson(json); 76 | } 77 | 78 | @JsonSerializable() 79 | class WorkProfile { 80 | final String title; 81 | final String image; 82 | 83 | WorkProfile({this.title, this.image}); 84 | 85 | factory WorkProfile.fromJson(Map json) => _$WorkProfileFromJson(json); 86 | } 87 | -------------------------------------------------------------------------------- /lib/pages/home_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/data/local/SharedPrefsHelper.dart'; 3 | import 'package:salon/pages/product_page.dart'; 4 | import 'package:salon/pages/profile_page.dart'; 5 | import 'package:salon/pages/tab/inspiration_tab.dart'; 6 | import 'package:salon/pages/tab/salon_tab.dart'; 7 | import 'package:salon/widget/bottom_bar_item.dart'; 8 | 9 | class HomePage extends StatefulWidget { 10 | _HomePageState createState() => _HomePageState(); 11 | } 12 | 13 | class _HomePageState extends State { 14 | TabController _tabController; 15 | 16 | final List _bottomBarItemList = [ 17 | /*"Feeds",*/ "Explore", 18 | "Products", 19 | "Inspiration" 20 | ]; 21 | final List _bottomBarItemIconList = [ 22 | // Icons.rss_feed, 23 | Icons.search, 24 | Icons.rss_feed, 25 | Icons.rss_feed, 26 | ]; 27 | final List _pageList = [ 28 | //FeedTab(), 29 | SalonTab(), 30 | ProductListTab(), 31 | InspirationTab(), 32 | ]; 33 | 34 | int _selectedPageIndex = 0; 35 | 36 | @override 37 | void initState() { 38 | super.initState(); 39 | print("Token: " + prefsHelper.token); 40 | } 41 | 42 | @override 43 | void dispose() { 44 | _tabController.dispose(); 45 | super.dispose(); 46 | } 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | return Scaffold( 51 | /* appBar: AppBar( 52 | title: Text( 53 | "DotMyStyle", 54 | style: TextStyle(color: Colors.blueGrey[700]), 55 | ), 56 | elevation: 2.0, 57 | backgroundColor: Colors.white, 58 | actions: [ 59 | IconButton( 60 | icon: Icon(Icons.account_circle), 61 | onPressed: onProfilePress, 62 | ) 63 | ], 64 | ),*/ 65 | body: _pageList.elementAt(_selectedPageIndex), 66 | bottomNavigationBar: BottomAppBar( 67 | child: Container( 68 | height: 44.0, 69 | child: Flex( 70 | direction: Axis.horizontal, 71 | mainAxisSize: MainAxisSize.max, 72 | crossAxisAlignment: CrossAxisAlignment.center, 73 | children: _bottomBarItemList 74 | .map( 75 | (item) => BottomBarItem( 76 | item, 77 | _selectedPageIndex == _bottomBarItemList.indexOf(item), 78 | _bottomBarItemIconList.elementAt( 79 | _bottomBarItemList.indexOf(item), 80 | ), 81 | onBottomBarItemTap), 82 | ) 83 | .toList(), 84 | ), 85 | ), 86 | ), 87 | ); 88 | } 89 | 90 | void onBottomBarItemTap(String item) { 91 | setState(() { 92 | _selectedPageIndex = _bottomBarItemList.indexOf(item); 93 | }); 94 | } 95 | 96 | void onProfilePress() { 97 | Navigator.push( 98 | context, 99 | new MaterialPageRoute( 100 | builder: (context) => new ProfilePage(), 101 | )); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /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/widget/home/category_container.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/provider.dart'; 3 | import 'package:salon/bloc/salon_bloc.dart'; 4 | import 'package:salon/data/model/category_model.dart'; 5 | 6 | import 'category_widget.dart'; 7 | 8 | class CategoryContainer extends StatelessWidget { 9 | @override 10 | Widget build(BuildContext context) { 11 | var bloc = Provider.of(context); 12 | return Container( 13 | height: 136.0, 14 | color: Colors.white, 15 | child: Column( 16 | children: [ 17 | Padding( 18 | padding: const EdgeInsets.only(top: 14.0, left: 12.0, right: 12.0), 19 | child: Row( 20 | mainAxisSize: MainAxisSize.min, 21 | crossAxisAlignment: CrossAxisAlignment.start, 22 | mainAxisAlignment: MainAxisAlignment.center, 23 | children: [ 24 | Expanded( 25 | child: Text( 26 | "Search by Category", 27 | style: TextStyle( 28 | fontWeight: FontWeight.w900, 29 | letterSpacing: 1.2, 30 | color: Colors.blueGrey[900], 31 | fontSize: 14.0), 32 | ), 33 | ), 34 | // ViewAll(), 35 | ], 36 | ), 37 | ), 38 | StreamBuilder( 39 | stream: bloc.categoryController, 40 | builder: (context, AsyncSnapshot> snapshot) { 41 | if (snapshot.hasData) { 42 | return Expanded( 43 | child: ListView( 44 | padding: EdgeInsets.symmetric(horizontal: 8.0), 45 | children: snapshot.data 46 | .map((item) => CategoryWidget(item.category, 47 | imageUrl: item.image)) 48 | .toList(), 49 | /*children: [ 50 | CategoryWidget( 51 | "Delhi", 52 | imageUrl: "https://d53pfl4a028j5.cloudfront.net/uploads/city_image/New%20Delhi%20cropped.jpg", 53 | ), 54 | CategoryWidget( 55 | "Gurgaon", 56 | imageUrl: "https://d53pfl4a028j5.cloudfront.net/uploads/city_image/rapid-metro-banner.jpg", 57 | ), 58 | CategoryWidget( 59 | "Noida", 60 | imageUrl: "https://d53pfl4a028j5.cloudfront.net/uploads/city_image/noida.jpg", 61 | ), 62 | CategoryWidget( 63 | "Ghaziabad", 64 | imageUrl: "https://d53pfl4a028j5.cloudfront.net/uploads/city_image/gaziabad%203.jpg", 65 | ), 66 | ],*/ 67 | scrollDirection: Axis.horizontal, 68 | ), 69 | ); 70 | } else { 71 | return Container(); 72 | } 73 | }), 74 | ], 75 | ), 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: salon 2 | description: A new Flutter project. 3 | 4 | # The following defines the version and build number for your application. 5 | # A version number is three numbers separated by dots, like 1.2.43 6 | # followed by an optional build number separated by a +. 7 | # Both the version and the builder number may be overridden in flutter 8 | # build by specifying --build-name and --build-number, respectively. 9 | # Read more about versioning at semver.org. 10 | version: 1.0.0+1 11 | 12 | dependencies: 13 | flutter: 14 | sdk: flutter 15 | 16 | # The following adds the Cupertino Icons font to your application. 17 | # Use with the CupertinoIcons class for iOS style icons. 18 | cupertino_icons: ^0.1.3 19 | rxdart: ^0.24.1 20 | http: ^0.12.1 21 | json_annotation: ^3.0.1 22 | shared_preferences: ^0.5.7+3 23 | cached_network_image: ^2.2.0+1 24 | 25 | 26 | dev_dependencies: 27 | # flutter_test: 28 | # sdk: flutter 29 | build_runner: ^1.10.0 30 | json_serializable: ^3.3.0 31 | 32 | 33 | # For information on the generic Dart part of this file, see the 34 | # following page: https://www.dartlang.org/tools/pub/pubspec 35 | 36 | # The following section is specific to Flutter. 37 | flutter: 38 | 39 | # The following line ensures that the Material Icons font is 40 | # included with your application, so that you can use the icons in 41 | # the material Icons class. 42 | uses-material-design: true 43 | assets: 44 | - assets/images/logo.jpg 45 | - assets/images/logo.png 46 | - assets/images/location.png 47 | fonts: 48 | - family: Nunito 49 | fonts: 50 | - asset: assets/fonts/nunitoSemiBold.ttf 51 | - asset: assets/fonts/nunitoBold.ttf 52 | weight: 700 53 | - family: whitneybookbas 54 | fonts: 55 | - asset: assets/fonts/whitneybookbas.otf 56 | - family: sourcesanspro 57 | fonts: 58 | - asset: assets/fonts/sourcesanspro-regular.otf 59 | - family: MyIcons 60 | fonts: 61 | - asset: assets/fonts/MyIcons.ttf 62 | # To add assets to your application, add an assets section, like this: 63 | # assets: 64 | # - images/a_dot_burr.jpeg 65 | # - images/a_dot_ham.jpeg 66 | 67 | # An image asset can refer to one or more resolution-specific "variants", see 68 | # https://flutter.io/assets-and-images/#resolution-aware. 69 | 70 | # For details regarding adding assets from package dependencies, see 71 | # https://flutter.io/assets-and-images/#from-packages 72 | 73 | # To add custom fonts to your application, add a fonts section here, 74 | # in this "flutter" section. Each entry in this list should have a 75 | # "family" key with the font family name, and a "fonts" key with a 76 | # list giving the asset and other descriptors for the font. For 77 | # example: 78 | # fonts: 79 | # - family: Schyler 80 | # fonts: 81 | # - asset: fonts/Schyler-Regular.ttf 82 | # - asset: fonts/Schyler-Italic.ttf 83 | # style: italic 84 | # - family: Trajan Pro 85 | # fonts: 86 | # - asset: fonts/TrajanPro.ttf 87 | # - asset: fonts/TrajanPro_Bold.ttf 88 | # weight: 700 89 | # 90 | # For details regarding fonts from package dependencies, 91 | # see https://flutter.io/custom-fonts/#from-packages 92 | 93 | 94 | #flutter packages pub run build_runner build --delete-conflicting-outputs -------------------------------------------------------------------------------- /lib/widget/book_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:salon/data/model/salon_model.dart'; 4 | import 'package:salon/pages/salon_detail_page.dart'; 5 | import 'package:salon/pages/tab/salon_tab.dart'; 6 | 7 | class BookCard extends StatefulWidget { 8 | final SalonItem item; 9 | 10 | BookCard(this.item); 11 | 12 | _BookCardState createState() => _BookCardState(); 13 | } 14 | 15 | class _BookCardState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return AspectRatio( 19 | aspectRatio: 0.9, 20 | child: Container( 21 | color: Colors.white, 22 | child: GestureDetector( 23 | onTap: () { 24 | onSalonItemTap(widget.item); 25 | }, 26 | child: Card( 27 | margin: EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0), 28 | elevation: 1.0, 29 | child: Container( 30 | child: Stack( 31 | children: [ 32 | Column( 33 | crossAxisAlignment: CrossAxisAlignment.start, 34 | children: [ 35 | Expanded( 36 | child: CachedNetworkImage( 37 | imageUrl: widget.item.image, 38 | fit: BoxFit.fitHeight, 39 | ), 40 | ), 41 | // Expanded( 42 | // child: Container(color: Colors.grey[300]), 43 | // ), 44 | Padding( 45 | padding: const EdgeInsets.all(8.0), 46 | child: Column( 47 | crossAxisAlignment: CrossAxisAlignment.start, 48 | mainAxisSize: MainAxisSize.min, 49 | children: [ 50 | Text( 51 | widget.item.name, 52 | style: TextStyle( 53 | fontWeight: FontWeight.w600, 54 | ), 55 | maxLines: 1, 56 | ), 57 | SizedBox( 58 | height: 2.0, 59 | ), 60 | ], 61 | ), 62 | ), 63 | 64 | // Image.network(widget.item.Salon_u), 65 | ], 66 | ), 67 | Positioned( 68 | right: 0.0, 69 | top: 0.0, 70 | child: GenderWidget(widget.item.genderType), 71 | ), 72 | ], 73 | ), 74 | ), 75 | ), 76 | ), 77 | ), 78 | ); 79 | } 80 | 81 | String getTags(List tags) { 82 | StringBuffer stringBuffer = StringBuffer(); 83 | stringBuffer.writeAll(tags.map((tag) => "#" + tag), " "); 84 | return stringBuffer.toString(); 85 | } 86 | 87 | void onSalonItemTap(SalonItem item) { 88 | Navigator.push( 89 | context, 90 | new MaterialPageRoute( 91 | builder: (context) => new SalonDetailPage( 92 | item: item, 93 | ), 94 | ), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def parse_KV_file(file, separator='=') 14 | file_abs_path = File.expand_path(file) 15 | if !File.exists? file_abs_path 16 | return []; 17 | end 18 | generated_key_values = {} 19 | skip_line_start_symbols = ["#", "/"] 20 | File.foreach(file_abs_path) do |line| 21 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 22 | plugin = line.split(pattern=separator) 23 | if plugin.length == 2 24 | podname = plugin[0].strip() 25 | path = plugin[1].strip() 26 | podpath = File.expand_path("#{path}", file_abs_path) 27 | generated_key_values[podname] = podpath 28 | else 29 | puts "Invalid plugin specification: #{line}" 30 | end 31 | end 32 | generated_key_values 33 | end 34 | 35 | target 'Runner' do 36 | # Flutter Pod 37 | 38 | copied_flutter_dir = File.join(__dir__, 'Flutter') 39 | copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework') 40 | copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec') 41 | unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path) 42 | # Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet. 43 | # That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration. 44 | # CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist. 45 | 46 | generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig') 47 | unless File.exist?(generated_xcode_build_settings_path) 48 | raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first" 49 | end 50 | generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path) 51 | cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR']; 52 | 53 | unless File.exist?(copied_framework_path) 54 | FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir) 55 | end 56 | unless File.exist?(copied_podspec_path) 57 | FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir) 58 | end 59 | end 60 | 61 | # Keep pod path relative so it can be checked into Podfile.lock. 62 | pod 'Flutter', :path => 'Flutter' 63 | 64 | # Plugin Pods 65 | 66 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 67 | # referring to absolute paths on developers' machines. 68 | system('rm -rf .symlinks') 69 | system('mkdir -p .symlinks/plugins') 70 | plugin_pods = parse_KV_file('../.flutter-plugins') 71 | plugin_pods.each do |name, path| 72 | symlink = File.join('.symlinks', 'plugins', name) 73 | File.symlink(path, symlink) 74 | pod name, :path => File.join(symlink, 'ios') 75 | end 76 | end 77 | 78 | post_install do |installer| 79 | installer.pods_project.targets.each do |target| 80 | target.build_configurations.each do |config| 81 | config.build_settings['ENABLE_BITCODE'] = 'NO' 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /lib/widget/stylist_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:salon/data/model/stylist_model.dart'; 4 | import 'package:salon/pages/stylist_detail_page.dart'; 5 | import 'package:salon/pages/tab/salon_tab.dart'; 6 | 7 | class StylistCard extends StatefulWidget { 8 | final StylistItem item; 9 | 10 | StylistCard(this.item); 11 | 12 | _StylistCardState createState() => _StylistCardState(); 13 | } 14 | 15 | class _StylistCardState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return AspectRatio( 19 | aspectRatio: 0.8, 20 | child: Container( 21 | color: Colors.white, 22 | child: GestureDetector( 23 | onTap: () { 24 | onItemTap(widget.item); 25 | }, 26 | child: Card( 27 | margin: EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0), 28 | elevation: 0.0, 29 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), 30 | child: Container( 31 | child: Stack( 32 | children: [ 33 | Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | Expanded( 37 | child: ClipRRect( 38 | borderRadius: new BorderRadius.only( 39 | topLeft: Radius.circular(4.0), 40 | topRight: Radius.circular(4.0), 41 | ), 42 | child: CachedNetworkImage( 43 | imageUrl: widget.item.profilePicture, 44 | fit: BoxFit.fitHeight, 45 | ), 46 | ), 47 | ), 48 | // Expanded( 49 | // child: Container(color: Colors.grey[300]), 50 | // ), 51 | Padding( 52 | padding: const EdgeInsets.symmetric(vertical: 8.0), 53 | child: Column( 54 | crossAxisAlignment: CrossAxisAlignment.start, 55 | mainAxisSize: MainAxisSize.min, 56 | children: [ 57 | Text( 58 | widget.item.name, 59 | style: TextStyle( 60 | fontWeight: FontWeight.w500, 61 | ), 62 | maxLines: 1, 63 | ), 64 | SizedBox( 65 | height: 2.0, 66 | ), 67 | ], 68 | ), 69 | ), 70 | 71 | // Image.network(widget.item.Salon_u), 72 | ], 73 | ), 74 | Positioned( 75 | right: 0.0, 76 | top: 0.0, 77 | child: GenderWidget(widget.item.genderType), 78 | ), 79 | ], 80 | ), 81 | ), 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | 88 | 89 | void onItemTap(StylistItem item) { 90 | Navigator.push( 91 | context, 92 | new MaterialPageRoute( 93 | builder: (context) => 94 | new StylistDetailPage( 95 | item: item, 96 | ), 97 | ), 98 | ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/pages/add_profile_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/add_profile_detail_bloc.dart'; 3 | import 'package:salon/bloc/provider.dart'; 4 | import 'package:salon/widget/select_age_field.dart'; 5 | import 'package:salon/widget/select_gender_field.dart'; 6 | 7 | class AddProfileDetailPage extends StatefulWidget { 8 | @override 9 | _AddProfileDetailPageState createState() => _AddProfileDetailPageState(); 10 | } 11 | 12 | class _AddProfileDetailPageState extends State { 13 | final AddProfileDetailBloc bloc = new AddProfileDetailBloc(); 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Provider( 18 | bloc: bloc, 19 | child: Scaffold( 20 | appBar: AppBar( 21 | title: Text( 22 | "Update Profile", 23 | style: TextStyle(color: Colors.blueGrey[700]), 24 | ), 25 | elevation: 2.0, 26 | backgroundColor: Colors.white, 27 | actions: [ 28 | IconButton( 29 | icon: Icon(Icons.done), 30 | onPressed: bloc.onSubmit, 31 | ) 32 | ], 33 | ), 34 | body: Container( 35 | child: SingleChildScrollView( 36 | padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 37 | child: Form( 38 | key: bloc.formKey, 39 | child: Column( 40 | mainAxisSize: MainAxisSize.min, 41 | mainAxisAlignment: MainAxisAlignment.center, 42 | crossAxisAlignment: CrossAxisAlignment.center, 43 | children: [ 44 | const HeaderWidget("Your Name"), 45 | const SizedBox( 46 | height: 8.0, 47 | ), 48 | NameField(bloc.nameController), 49 | const SizedBox( 50 | height: 16.0, 51 | ), 52 | const HeaderWidget("Gender"), 53 | const SizedBox( 54 | height: 8.0, 55 | ), 56 | SelectGenderField(bloc), 57 | const SizedBox( 58 | height: 8.0, 59 | ), 60 | const HeaderWidget("Age"), 61 | const SizedBox( 62 | height: 8.0, 63 | ), 64 | SelectAgeField(bloc), 65 | ], 66 | ), 67 | ), 68 | ), 69 | ), 70 | ), 71 | ); 72 | } 73 | } 74 | 75 | class NameField extends StatelessWidget { 76 | final TextEditingController nameController; 77 | 78 | NameField(this.nameController); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | return Container( 83 | child: TextFormField( 84 | decoration: InputDecoration( 85 | hintText: "Enter name", 86 | border: OutlineInputBorder(), 87 | contentPadding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 16.0), 88 | ), 89 | controller: nameController, 90 | validator: (text) { 91 | if (text.trim().length == 0) return "Please provide a valid name."; 92 | }, 93 | ), 94 | ); 95 | } 96 | } 97 | 98 | class HeaderWidget extends StatelessWidget { 99 | final String text; 100 | 101 | const HeaderWidget(this.text); 102 | 103 | @override 104 | Widget build(BuildContext context) { 105 | return Container( 106 | alignment: Alignment.centerLeft, 107 | child: Text( 108 | text, 109 | style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w700, color: Colors.black54), 110 | ), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/widget/feed_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:salon/feed_model.dart'; 4 | 5 | class FeedCard extends StatefulWidget { 6 | final FeedItem feedItem; 7 | 8 | FeedCard(this.feedItem); 9 | 10 | _FeedCardState createState() => _FeedCardState(); 11 | } 12 | 13 | class _FeedCardState extends State { 14 | @override 15 | Widget build(BuildContext context) { 16 | return Container( 17 | width: 200.0, 18 | height: 200.0, 19 | margin: EdgeInsets.symmetric(horizontal: 2.0, vertical: 1.0), 20 | child: Card( 21 | elevation: 2.0, 22 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), 23 | child: Container( 24 | child: Column( 25 | mainAxisSize: MainAxisSize.min, 26 | crossAxisAlignment: CrossAxisAlignment.start, 27 | children: [ 28 | Expanded( 29 | child: ClipRRect( 30 | borderRadius: new BorderRadius.only( 31 | topLeft: Radius.circular(4.0), 32 | topRight: Radius.circular(4.0), 33 | ), 34 | child: CachedNetworkImage( 35 | imageUrl: (widget.feedItem.feed_url), 36 | fit: BoxFit.fitHeight, 37 | ), 38 | ), 39 | ), 40 | Padding( 41 | padding: const EdgeInsets.only(left: 12.0, right: 12.0, top: 6.0, bottom: 6.0), 42 | child: RichText( 43 | maxLines: 2, 44 | 45 | overflow: TextOverflow.ellipsis, 46 | text: new TextSpan( 47 | // Note: Styles for TextSpans must be explicitly defined. 48 | // Child text spans will inherit styles from parent 49 | style: new TextStyle( 50 | fontSize: 14.0, 51 | color: Colors.black, 52 | fontFamily: 'Nunito', 53 | ), 54 | children: [ 55 | new TextSpan( 56 | text: widget.feedItem.description + " ", 57 | style: TextStyle( 58 | fontFamily: 'Nunito', 59 | )), 60 | new TextSpan(text: getTags(widget.feedItem.tags), style: new TextStyle(fontWeight: FontWeight.normal, fontFamily: 'Nunito', color: Colors.blue)), 61 | ], 62 | ), 63 | ), 64 | ), 65 | /*Padding( 66 | padding: const EdgeInsets.only(top: 8.0, left: 8.0), 67 | child: Row( 68 | mainAxisSize: MainAxisSize.min, 69 | children: [ 70 | Padding( 71 | padding: const EdgeInsets.all(8.0), 72 | child: Text( 73 | "LIKE", 74 | style: TextStyle(color: Colors.blue), 75 | ), 76 | ), 77 | Padding( 78 | padding: const EdgeInsets.all(8.0), 79 | child: Text( 80 | "COMMENT", 81 | style: TextStyle(color: Colors.blue), 82 | ), 83 | ), 84 | ], 85 | ), 86 | )*/ 87 | ], 88 | ), 89 | ), 90 | ), 91 | ); 92 | } 93 | 94 | String getTags(List tags) { 95 | StringBuffer stringBuffer = StringBuffer(); 96 | stringBuffer.writeAll(tags.map((tag) => "#" + tag), " "); 97 | return stringBuffer.toString(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /lib/widget/salon_card.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:salon/data/model/salon_model.dart'; 4 | import 'package:salon/pages/salon_detail_page.dart'; 5 | import 'package:salon/pages/tab/salon_tab.dart'; 6 | 7 | class SalonCard extends StatefulWidget { 8 | final SalonItem item; 9 | 10 | SalonCard(this.item); 11 | 12 | _SalonCardState createState() => _SalonCardState(); 13 | } 14 | 15 | class _SalonCardState extends State { 16 | @override 17 | Widget build(BuildContext context) { 18 | return AspectRatio( 19 | aspectRatio: 0.8, 20 | child: Container( 21 | color: Colors.white, 22 | child: GestureDetector( 23 | onTap: () { 24 | onSalonItemTap(widget.item); 25 | }, 26 | child: Card( 27 | margin: EdgeInsets.symmetric(horizontal: 6.0, vertical: 4.0), 28 | elevation: 0.0, 29 | shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4.0))), 30 | child: Container( 31 | child: Stack( 32 | children: [ 33 | Column( 34 | crossAxisAlignment: CrossAxisAlignment.start, 35 | children: [ 36 | Expanded( 37 | child: ClipRRect( 38 | borderRadius: new BorderRadius.only( 39 | topLeft: Radius.circular(2.0), 40 | topRight: Radius.circular(2.0), 41 | bottomRight: Radius.circular(2.0), 42 | bottomLeft: Radius.circular(2.0), 43 | ), 44 | child: CachedNetworkImage( 45 | imageUrl: widget.item.image, 46 | fit: BoxFit.fitHeight, 47 | ), 48 | ), 49 | ), 50 | // Expanded( 51 | // child: Container(color: Colors.grey[300]), 52 | // ), 53 | Padding( 54 | padding: const EdgeInsets.symmetric(vertical: 8.0), 55 | child: Column( 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | mainAxisSize: MainAxisSize.min, 58 | children: [ 59 | Text( 60 | widget.item.name, 61 | style: TextStyle(fontWeight: FontWeight.w500, fontSize: 15.0, color: Colors.black), 62 | maxLines: 1, 63 | ), 64 | SizedBox( 65 | height: 2.0, 66 | ), 67 | ], 68 | ), 69 | ), 70 | 71 | // Image.network(widget.item.Salon_u), 72 | ], 73 | ), 74 | Positioned( 75 | right: 0.0, 76 | top: 0.0, 77 | child: GenderWidget(widget.item.genderType), 78 | ), 79 | ], 80 | ), 81 | ), 82 | ), 83 | ), 84 | ), 85 | ); 86 | } 87 | 88 | String getTags(List tags) { 89 | StringBuffer stringBuffer = StringBuffer(); 90 | stringBuffer.writeAll(tags.map((tag) => "#" + tag), " "); 91 | return stringBuffer.toString(); 92 | } 93 | 94 | void onSalonItemTap(SalonItem item) { 95 | Navigator.push( 96 | context, 97 | new MaterialPageRoute( 98 | builder: (context) => new SalonDetailPage( 99 | item: item, 100 | ), 101 | ), 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/data/model/stylist_detail_model.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'stylist_detail_model.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | StylistDetailModel _$StylistDetailModelFromJson(Map json) { 10 | return StylistDetailModel( 11 | id: json['id'] as String, 12 | name: json['name'] as String, 13 | about: json['about'] as String, 14 | genderType: json['gender_type'] as String, 15 | tagline: json['tagline'] as String, 16 | profilePicture: json['profile_picture'] as String, 17 | offeredServices: (json['offered_services'] as List)?.map((e) => e == null ? null : OfferedServices.fromJson(e as Map))?.toList(), 18 | experience: json['experience'] == null ? null : Experience.fromJson(json['experience'] as Map), 19 | phone: json['phone'] as String); 20 | } 21 | 22 | Map _$StylistDetailModelToJson(StylistDetailModel instance) => { 23 | 'id': instance.id, 24 | 'name': instance.name, 25 | 'about': instance.about, 26 | 'gender_type': instance.genderType, 27 | 'tagline': instance.tagline, 28 | 'profile_picture': instance.profilePicture, 29 | 'offered_services': instance.offeredServices, 30 | 'experience': instance.experience, 31 | 'phone': instance.phone 32 | }; 33 | 34 | OfferedServices _$OfferedServicesFromJson(Map json) { 35 | return OfferedServices(category: json['category'] as String, services: (json['services'] as List)?.map((e) => e == null ? null : Services.fromJson(e as Map))?.toList()); 36 | } 37 | 38 | Map _$OfferedServicesToJson(OfferedServices instance) => {'category': instance.category, 'services': instance.services}; 39 | 40 | Services _$ServicesFromJson(Map json) { 41 | return Services( 42 | sId: json['sId'] as String, 43 | title: json['title'] as String, 44 | productBrand: json['product_brand'] as String, 45 | averageTime: json['average_time'] as String, 46 | category: json['category'] as String, 47 | pricing: json['pricing'] == null ? null : Pricing.fromJson(json['pricing'] as Map)); 48 | } 49 | 50 | Map _$ServicesToJson(Services instance) => { 51 | 'sId': instance.sId, 52 | 'title': instance.title, 53 | 'product_brand': instance.productBrand, 54 | 'average_time': instance.averageTime, 55 | 'category': instance.category, 56 | 'pricing': instance.pricing 57 | }; 58 | 59 | Pricing _$PricingFromJson(Map json) { 60 | return Pricing(basePrice: json['base_price'] as int, salePrice: json['sale_price'] as int, discount: json['discount'] as int, isDotExclusive: json['is_dot_exclusive'] as bool); 61 | } 62 | 63 | Map _$PricingToJson(Pricing instance) => 64 | {'base_price': instance.basePrice, 'sale_price': instance.salePrice, 'discount': instance.discount, 'is_dot_exclusive': instance.isDotExclusive}; 65 | 66 | Experience _$ExperienceFromJson(Map json) { 67 | $checkKeys(json, requiredKeys: const ['year_experience']); 68 | return Experience( 69 | yearExperience: json['year_experience'] as int, workProfile: (json['work_profile'] as List)?.map((e) => e == null ? null : WorkProfile.fromJson(e as Map))?.toList()); 70 | } 71 | 72 | Map _$ExperienceToJson(Experience instance) => {'year_experience': instance.yearExperience, 'work_profile': instance.workProfile}; 73 | 74 | WorkProfile _$WorkProfileFromJson(Map json) { 75 | return WorkProfile(title: json['title'] as String, image: json['image'] as String); 76 | } 77 | 78 | Map _$WorkProfileToJson(WorkProfile instance) => {'title': instance.title, 'image': instance.image}; 79 | -------------------------------------------------------------------------------- /lib/pages/profile_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/profile_bloc.dart'; 3 | 4 | class ProfilePage extends StatefulWidget { 5 | ProfilePage(); 6 | 7 | _ProfilePageState createState() => _ProfilePageState(); 8 | } 9 | 10 | class _ProfilePageState extends State { 11 | ProfileBloc _bloc; 12 | 13 | @override 14 | void initState() { 15 | super.initState(); 16 | _bloc = new ProfileBloc(); 17 | } 18 | 19 | @override 20 | Widget build(BuildContext context) { 21 | return Scaffold( 22 | appBar: AppBar( 23 | title: Text( 24 | "Your Profile", 25 | style: TextStyle(color: Colors.blueGrey[700]), 26 | ), 27 | iconTheme: IconThemeData( 28 | color: Colors.blueGrey[700], 29 | ), 30 | elevation: 2.0, 31 | backgroundColor: Colors.white, 32 | ), 33 | body: Column( 34 | children: [ 35 | ListTile( 36 | title: Text("Amit Kumar"), 37 | subtitle: Text("+91-7078689565"), 38 | trailing: Icon(Icons.settings), 39 | ), 40 | WalletMenuItem(), 41 | MenuItem("Wishlist"), 42 | MenuItem("Booking History"), 43 | MenuItem("Support"), 44 | MenuItem("Invite Friends"), 45 | MenuItem("About Us"), 46 | MenuItem("Logout"), 47 | ], 48 | ), 49 | /*body: StreamBuilder( 50 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 51 | if (snapshot.hasData) { 52 | return CustomScrollView( 53 | slivers: [ 54 | SliverPadding( 55 | padding: EdgeInsets.all(4.0), 56 | sliver: SliverGrid( 57 | delegate: SliverChildBuilderDelegate((context, index) { 58 | return ProductCard(snapshot.data.elementAt(index)); 59 | }, childCount: snapshot.data.length), 60 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.9), 61 | ), 62 | ), 63 | ], 64 | ); 65 | } else if (snapshot.hasError) { 66 | return Center( 67 | child: Text("Some error..."), 68 | ); 69 | } else { 70 | return DialogUtils.showCircularProgressBar(); 71 | } 72 | }, 73 | stream: _bloc.profileController, 74 | ),*/ 75 | ); 76 | } 77 | 78 | @override 79 | void dispose() { 80 | _bloc.dispose(); 81 | super.dispose(); 82 | } 83 | } 84 | 85 | class MenuItem extends StatelessWidget { 86 | final String title; 87 | 88 | MenuItem(this.title); 89 | 90 | @override 91 | Widget build(BuildContext context) { 92 | return Container( 93 | decoration: BoxDecoration( 94 | color: Colors.white, 95 | border: Border( 96 | top: BorderSide(color: Colors.grey[100]), 97 | bottom: BorderSide(color: Colors.grey[100]))), 98 | child: ListTile( 99 | title: Text(title), 100 | trailing: Icon(Icons.chevron_right), 101 | ), 102 | ); 103 | } 104 | } 105 | 106 | class WalletMenuItem extends StatelessWidget { 107 | @override 108 | Widget build(BuildContext context) { 109 | return Container( 110 | decoration: BoxDecoration( 111 | color: Colors.white, 112 | border: Border( 113 | top: BorderSide(color: Colors.grey[100]), 114 | bottom: BorderSide(color: Colors.grey[100]))), 115 | child: ListTile( 116 | title: Text("DOT Wallet"), 117 | trailing: Container( 118 | height: 24.0, 119 | child: Text( 120 | "₹ 0", 121 | textAlign: TextAlign.center, 122 | style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700), 123 | ), 124 | padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.5), 125 | decoration: BoxDecoration( 126 | color: Color(0xff64DD17), 127 | borderRadius: BorderRadius.all(Radius.circular(12.0))), 128 | ), 129 | ), 130 | ); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/pages/tab/inspiration_tab.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/inpiration_bloc.dart'; 3 | import 'package:salon/bloc/provider.dart'; 4 | import 'package:salon/data/model/inspiraton_model.dart'; 5 | import 'package:salon/utils/dialog_utils.dart'; 6 | 7 | class InspirationTab extends StatefulWidget { 8 | @override 9 | InspirationTabState createState() { 10 | return new InspirationTabState(); 11 | } 12 | } 13 | 14 | class InspirationTabState extends State with AutomaticKeepAliveClientMixin { 15 | InspirationBloc _bloc = new InspirationBloc(); 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _bloc.initData(); 21 | } 22 | 23 | @override 24 | void dispose() { 25 | _bloc.dispose(); 26 | super.dispose(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Provider( 32 | bloc: _bloc, 33 | child: Container( 34 | child: StreamBuilder( 35 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 36 | if (snapshot.hasData) { 37 | List itemList = snapshot.data; 38 | List childrenWidget = new List(); 39 | List list = snapshot.data.map((item) => item.category).toSet().toList(); 40 | for (int i = 0; i < list.length; i++) { 41 | childrenWidget.add(SliverList( 42 | delegate: SliverChildListDelegate([HeaderWidget(list.elementAt(i))]), 43 | )); 44 | var _list = itemList.where((item) => item.category == list.elementAt(i)); 45 | childrenWidget.add(SliverPadding( 46 | padding: const EdgeInsets.all(4.0), 47 | sliver: SliverGrid( 48 | delegate: SliverChildBuilderDelegate( 49 | (context, i) => 50 | new GridTile( 51 | child: new InspirationCard(_list.elementAt(i)), 52 | ), 53 | childCount: _list.length), 54 | gridDelegate: new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.9)), 55 | )); 56 | } 57 | return CustomScrollView( 58 | slivers: childrenWidget, 59 | scrollDirection: Axis.vertical, 60 | ); 61 | } else if (snapshot.hasError) { 62 | return Center( 63 | child: Text("Some error..."), 64 | ); 65 | } else { 66 | return DialogUtils.showCircularProgressBar(); 67 | } 68 | }, 69 | stream: _bloc.list, 70 | ), 71 | ), 72 | ); 73 | } 74 | 75 | // TODO: implement wantKeepAlive 76 | @override 77 | bool get wantKeepAlive => true; 78 | } 79 | 80 | class HeaderWidget extends StatelessWidget { 81 | final String title; 82 | 83 | HeaderWidget(this.title); 84 | 85 | @override 86 | Widget build(BuildContext context) { 87 | // TODO: implement build 88 | return Padding( 89 | padding: const EdgeInsets.all(8.0), 90 | child: Text( 91 | title, 92 | style: TextStyle(color: Colors.grey[900], fontSize: 16.0, fontWeight: FontWeight.w600), 93 | ), 94 | ); 95 | } 96 | } 97 | 98 | class InspirationCard extends StatefulWidget { 99 | final InspirationItem item; 100 | 101 | InspirationCard(this.item); 102 | 103 | _InspirationCardState createState() => _InspirationCardState(); 104 | } 105 | 106 | class _InspirationCardState extends State { 107 | @override 108 | Widget build(BuildContext context) { 109 | return Container( 110 | margin: EdgeInsets.all(4.0), 111 | child: Container( 112 | child: Column( 113 | crossAxisAlignment: CrossAxisAlignment.start, 114 | children: [ 115 | Expanded( 116 | child: Image.network( 117 | widget.item.url, 118 | fit: BoxFit.fitHeight, 119 | )), 120 | // Expanded( 121 | // child: Container(color: Colors.grey[300]), 122 | // ), 123 | 124 | // Image.network(widget.item.Salon_u), 125 | ], 126 | ), 127 | ), 128 | ); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /lib/bloc/salon_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:rxdart/rxdart.dart'; 4 | import 'package:salon/api_helper.dart'; 5 | import 'package:salon/bloc/base_bloc.dart'; 6 | import 'package:salon/data/local/SharedPrefsHelper.dart'; 7 | import 'package:salon/data/model/category_model.dart'; 8 | import 'package:salon/data/model/promotion_model.dart'; 9 | import 'package:salon/data/model/salon_model.dart'; 10 | import 'package:salon/data/model/stylist_model.dart'; 11 | import 'package:salon/data/network/api_endpoint.dart'; 12 | import 'package:salon/feed_model.dart'; 13 | import 'package:salon/network_utils.dart'; 14 | 15 | class SalonBloc extends BaseBloc { 16 | static SalonBloc _instance = SalonBloc._internal(); 17 | BehaviorSubject> salonPopularList; 18 | BehaviorSubject> salonExtraList; 19 | BehaviorSubject> promotionList; 20 | BehaviorSubject> feedController; 21 | BehaviorSubject> stylistController; 22 | BehaviorSubject> categoryController; 23 | 24 | factory SalonBloc() { 25 | if (_instance == null) return _instance = SalonBloc._internal(); 26 | return _instance; 27 | } 28 | 29 | SalonBloc._internal() { 30 | salonPopularList = new BehaviorSubject(); 31 | salonExtraList = new BehaviorSubject(); 32 | promotionList = new BehaviorSubject(); 33 | feedController = new BehaviorSubject(); 34 | stylistController = new BehaviorSubject(); 35 | categoryController = new BehaviorSubject(); 36 | 37 | initPromotionData(); 38 | initSalonData(); 39 | initFeedData(); 40 | initStylistData(); 41 | initCategoryData(); 42 | } 43 | 44 | void initSalonData() async { 45 | try { 46 | var response = await apiHelper.getWithAuth( 47 | endpoint: ApiEndpoint.salon + 48 | "?location=" + 49 | prefsHelper.selectedAreaId.toString()); 50 | if (NetworkUtils.isReqSuccess( 51 | tag: ApiEndpoint.salon, response: response)) { 52 | print(response.body); 53 | 54 | var model = SalonModel.fromJson(json.decode(response.body)); 55 | /* String group = model.salonList 56 | .elementAt(0) 57 | .group; 58 | List popular = model.salonList.where((item) => item.group == group).toList(); 59 | List extras = model.salonList.where((item) => item.group != group).toList(); 60 | */ 61 | salonPopularList.add(model.salonList); 62 | // salonExtraList.add(extras); 63 | } else { 64 | print("Some error"); 65 | } 66 | } catch (e) { 67 | print(e); 68 | } 69 | } 70 | 71 | void initPromotionData() async { 72 | try { 73 | var response = 74 | await apiHelper.getWithAuth(endpoint: ApiEndpoint.promotion); 75 | if (NetworkUtils.isReqSuccess( 76 | tag: ApiEndpoint.promotion, response: response)) { 77 | print(response.body); 78 | 79 | var model = PromotionModel.fromJson(json.decode(response.body)); 80 | promotionList.add(model.list); 81 | } else { 82 | print("Some error"); 83 | } 84 | } catch (e) { 85 | print(e); 86 | } 87 | } 88 | 89 | void initFeedData() async { 90 | NetworkResponse _networkResponse = 91 | await apiHelper.getWithAuth1(endpoint: ApiEndpoint.feeds); 92 | if (_networkResponse.isSuccess) { 93 | FeedModel feedModel = 94 | FeedModel.fromJson(json.decode(_networkResponse.response.body)); 95 | feedController.add(feedModel.feedList); 96 | } else { 97 | print("Some error"); 98 | } 99 | } 100 | 101 | void initStylistData() async { 102 | NetworkResponse _networkResponse = 103 | await apiHelper.getWithAuth1(endpoint: ApiEndpoint.experts); 104 | if (_networkResponse.isSuccess) { 105 | StylistModel _model = 106 | StylistModel.fromJson(json.decode(_networkResponse.response.body)); 107 | stylistController.add(_model.stylistList); 108 | } else { 109 | print("Some error"); 110 | } 111 | } 112 | 113 | void initCategoryData() async { 114 | NetworkResponse _networkResponse = 115 | await apiHelper.getWithAuth1(endpoint: ApiEndpoint.categoryList); 116 | if (_networkResponse.isSuccess) { 117 | CategoryModel _model = 118 | CategoryModel.fromJson(json.decode(_networkResponse.response.body)); 119 | categoryController.add(_model.categoryList); 120 | } else { 121 | print("Some error"); 122 | } 123 | } 124 | 125 | @override 126 | void dispose() { 127 | salonPopularList.close(); 128 | salonExtraList.close(); 129 | promotionList.close(); 130 | feedController.close(); 131 | stylistController.close(); 132 | categoryController.close(); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/example/salon/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.salon; 2 | 3 | import android.Manifest; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.os.Bundle; 7 | import android.support.v4.app.ActivityCompat; 8 | import android.support.v4.content.ContextCompat; 9 | 10 | import io.flutter.app.FlutterActivity; 11 | import io.flutter.plugin.common.MethodCall; 12 | import io.flutter.plugin.common.MethodChannel; 13 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 14 | import io.flutter.plugin.common.MethodChannel.Result; 15 | import io.flutter.plugins.GeneratedPluginRegistrant; 16 | import swarajsaaj.smscodereader.interfaces.OTPListener; 17 | import swarajsaaj.smscodereader.receivers.OtpReader; 18 | 19 | public class MainActivity extends FlutterActivity implements OTPListener { 20 | private static final String CHANNEL = "workflowapp.flutter.io/devicedetail"; 21 | private static final String CHANNEL_VERSION = "com.example.workflowsapp/version"; 22 | private static final String TAG = MainActivity.class.getName(); 23 | private static final int MY_PERMISSIONS_REQUEST_RECEIVE_SMS = 4242; 24 | private Result sms_result; 25 | private Result otp_result; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | GeneratedPluginRegistrant.registerWith(this); 31 | 32 | new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler( 33 | new MethodCallHandler() { 34 | @Override 35 | public void onMethodCall(MethodCall call, Result result) { 36 | if (call.method.equals("getSerialId")) 37 | result.success(getSerialId()); 38 | else if (call.method.equals("smsPermission")) { 39 | MainActivity.this.sms_result = result; 40 | checkSMSPermission(); 41 | } else if (call.method.equals("getOtp")) { 42 | MainActivity.this.otp_result = result; 43 | initOtpReader(); 44 | } else if (call.method.equals("getVersion")) { 45 | result.success(BuildConfig.VERSION_CODE); 46 | } else result.notImplemented(); 47 | } 48 | }); 49 | } 50 | 51 | private void initOtpReader() { 52 | OtpReader.bind(this, "TFCTOR"); 53 | 54 | } 55 | 56 | @Override 57 | protected void onDestroy() { 58 | OtpReader.bind(null, null); 59 | 60 | super.onDestroy(); 61 | } 62 | 63 | @Override 64 | public void otpReceived(String smsText) { 65 | //Do whatever you want to do with the text 66 | 67 | String otp = smsText.replaceAll("[^0-9]", ""); 68 | otp_result.success(otp); 69 | } 70 | 71 | private void checkSMSPermission() { 72 | if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) { 73 | // only for gingerbread and newer versions 74 | sms_result.success(true); 75 | } else if (ContextCompat.checkSelfPermission(MainActivity.this, 76 | Manifest.permission.RECEIVE_SMS) 77 | != PackageManager.PERMISSION_GRANTED) { 78 | 79 | // Permission is not granted 80 | // Should we show an explanation? 81 | if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this, 82 | Manifest.permission.RECEIVE_SMS)) { 83 | // Show an explanation to the user *asynchronously* -- don't block 84 | // this thread waiting for the user's response! After the user 85 | // sees the explanation, try again to request the permission. 86 | ActivityCompat.requestPermissions(MainActivity.this, 87 | new String[]{Manifest.permission.RECEIVE_SMS}, 88 | MY_PERMISSIONS_REQUEST_RECEIVE_SMS); 89 | } else { 90 | // No explanation needed; request the permission 91 | ActivityCompat.requestPermissions(MainActivity.this, 92 | new String[]{Manifest.permission.RECEIVE_SMS}, 93 | MY_PERMISSIONS_REQUEST_RECEIVE_SMS); 94 | 95 | // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an 96 | // app-defined int constant. The callback method gets the 97 | // result of the request. 98 | } 99 | } else { 100 | // Permission has already been granted 101 | sms_result.success(true); 102 | } 103 | } 104 | 105 | 106 | // private String getIMEIId(){ 107 | // TelephonyManager tm = (TelephonyManager) getSystemService(android.content.Context.TELEPHONY_SERVICE); 108 | // return tm.getDeviceId(); 109 | // return "IMEI"; 110 | // } 111 | 112 | private String getSerialId() { 113 | if (Build.VERSION.SDK_INT < 26) 114 | return Build.SERIAL; 115 | else 116 | return Build.getSerial(); 117 | 118 | } 119 | 120 | @Override 121 | public void onRequestPermissionsResult(int requestCode, 122 | String permissions[], int[] grantResults) { 123 | 124 | switch (requestCode) { 125 | case MY_PERMISSIONS_REQUEST_RECEIVE_SMS: { 126 | // If request is cancelled, the result arrays are empty. 127 | if (grantResults.length > 0 128 | && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 129 | // permission was granted, yay! Do the 130 | // contacts-related task you need to do. 131 | sms_result.success(true); 132 | return; 133 | } else { 134 | // permission denied, boo! Disable the 135 | // functionality that depends on this permission. 136 | sms_result.success(false); 137 | return; 138 | } 139 | 140 | } 141 | 142 | // other 'case' lines to check for other 143 | // permissions this app might request. 144 | } 145 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /lib/pages/salon_service_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/service_bloc.dart'; 3 | import 'package:salon/data/model/salon_detail_model.dart'; 4 | import 'package:salon/data/model/service_model.dart'; 5 | import 'package:salon/utils/dialog_utils.dart'; 6 | 7 | class SalonServicePage extends StatefulWidget { 8 | final SalonDetailModel salonDetailModel; 9 | final CategoryModel categoryModel; 10 | 11 | SalonServicePage({@required this.salonDetailModel, this.categoryModel}); 12 | 13 | _SalonServicePageState createState() => _SalonServicePageState(); 14 | } 15 | 16 | class _SalonServicePageState extends State { 17 | ServiceBloc _bloc = new ServiceBloc(); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _bloc.initData(widget.salonDetailModel.id, widget.categoryModel.id); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Scaffold( 28 | appBar: AppBar( 29 | title: Text( 30 | widget.salonDetailModel.name, 31 | style: TextStyle(color: Colors.blueGrey[700]), 32 | ), 33 | iconTheme: IconThemeData(color: Colors.blueGrey[700]), 34 | elevation: 2.0, 35 | backgroundColor: Colors.white), 36 | floatingActionButton: FloatingActionButton( 37 | onPressed: () {}, 38 | backgroundColor: Colors.blue, 39 | child: Icon( 40 | Icons.phone, 41 | color: Colors.white, 42 | ), 43 | ), 44 | body: StreamBuilder( 45 | builder: (BuildContext context, AsyncSnapshot snapshot) { 46 | if (snapshot.hasData) { 47 | return SingleChildScrollView( 48 | child: Container( 49 | margin: EdgeInsets.all(4.0), 50 | child: Card( 51 | elevation: 2.0, 52 | color: Colors.white, 53 | child: Padding( 54 | padding: const EdgeInsets.symmetric(vertical:8.0,horizontal: 12.0), 55 | child: Column( 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | children: getList(snapshot.data.list), 58 | ), 59 | ), 60 | ), 61 | ), 62 | ); 63 | } else if (snapshot.hasError) { 64 | return Center( 65 | child: Text("Some error..."), 66 | ); 67 | } else { 68 | return DialogUtils.showCircularProgressBar(); 69 | } 70 | }, 71 | stream: _bloc.serviceDetail, 72 | ), 73 | ); 74 | } 75 | 76 | @override 77 | void dispose() { 78 | _bloc.dispose(); 79 | super.dispose(); 80 | } 81 | 82 | /*List getCategories(SalonDetailModel data) { 83 | return data.categories 84 | .map((item) => Chip( 85 | label: Text( 86 | item.category, 87 | style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600), 88 | ), 89 | backgroundColor: Colors.pinkAccent, 90 | )) 91 | .toList(); 92 | } 93 | */ 94 | List getList(List serviceList) { 95 | assert(serviceList.isNotEmpty); 96 | List _widgetList = new List(); 97 | List _categoryList = getCategories(serviceList); 98 | 99 | for (String category in _categoryList) { 100 | _widgetList.add(CategoryWidget(category: category)); 101 | var _filteredServiceList = getServices(serviceList, category); 102 | for (ServiceItem _serviceItem in _filteredServiceList) _widgetList.add(ServiceWidget(serviceItem: _serviceItem)); 103 | } 104 | 105 | return _widgetList; 106 | } 107 | 108 | List getCategories(List list) { 109 | return list 110 | .map((serviceItem) { 111 | return serviceItem.categoryItem.category; 112 | }).toList() 113 | .toSet() 114 | .toList(); 115 | } 116 | 117 | List getServices(List serviceList, String category) { 118 | return serviceList.where((serviceItem) => serviceItem.categoryItem.category == category).toList(); 119 | } 120 | } 121 | 122 | class CategoryWidget extends StatelessWidget { 123 | final String category; 124 | 125 | CategoryWidget({@required this.category}); 126 | @override 127 | Widget build(BuildContext context) { 128 | return Chip( 129 | label: Text( 130 | category, 131 | style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600), 132 | ), 133 | backgroundColor: Colors.pinkAccent, 134 | ); 135 | } 136 | } 137 | 138 | class ServiceWidget extends StatelessWidget { 139 | final ServiceItem serviceItem; 140 | 141 | ServiceWidget({@required this.serviceItem}); 142 | 143 | @override 144 | Widget build(BuildContext context) { 145 | return Padding( 146 | padding: const EdgeInsets.symmetric(vertical:8.0), 147 | child: Row( 148 | children: [ 149 | Flexible(flex: 2,child: Text(serviceItem.name,style: TextStyle(fontSize: 16.0),)), 150 | SizedBox( 151 | width: 16.0, 152 | ), 153 | Flexible(flex:1,child: Text(serviceItem.price.toString(),style: TextStyle(fontWeight: FontWeight.w700,fontSize: 16.0),)), 154 | ], 155 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 156 | crossAxisAlignment: CrossAxisAlignment.start, 157 | ), 158 | ); 159 | } 160 | } 161 | 162 | class HeaderWidget extends StatelessWidget { 163 | final String text; 164 | 165 | HeaderWidget(this.text); 166 | 167 | @override 168 | Widget build(BuildContext context) { 169 | return Container( 170 | padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0), 171 | child: Text( 172 | text.toUpperCase(), 173 | style: TextStyle( 174 | fontWeight: FontWeight.w700, 175 | color: Colors.blueGrey[700], 176 | ), 177 | ), 178 | ); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /lib/pages/area_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:salon/bloc/area_bloc.dart'; 3 | import 'package:salon/data/local/SharedPrefsHelper.dart'; 4 | import 'package:salon/data/model/area_model.dart'; 5 | import 'package:salon/pages/home_page.dart'; 6 | import 'package:salon/utils/dialog_utils.dart'; 7 | 8 | class AreaPage extends StatefulWidget { 9 | AreaPage(); 10 | 11 | _AreaPageState createState() => _AreaPageState(); 12 | } 13 | 14 | class _AreaPageState extends State { 15 | AreaBloc _bloc; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _bloc = new AreaBloc(); 21 | } 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | return Scaffold( 26 | body: Column( 27 | crossAxisAlignment: CrossAxisAlignment.start, 28 | children: [ 29 | SizedBox(height: 64.0), 30 | Padding( 31 | padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16.0), 32 | child: Row( 33 | mainAxisSize: MainAxisSize.min, 34 | children: [ 35 | Image.asset( 36 | 'assets/images/location.png', 37 | height: 24.0, 38 | ), 39 | SizedBox( 40 | width: 8.0, 41 | ), 42 | Text( 43 | "Select an Area :", 44 | style: TextStyle(fontSize: 20.0), 45 | ), 46 | ], 47 | ), 48 | ), 49 | Expanded( 50 | child: SingleChildScrollView( 51 | padding: EdgeInsets.symmetric(horizontal: 2.0), 52 | child: StreamBuilder( 53 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 54 | if (snapshot.hasData) { 55 | return Column( 56 | crossAxisAlignment: CrossAxisAlignment.start, 57 | children: [ 58 | Padding( 59 | padding: const EdgeInsets.symmetric(horizontal: 14.0, vertical: 4.0), 60 | child: Text( 61 | "NEW DELHI", 62 | style: TextStyle( 63 | fontSize: 14.0, 64 | ), 65 | ), 66 | ), 67 | Column( 68 | children: snapshot.data 69 | .map((item) => AreaWidget( 70 | item: item, 71 | callback: onAreaTap, 72 | )) 73 | .toList(), 74 | ), 75 | ], 76 | ); 77 | } else if (snapshot.hasError) { 78 | return Center( 79 | child: Text("Some error..."), 80 | ); 81 | } else { 82 | return DialogUtils.showCircularProgressBar(); 83 | } 84 | }, 85 | stream: _bloc.areaController, 86 | ), 87 | ), 88 | ), 89 | ], 90 | ), 91 | /*body: StreamBuilder( 92 | builder: (BuildContext context, AsyncSnapshot> snapshot) { 93 | if (snapshot.hasData) { 94 | return CustomScrollView( 95 | slivers: [ 96 | SliverPadding( 97 | padding: EdgeInsets.all(4.0), 98 | sliver: SliverGrid( 99 | delegate: SliverChildBuilderDelegate((context, index) { 100 | return ProductCard(snapshot.data.elementAt(index)); 101 | }, childCount: snapshot.data.length), 102 | gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, childAspectRatio: 0.9), 103 | ), 104 | ), 105 | ], 106 | ); 107 | } else if (snapshot.hasError) { 108 | return Center( 109 | child: Text("Some error..."), 110 | ); 111 | } else { 112 | return DialogUtils.showCircularProgressBar(); 113 | } 114 | }, 115 | stream: _bloc.profileController, 116 | ),*/ 117 | ); 118 | } 119 | 120 | @override 121 | void dispose() { 122 | _bloc.dispose(); 123 | super.dispose(); 124 | } 125 | 126 | void onAreaTap(AreaItem item) { 127 | prefsHelper.selectedAreaName = item.location; 128 | prefsHelper.selectedAreaId = item.id; 129 | Navigator.pushAndRemoveUntil(context, MaterialPageRoute(builder: (context) => HomePage()), (route) => false); 130 | } 131 | } 132 | 133 | class MenuItem extends StatelessWidget { 134 | final String title; 135 | 136 | MenuItem(this.title); 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | return Container( 141 | decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.grey[100]), bottom: BorderSide(color: Colors.grey[100]))), 142 | child: ListTile( 143 | title: Text(title), 144 | trailing: Icon(Icons.chevron_right), 145 | ), 146 | ); 147 | } 148 | } 149 | 150 | class WalletMenuItem extends StatelessWidget { 151 | @override 152 | Widget build(BuildContext context) { 153 | return Container( 154 | decoration: BoxDecoration(color: Colors.white, border: Border(top: BorderSide(color: Colors.grey[100]), bottom: BorderSide(color: Colors.grey[100]))), 155 | child: ListTile( 156 | title: Text("DOT Wallet"), 157 | trailing: Container( 158 | height: 24.0, 159 | child: Text( 160 | "₹ 0", 161 | textAlign: TextAlign.center, 162 | style: TextStyle(color: Colors.white, fontWeight: FontWeight.w700), 163 | ), 164 | padding: EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.5), 165 | decoration: BoxDecoration(color: Color(0xff64DD17), borderRadius: BorderRadius.all(Radius.circular(12.0))), 166 | ), 167 | ), 168 | ); 169 | } 170 | } 171 | 172 | typedef void OnTapCallback(AreaItem item); 173 | 174 | class AreaWidget extends StatelessWidget { 175 | final AreaItem item; 176 | 177 | final OnTapCallback callback; 178 | 179 | AreaWidget({@required this.item, @required this.callback}); 180 | 181 | @override 182 | Widget build(BuildContext context) { 183 | return Container( 184 | margin: EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), 185 | child: Card( 186 | margin: EdgeInsets.all(0.0), 187 | shape: RoundedRectangleBorder( 188 | borderRadius: BorderRadius.all( 189 | Radius.circular(4.0), 190 | ), 191 | ), 192 | elevation: 2.0, 193 | child: Material( 194 | color: Colors.transparent, 195 | child: InkWell( 196 | onTap: () { 197 | callback(item); 198 | }, 199 | child: ListTile( 200 | title: Text(item.location), 201 | trailing: Icon( 202 | Icons.keyboard_arrow_right, 203 | ), 204 | ), 205 | ), 206 | ), 207 | ), 208 | ); 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /lib/pages/salon_detail_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:cached_network_image/cached_network_image.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:salon/bloc/salon_detail_bloc.dart'; 4 | import 'package:salon/data/model/salon_detail_model.dart'; 5 | import 'package:salon/data/model/salon_model.dart'; 6 | import 'package:salon/pages/salon_service_page.dart'; 7 | import 'package:salon/utils/dialog_utils.dart'; 8 | 9 | class SalonDetailPage extends StatefulWidget { 10 | final SalonItem item; 11 | 12 | SalonDetailPage({@required this.item}); 13 | 14 | _SalonDetailPageState createState() => _SalonDetailPageState(); 15 | } 16 | 17 | class _SalonDetailPageState extends State { 18 | SalonDetailBloc _bloc = new SalonDetailBloc(); 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _bloc.initData(widget.item.id); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | /*appBar: AppBar( 30 | title: Text( 31 | widget.item.name, 32 | style: TextStyle(color: Colors.blueGrey[700]), 33 | ), 34 | iconTheme: IconThemeData(color: Colors.blueGrey[700]), 35 | elevation: 2.0, 36 | backgroundColor: Colors.white), 37 | */ 38 | floatingActionButton: FloatingActionButton( 39 | onPressed: () {}, 40 | backgroundColor: Colors.blue, 41 | child: Icon( 42 | Icons.phone, 43 | color: Colors.white, 44 | ), 45 | ), 46 | body: StreamBuilder( 47 | builder: (BuildContext context, AsyncSnapshot snapshot) { 48 | if (snapshot.hasData) { 49 | return SingleChildScrollView( 50 | child: Container( 51 | color: Colors.white, 52 | child: Column( 53 | children: [ 54 | AspectRatio( 55 | aspectRatio: 1.5, 56 | child: CachedNetworkImage( 57 | imageUrl: widget.item.image, 58 | fit: BoxFit.fitWidth, 59 | ), 60 | ), 61 | SizedBox( 62 | height: 6.0, 63 | ), 64 | Padding( 65 | padding: const EdgeInsets.symmetric(horizontal: 12.0, vertical: 4.0), 66 | child: Row( 67 | children: [ 68 | Text( 69 | widget.item.name, 70 | style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.w700, letterSpacing: 1.1), 71 | ), 72 | ], 73 | ), 74 | ), 75 | Padding( 76 | padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0), 77 | child: Row( 78 | children: [ 79 | Icon( 80 | Icons.location_on, 81 | size: 18.0, 82 | ), 83 | Expanded( 84 | child: Padding( 85 | padding: const EdgeInsets.symmetric(horizontal: 4.0), 86 | child: Text( 87 | widget.item.address, 88 | style: TextStyle(fontSize: 14.0), 89 | textAlign: TextAlign.start, 90 | ), 91 | ), 92 | ), 93 | Text( 94 | "0.2 km", 95 | style: TextStyle(fontSize: 14.0), 96 | textAlign: TextAlign.start, 97 | ), 98 | SizedBox( 99 | width: 8.0, 100 | ), 101 | CircleAvatar( 102 | backgroundColor: Colors.green[900], 103 | radius: 16.0, 104 | child: Icon( 105 | Icons.location_on, 106 | color: Colors.white, 107 | ), 108 | ), 109 | SizedBox( 110 | width: 4.0, 111 | ), 112 | ], 113 | ), 114 | ), 115 | SizedBox( 116 | height: 8.0, 117 | ), 118 | Container( 119 | child: Row( 120 | mainAxisSize: MainAxisSize.min, 121 | children: [ 122 | Icon( 123 | Icons.done_all, 124 | color: Colors.white, 125 | size: 16.0, 126 | ), 127 | SizedBox( 128 | width: 4.0, 129 | ), 130 | Text( 131 | "Currently Open", 132 | style: TextStyle(fontSize: 12.0, color: Colors.white, fontWeight: FontWeight.w900), 133 | ), 134 | ], 135 | ), 136 | padding: EdgeInsets.only(top: 4.0, bottom: 6.0, left: 10.0, right: 12.0), 137 | decoration: BoxDecoration( 138 | border: Border.all(color: Colors.blueGrey[200], width: 0.5), 139 | borderRadius: BorderRadius.all( 140 | Radius.circular(16.0), 141 | ), 142 | color: Colors.green), 143 | ), 144 | SizedBox( 145 | height: 12.0, 146 | ), 147 | Container( 148 | color: Colors.blueGrey[50], 149 | height: 1.0, 150 | margin: EdgeInsets.symmetric(horizontal: 32.0), 151 | ), 152 | SizedBox( 153 | height: 4.0, 154 | ), 155 | Row( 156 | children: [ 157 | HeaderWidget("About"), 158 | ], 159 | ), 160 | Row( 161 | children: [ 162 | Expanded( 163 | child: Padding( 164 | padding: const EdgeInsets.symmetric(horizontal: 12.0), 165 | child: Text( 166 | "Affinity Salon is a very famous salon chain in whole Delhi NCR Area, is a very famous salon chain in whole Delhi NCR Area.", 167 | style: TextStyle(fontSize: 13.0), 168 | ), 169 | ), 170 | ) 171 | ], 172 | ), 173 | SizedBox( 174 | height: 12.0, 175 | ), 176 | Row( 177 | children: [ 178 | Padding( 179 | padding: const EdgeInsets.only(left: 12.0, right: 4.0), 180 | child: Text( 181 | "Timings: ", 182 | style: TextStyle(fontWeight: FontWeight.w700), 183 | ), 184 | ), 185 | Padding( 186 | padding: const EdgeInsets.only(left: 0.0, bottom: 2.0), 187 | child: Text( 188 | "10:00AM - 8:00PM ", 189 | style: TextStyle(fontWeight: FontWeight.w500, fontSize: 12.0, color: Colors.black87), 190 | ), 191 | ), 192 | ], 193 | ), 194 | /*Padding( 195 | padding: const EdgeInsets.symmetric(horizontal: 8.0), 196 | child: new Wrap( 197 | spacing: 8.0, 198 | // gap between adjacent chips 199 | runSpacing: 8.0, // gap between lines 200 | children: getCategories(snapshot.data), 201 | ), 202 | ),*/ 203 | SizedBox( 204 | height: 6.0, 205 | ), 206 | ], 207 | ), 208 | ), 209 | ); 210 | } else if (snapshot.hasError) { 211 | return Center( 212 | child: Text("Some error..."), 213 | ); 214 | } else { 215 | return DialogUtils.showCircularProgressBar(); 216 | } 217 | }, 218 | stream: _bloc.salonDetail, 219 | ), 220 | ); 221 | } 222 | 223 | @override 224 | void dispose() { 225 | _bloc.dispose(); 226 | super.dispose(); 227 | } 228 | 229 | List getCategories(SalonDetailModel salonDetailModel) { 230 | return salonDetailModel.categories 231 | .map( 232 | (categoryItem) => GestureDetector( 233 | child: Chip( 234 | label: Text( 235 | categoryItem.category, 236 | style: TextStyle(color: Colors.white, fontWeight: FontWeight.w600), 237 | ), 238 | backgroundColor: Colors.pinkAccent, 239 | ), 240 | onTap: () { 241 | onCategoryTap(categoryItem, salonDetailModel); 242 | }, 243 | ), 244 | ) 245 | .toList(); 246 | } 247 | 248 | void onCategoryTap(CategoryModel categoryModel, SalonDetailModel salonDetailModel) { 249 | Navigator.push( 250 | context, 251 | new MaterialPageRoute( 252 | builder: (context) => new SalonServicePage( 253 | categoryModel: categoryModel, 254 | salonDetailModel: salonDetailModel, 255 | ))); 256 | } 257 | } 258 | 259 | class HeaderWidget extends StatelessWidget { 260 | final String text; 261 | 262 | HeaderWidget(this.text); 263 | 264 | @override 265 | Widget build(BuildContext context) { 266 | return Container( 267 | padding: EdgeInsets.symmetric(vertical: 6.0, horizontal: 12.0), 268 | child: Text( 269 | text, 270 | style: TextStyle(fontWeight: FontWeight.w700, color: Colors.black87, letterSpacing: 1.1, fontSize: 16.0), 271 | ), 272 | ); 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /lib/pages/login_page.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | import 'package:http/http.dart' as http; 7 | import 'package:salon/api_helper.dart'; 8 | import 'package:salon/data/local/SharedPrefsHelper.dart'; 9 | import 'package:salon/data/network/api_endpoint.dart'; 10 | import 'package:salon/network_utils.dart'; 11 | import 'package:salon/utils/dialog_utils.dart'; 12 | import 'package:salon/utils/snackbar_utils.dart'; 13 | 14 | import 'home_page.dart'; 15 | 16 | class LoginPage extends StatefulWidget { 17 | @override 18 | State createState() { 19 | return new LoginPageState(); 20 | } 21 | } 22 | 23 | class LoginPageState extends State { 24 | static const platform = 25 | const MethodChannel("workflowapp.flutter.io/devicedetail"); 26 | 27 | TextEditingController _numberController = new TextEditingController(); 28 | TextEditingController _otpController = new TextEditingController(); 29 | 30 | bool _registerButtonEnabled = false; 31 | bool _otpButtonEnabled = false; 32 | 33 | String _sessionId; 34 | 35 | final GlobalKey _scaffoldKey = new GlobalKey(); 36 | bool showNumberPage = false; 37 | bool isLoading = false; 38 | 39 | @override 40 | Widget build(BuildContext context) { 41 | return new Scaffold( 42 | key: _scaffoldKey, 43 | backgroundColor: Colors.grey[100], 44 | body: Container( 45 | child: Center( 46 | child: Container( 47 | margin: const EdgeInsets.symmetric(horizontal: 48.0), 48 | child: new Column( 49 | mainAxisSize: MainAxisSize.min, 50 | mainAxisAlignment: MainAxisAlignment.center, 51 | crossAxisAlignment: CrossAxisAlignment.center, 52 | children: [ 53 | Image.asset( 54 | 'assets/images/logo.png', 55 | height: 90.0, 56 | ), 57 | new SizedBox( 58 | height: 48.0, 59 | ), 60 | (!showNumberPage ? numberPage() : otpPage()), 61 | ], 62 | ), 63 | ), 64 | ), 65 | ), 66 | ); 67 | } 68 | 69 | onRegisterButtonClick() async { 70 | await _initOtpReader(); 71 | 72 | DialogUtils.showProgressBar(context, "Requesting OTP..."); 73 | 74 | var number = _numberController.text; 75 | assert(number.length == 10); 76 | Map map = new Map(); 77 | map.putIfAbsent("number", () => number); 78 | var response = 79 | await apiHelper.postWithoutAuth(ApiEndpoint.sendOtp, jsonEncode(map)); 80 | if (NetworkUtils.isReqSuccess(tag: "sendOtp", response: response)) { 81 | Navigator.pop(context); 82 | 83 | _sessionId = json.decode(response.body)['Details']; 84 | assert(_sessionId != null && _sessionId.isNotEmpty); 85 | setState(() { 86 | showNumberPage = !showNumberPage; 87 | }); 88 | } else { 89 | Navigator.pop(context); 90 | SnackbarUtils.show( 91 | _scaffoldKey, "Something went wrong. Please try again"); 92 | //_scaffoldKey.currentState.showSnackBar(snackbar) 93 | } 94 | } 95 | 96 | onOtpButtonClick() async { 97 | DialogUtils.showProgressBar(context, "Please Wait!"); 98 | String otp = _otpController.text.trim(); 99 | assert(otp.length == 6); 100 | 101 | Map _map = new Map(); 102 | // { 103 | // "device_name": "string", 104 | // "otp_input": "string", 105 | // "phone": "string", 106 | // "session_id": "string" 107 | // } 108 | //TODO: remove this hardcode 109 | assert(_sessionId != null); 110 | assert(_otpController.text != null); 111 | assert(_numberController.text != null); 112 | 113 | _map.putIfAbsent("device_name", () => "Ayush_phone"); 114 | _map.putIfAbsent("otp_input", () => _otpController.text.trim()); 115 | _map.putIfAbsent("phone", () => _numberController.text.trim()); 116 | _map.putIfAbsent("session_id", () => _sessionId); 117 | 118 | print(json.encode(_map)); 119 | http.Response response = await apiHelper.postWithoutAuth( 120 | ApiEndpoint.verifyOtp, json.encode(_map)); 121 | if (NetworkUtils.isReqSuccess( 122 | tag: "verifyOtp", 123 | response: response, 124 | )) { 125 | print(response.headers); 126 | String token = jsonDecode(response.body)["token"]; 127 | assert(token != null && token.isNotEmpty); 128 | prefsHelper.isLogin = true; 129 | prefsHelper.token = token; 130 | Navigator.pop(context); 131 | await Future.delayed( 132 | Duration(milliseconds: 200), 133 | ); 134 | 135 | SnackbarUtils.show(_scaffoldKey, "Login Successful"); 136 | await Future.delayed( 137 | Duration(milliseconds: 700), 138 | ); 139 | // Navigator.pushReplacement(context, new MaterialPageRoute(builder: (context) => new AreaPage())); 140 | Navigator.pushReplacement( 141 | context, new MaterialPageRoute(builder: (context) => HomePage())); 142 | } else { 143 | Navigator.pop(context); 144 | 145 | SnackbarUtils.show(_scaffoldKey, "Invalid OTP. Please try again"); 146 | //_scaffoldKey.currentState.showSnackBar(snackbar) 147 | } 148 | } 149 | 150 | Widget numberPage() { 151 | return Column( 152 | children: [ 153 | const SizedBox( 154 | height: 8.0, 155 | ), 156 | new TextField( 157 | controller: _numberController, 158 | onChanged: (text) { 159 | var _number = text; 160 | if (_number.trim().length == 10) { 161 | setState(() { 162 | _registerButtonEnabled = true; 163 | }); 164 | } else { 165 | setState(() { 166 | _registerButtonEnabled = false; 167 | }); 168 | } 169 | }, 170 | keyboardType: TextInputType.phone, 171 | style: new TextStyle(color: Colors.black87, fontSize: 18.0), 172 | decoration: new InputDecoration( 173 | border: OutlineInputBorder(), 174 | contentPadding: const EdgeInsets.all(2.0), 175 | prefixIcon: Padding( 176 | padding: const EdgeInsets.only( 177 | left: 4.0, 178 | right: 4.0, 179 | ), 180 | child: new Icon(Icons.phone), 181 | ), 182 | suffixStyle: TextStyle(color: Colors.grey[500], fontSize: 16.0), 183 | counterText: "", 184 | hintText: "Enter your number"), 185 | obscureText: false, 186 | maxLength: 10, 187 | ), 188 | new SizedBox( 189 | height: 24.0, 190 | ), 191 | new RaisedButton( 192 | elevation: 2.0, 193 | disabledTextColor: Colors.grey[700], 194 | disabledColor: Colors.grey[300], 195 | color: Colors.blueAccent, 196 | shape: RoundedRectangleBorder( 197 | borderRadius: BorderRadius.all( 198 | Radius.circular(20.0), 199 | ), 200 | ), 201 | textColor: Colors.white.withOpacity(0.9), 202 | onPressed: _registerButtonEnabled ? onRegisterButtonClick : null, 203 | child: Padding( 204 | padding: 205 | const EdgeInsets.symmetric(vertical: 10.0, horizontal: 4.0), 206 | child: Row( 207 | mainAxisSize: MainAxisSize.min, 208 | children: [ 209 | Icon( 210 | Icons.power_settings_new, 211 | color: _registerButtonEnabled 212 | ? Colors.white.withOpacity(0.9) 213 | : Colors.grey[700], 214 | size: 20.0, 215 | ), 216 | SizedBox( 217 | width: 4.0, 218 | ), 219 | Text( 220 | "Register", 221 | style: new TextStyle( 222 | fontSize: 16.0, fontWeight: FontWeight.w700), 223 | ), 224 | ], 225 | ), 226 | ), 227 | ), 228 | new SizedBox( 229 | height: 16.0, 230 | ), 231 | ], 232 | ); 233 | } 234 | 235 | Widget otpPage() { 236 | return Column( 237 | children: [ 238 | Padding( 239 | padding: const EdgeInsets.only( 240 | left: 16.0, 241 | right: 16.0, 242 | ), 243 | child: new TextField( 244 | controller: _otpController, 245 | onChanged: (text) { 246 | var _otp = text; 247 | if (_otp.trim().length == 6) { 248 | setState(() { 249 | _otpButtonEnabled = true; 250 | }); 251 | } else { 252 | setState(() { 253 | _otpButtonEnabled = false; 254 | }); 255 | } 256 | }, 257 | keyboardType: TextInputType.phone, 258 | style: new TextStyle(color: Colors.black87, fontSize: 18.0), 259 | decoration: new InputDecoration( 260 | border: OutlineInputBorder(), 261 | contentPadding: const EdgeInsets.all(2.0), 262 | prefixIcon: Padding( 263 | padding: const EdgeInsets.only( 264 | left: 4.0, 265 | right: 4.0, 266 | ), 267 | child: new Icon(Icons.phone), 268 | ), 269 | suffixStyle: TextStyle(color: Colors.grey[500], fontSize: 16.0), 270 | counterText: "", 271 | hintText: "Enter OTP"), 272 | obscureText: false, 273 | maxLength: 6, 274 | ), 275 | ), 276 | new SizedBox( 277 | height: 24.0, 278 | ), 279 | new RaisedButton( 280 | elevation: 2.0, 281 | disabledTextColor: Colors.grey[700], 282 | disabledColor: Colors.grey[300], 283 | color: Colors.blueAccent, 284 | shape: RoundedRectangleBorder( 285 | borderRadius: BorderRadius.all( 286 | Radius.circular(20.0), 287 | ), 288 | ), 289 | textColor: Colors.white.withOpacity(0.9), 290 | onPressed: _otpButtonEnabled ? onOtpButtonClick : null, 291 | child: Padding( 292 | padding: 293 | const EdgeInsets.symmetric(vertical: 10.0, horizontal: 4.0), 294 | child: Text( 295 | "Submit", 296 | style: new TextStyle(fontSize: 16.0, fontWeight: FontWeight.w700), 297 | ), 298 | ), 299 | ), 300 | new SizedBox( 301 | height: 16.0, 302 | ), 303 | ], 304 | ); 305 | } 306 | 307 | Future _initOtpReader() async { 308 | if (Platform.isAndroid) { 309 | final bool result = await platform.invokeMethod('smsPermission'); 310 | if (result != null && result) { 311 | print("SMS Permission granted"); 312 | platform.invokeMethod('getOtp').then((otp) { 313 | print(otp.toString()); 314 | if (otp != null && otp.toString().isNotEmpty) { 315 | _otpController.text = otp.toString(); 316 | onOtpButtonClick(); 317 | } 318 | }); 319 | } else { 320 | print("Otp init error"); 321 | } 322 | } 323 | } 324 | } 325 | --------------------------------------------------------------------------------