diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 518469b..f83cfb9 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -10,12 +10,8 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 3915B9972454B544000BD821 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3915B9962454B544000BD821 /* GoogleService-Info.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 68A0E9B6A28F5130E373EEE2 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FE17B991E2D6714A5E1D4FF5 /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -28,8 +24,6 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( - 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, - 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -41,7 +35,6 @@ 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 3915B9962454B544000BD821 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 4B20A56B8C0449CF4994B84D /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 65A2F69D4D2E6DBD65A20F91 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 6CE5340600B65B28702F21B5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; @@ -50,7 +43,6 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -64,8 +56,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, - 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 68A0E9B6A28F5130E373EEE2 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -84,9 +74,7 @@ 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( - 3B80C3931E831B6300D905FE /* App.framework */, 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, - 9740EEBA1CF902C7004384FC /* Flutter.framework */, 9740EEB21CF90195004384FC /* Debug.xcconfig */, 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 9740EEB31CF90195004384FC /* Generated.xcconfig */, @@ -233,7 +221,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; 79635FBF6A90A2364C399DB8 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; diff --git a/lib/src/app.dart b/lib/src/app.dart index 6c93c3d..83cfe51 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,33 +1,65 @@ +import 'package:farmers_market/src/blocs/auth_bloc.dart'; import 'package:farmers_market/src/routes.dart'; +import 'package:farmers_market/src/screens/landing.dart'; import 'package:farmers_market/src/screens/login.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'dart:io'; -class App extends StatelessWidget { +import 'package:provider/provider.dart'; +final authBloc = AuthBloc(); + +class App extends StatefulWidget { + @override + _AppState createState() => _AppState(); +} + +class _AppState extends State { @override Widget build(BuildContext context) { - return PlatformApp(); + return MultiProvider( + providers: [ + Provider(create: (context) => authBloc), + FutureProvider(create: (context) => authBloc.isLoggedIn()) + ], + child: PlatformApp()); + } + + @override + void dispose() { + authBloc.dispose(); + super.dispose(); } } class PlatformApp extends StatelessWidget { + @override Widget build(BuildContext context) { + + var isLoggedIn = Provider.of(context); + if (Platform.isIOS) { return CupertinoApp( - home: Login(), + home: (isLoggedIn == null) ? loadingScreen(true) : (isLoggedIn == true ) ? Landing() : Login(), onGenerateRoute: Routes.cupertinoRoutes, theme: CupertinoThemeData( scaffoldBackgroundColor: Colors.white ) - ); + ); } else { return MaterialApp( - home: Login(), + home: (isLoggedIn == null) ? loadingScreen(false) : (isLoggedIn == true ) ? Landing() : Login(), onGenerateRoute: Routes.materialRoutes, theme: ThemeData(scaffoldBackgroundColor: Colors.white) ); } } + + Widget loadingScreen(bool isIOS){ + return (isIOS) + ? CupertinoPageScaffold(child: Center(child: CupertinoActivityIndicator(),),) + : Scaffold(body: Center(child: CircularProgressIndicator())); + } + } diff --git a/lib/src/blocs/auth_bloc.dart b/lib/src/blocs/auth_bloc.dart new file mode 100644 index 0000000..8fab0e0 --- /dev/null +++ b/lib/src/blocs/auth_bloc.dart @@ -0,0 +1,86 @@ + +import 'dart:async'; + +import 'package:farmers_market/src/models/user.dart'; +import 'package:farmers_market/src/services/firestore_service.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:rxdart/subjects.dart'; + +final RegExp regExpEmail = RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$'); + +class AuthBloc { + final _email = BehaviorSubject(); + final _password = BehaviorSubject(); + final _user = BehaviorSubject(); + final FirebaseAuth _auth = FirebaseAuth.instance; + final FirestoreService _firestoreService = FirestoreService(); + + //Get Data + Stream get email => _email.stream.transform(validateEmail); + Stream get password => _password.stream.transform(validatePassword); + Stream get isValid => CombineLatestStream.combine2(email, password, (email,password)=> true); + Stream get user => _user.stream; + + //Set Data + Function(String) get changeEmail => _email.sink.add; + Function(String) get changePassword => _password.sink.add; + + dispose(){ + _email.close(); + _password.close(); + _user.close(); + } + + //Transformers + final validateEmail = StreamTransformer.fromHandlers(handleData: (email, sink){ + if (regExpEmail.hasMatch(email.trim())){ + sink.add(email.trim()); + }else { + sink.addError('Must Be Valid Email Address'); + } + }); + + final validatePassword = StreamTransformer.fromHandlers(handleData: (password, sink){ + if (password.length >= 8){ + sink.add(password.trim()); + }else { + sink.addError('8 Character Minimum'); + } + }); + + //Functions + signupEmail() async{ + try{ + AuthResult authResult = await _auth.createUserWithEmailAndPassword(email: _email.value.trim(), password: _password.value.trim()); + var user = User(userId: authResult.user.uid, email: _email.value.trim()); + await _firestoreService.addUser(user); + _user.sink.add(user); + } catch (error){ + print(error); + } + } + + loginEmail() async{ + try{ + AuthResult authResult = await _auth.signInWithEmailAndPassword(email: _email.value.trim(), password: _password.value.trim()); + var user = await _firestoreService.fetchUser(authResult.user.uid); + _user.sink.add(user); + } catch (error){ + print(error); + } + } + + Future isLoggedIn() async { + var firebaseUser = await _auth.currentUser(); + if (firebaseUser == null) return false; + + var user = await _firestoreService.fetchUser(firebaseUser.uid); + if (user == null) return false; + + _user.sink.add(user); + return true; + } + +} \ No newline at end of file diff --git a/lib/src/models/user.dart b/lib/src/models/user.dart new file mode 100644 index 0000000..18e4968 --- /dev/null +++ b/lib/src/models/user.dart @@ -0,0 +1,17 @@ +class User { + final String userId; + final String email; + + User({this.email, this.userId}); + + Map toMap(){ + return { + 'userId': userId, + 'email': email + }; + } + + User.fromFirestore(Map firestore) + : userId = firestore['userId'], + email = firestore['email']; +} \ No newline at end of file diff --git a/lib/src/routes.dart b/lib/src/routes.dart index e7bfee6..3351b55 100644 --- a/lib/src/routes.dart +++ b/lib/src/routes.dart @@ -1,34 +1,38 @@ import 'package:farmers_market/src/screens/landing.dart'; import 'package:farmers_market/src/screens/login.dart'; import 'package:farmers_market/src/screens/signup.dart'; +import 'package:farmers_market/src/screens/vendor.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; abstract class Routes { - - static MaterialPageRoute materialRoutes(RouteSettings settings){ - switch(settings.name){ - case "/": - return MaterialPageRoute(builder: (context) => Landing()); + static MaterialPageRoute materialRoutes(RouteSettings settings) { + switch (settings.name) { + case "/landing": + return MaterialPageRoute(builder: (context) => Landing()); case "/signup": - return MaterialPageRoute(builder: (context) => Signup()); + return MaterialPageRoute(builder: (context) => Signup()); case "/login": - return MaterialPageRoute(builder: (context) => Login()); + return MaterialPageRoute(builder: (context) => Login()); + case "/vendor": + return MaterialPageRoute(builder: (context) => Vendor()); default: - return MaterialPageRoute(builder: (context) => Login()); + return MaterialPageRoute(builder: (context) => Login()); } } - static CupertinoPageRoute cupertinoRoutes(RouteSettings settings){ - switch(settings.name){ - case "/": - return CupertinoPageRoute(builder: (context) => Landing()); + static CupertinoPageRoute cupertinoRoutes(RouteSettings settings) { + switch (settings.name) { + case "/landing": + return CupertinoPageRoute(builder: (context) => Landing()); case "/signup": - return CupertinoPageRoute(builder: (context) => Signup()); + return CupertinoPageRoute(builder: (context) => Signup()); case "/login": - return CupertinoPageRoute(builder: (context) => Login()); + return CupertinoPageRoute(builder: (context) => Login()); + case "/vendor": + return CupertinoPageRoute(builder: (context) => Vendor()); default: - return CupertinoPageRoute(builder: (context) => Login()); + return CupertinoPageRoute(builder: (context) => Login()); } } -} \ No newline at end of file +} diff --git a/lib/src/screens/landing.dart b/lib/src/screens/landing.dart index 2f38e16..d9e8b4e 100644 --- a/lib/src/screens/landing.dart +++ b/lib/src/screens/landing.dart @@ -1,8 +1,30 @@ +import 'package:farmers_market/src/widgets/button.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'dart:io'; class Landing extends StatelessWidget{ @override Widget build(BuildContext context) { - return Scaffold(); + if (Platform.isIOS){ + return CupertinoPageScaffold( + child: pageBody(context), + ); + } else { + return Scaffold(body:pageBody(context)); + } + } + + Widget pageBody(BuildContext context){ + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AppButton( + buttonText: 'Vendor Page', + buttonType: ButtonType.Straw, + onPressed: () => Navigator.pushNamed(context, '/vendor'), + ) + ], + ); } } \ No newline at end of file diff --git a/lib/src/screens/login.dart b/lib/src/screens/login.dart index 2be29d3..513a327 100644 --- a/lib/src/screens/login.dart +++ b/lib/src/screens/login.dart @@ -1,3 +1,4 @@ +import 'package:farmers_market/src/blocs/auth_bloc.dart'; import 'package:farmers_market/src/styles/base.dart'; import 'package:farmers_market/src/styles/text.dart'; import 'package:farmers_market/src/widgets/button.dart'; @@ -8,21 +9,40 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'dart:io'; -class Login extends StatelessWidget { +import 'package:provider/provider.dart'; + +class Login extends StatefulWidget { + @override + _LoginState createState() => _LoginState(); +} + +class _LoginState extends State { + + @override + void initState() { + final authBloc = Provider.of(context,listen: false); + authBloc.user.listen((user) { + if (user != null) Navigator.pushReplacementNamed(context, '/landing'); + }); + super.initState(); + } + @override Widget build(BuildContext context) { + final authBloc = Provider.of(context); + if (Platform.isIOS) { return CupertinoPageScaffold( - child: pageBody(context), + child: pageBody(context, authBloc), ); } else { return Scaffold( - body: pageBody(context), + body: pageBody(context, authBloc), ); } } - Widget pageBody(BuildContext context) { + Widget pageBody(BuildContext context, AuthBloc authBloc) { return ListView( padding: EdgeInsets.all(0.0), children: [ @@ -39,56 +59,84 @@ class Login extends StatelessWidget { image: DecorationImage(image: AssetImage('assets/images/logo.png')), ), ), - AppTextField( - isIOS: Platform.isIOS, - hintText: 'Email', - cupertinoIcon: CupertinoIcons.mail_solid, - materialIcon: Icons.email, - textInputType: TextInputType.emailAddress, + StreamBuilder( + stream: authBloc.email, + builder: (context, snapshot) { + return AppTextField( + isIOS: Platform.isIOS, + hintText: 'Email', + cupertinoIcon: CupertinoIcons.mail_solid, + materialIcon: Icons.email, + textInputType: TextInputType.emailAddress, + errorText: snapshot.error, + onChanged: authBloc.changeEmail, + ); + }), + StreamBuilder( + stream: authBloc.password, + builder: (context, snapshot) { + return AppTextField( + isIOS: Platform.isIOS, + hintText: 'Password', + cupertinoIcon: IconData(0xf4c9, + fontFamily: CupertinoIcons.iconFont, + fontPackage: CupertinoIcons.iconFontPackage), + materialIcon: Icons.lock, + obscureText: true, + errorText: snapshot.error, + onChanged: authBloc.changePassword, + ); + }), + StreamBuilder( + stream: authBloc.isValid, + builder: (context, snapshot) { + return AppButton( + buttonText: 'Login', + buttonType: (snapshot.data == true) + ? ButtonType.LightBlue + : ButtonType.Disabled, + onPressed: authBloc.loginEmail, + ); + }), + SizedBox( + height: 6.0, ), - AppTextField( - isIOS: Platform.isIOS, - hintText: 'Password', - cupertinoIcon: IconData(0xf4c9,fontFamily: CupertinoIcons.iconFont, fontPackage: CupertinoIcons.iconFontPackage), - materialIcon: Icons.lock, - obscureText: true, + Center( + child: Text('Or', style: TextStyles.suggestion), + ), + SizedBox( + height: 6.0, ), - AppButton(buttonText: 'Login',buttonType: ButtonType.LightBlue,), - SizedBox(height: 6.0,), - Center(child: Text('Or',style: TextStyles.suggestion),), - SizedBox(height: 6.0,), Padding( padding: BaseStyles.listPadding, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - AppSocialButton(socialType: SocialType.Facebook,), - SizedBox(width:15.0), + AppSocialButton( + socialType: SocialType.Facebook, + ), + SizedBox(width: 15.0), AppSocialButton(socialType: SocialType.Google), - ],), + ], + ), ), - Padding( + Padding( padding: BaseStyles.listPadding, - child: RichText( - textAlign: TextAlign.center, - text: TextSpan( - text: 'New Here? ', - style: TextStyles.body, - children: [ - TextSpan( - text: 'Signup', - style: TextStyles.link, - recognizer: TapGestureRecognizer() - ..onTap = () => Navigator.pushNamed(context, '/signup') - ) - ] - ) - ), + child: RichText( + textAlign: TextAlign.center, + text: TextSpan( + text: 'New Here? ', + style: TextStyles.body, + children: [ + TextSpan( + text: 'Signup', + style: TextStyles.link, + recognizer: TapGestureRecognizer() + ..onTap = + () => Navigator.pushNamed(context, '/signup')) + ])), ) ], ); } - - - } diff --git a/lib/src/screens/signup.dart b/lib/src/screens/signup.dart index e543515..12ba3f7 100644 --- a/lib/src/screens/signup.dart +++ b/lib/src/screens/signup.dart @@ -1,3 +1,4 @@ +import 'package:farmers_market/src/blocs/auth_bloc.dart'; import 'package:farmers_market/src/styles/base.dart'; import 'package:farmers_market/src/styles/text.dart'; import 'package:farmers_market/src/widgets/button.dart'; @@ -8,21 +9,39 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'dart:io'; -class Signup extends StatelessWidget{ +import 'package:provider/provider.dart'; + +class Signup extends StatefulWidget{ + @override + _SignupState createState() => _SignupState(); +} + +class _SignupState extends State { + + @override + void initState() { + final authBloc = Provider.of(context,listen: false); + authBloc.user.listen((user) { + if (user != null) Navigator.pushReplacementNamed(context, '/landing'); + }); + super.initState(); + } + @override Widget build(BuildContext context) { + final authBloc = Provider.of(context); if (Platform.isIOS){ return CupertinoPageScaffold( - child: pageBody(context), + child: pageBody(context,authBloc), ); } else { return Scaffold( - body: pageBody(context), + body: pageBody(context,authBloc), ); } } - Widget pageBody(BuildContext context) { + Widget pageBody(BuildContext context,AuthBloc authBloc) { return ListView( padding: EdgeInsets.all(0.0), children: [ @@ -39,28 +58,50 @@ class Signup extends StatelessWidget{ image: DecorationImage(image: AssetImage('assets/images/logo.png')), ), ), - AppTextField( - isIOS: Platform.isIOS, - hintText: 'Email', - cupertinoIcon: CupertinoIcons.mail_solid, - materialIcon: Icons.email, - textInputType: TextInputType.emailAddress, + StreamBuilder( + stream: authBloc.email, + builder: (context, snapshot) { + return AppTextField( + isIOS: Platform.isIOS, + hintText: 'Email', + cupertinoIcon: CupertinoIcons.mail_solid, + materialIcon: Icons.email, + textInputType: TextInputType.emailAddress, + errorText: snapshot.error, + onChanged: authBloc.changeEmail, + ); + } + ), + StreamBuilder( + stream: authBloc.password, + builder: (context, snapshot) { + return AppTextField( + isIOS: Platform.isIOS, + hintText: 'Password', + cupertinoIcon: IconData(0xf4c9,fontFamily: CupertinoIcons.iconFont, fontPackage: CupertinoIcons.iconFontPackage), + materialIcon: Icons.lock, + obscureText: true, + errorText: snapshot.error, + onChanged: authBloc.changePassword, + ); + } ), - AppTextField( - isIOS: Platform.isIOS, - hintText: 'Password', - cupertinoIcon: IconData(0xf4c9,fontFamily: CupertinoIcons.iconFont, fontPackage: CupertinoIcons.iconFontPackage), - materialIcon: Icons.lock, - obscureText: true, + StreamBuilder( + stream: authBloc.isValid, + builder: (context, snapshot) { + return AppButton(buttonText: 'Signup',buttonType: (snapshot.data == true) ? ButtonType.LightBlue : ButtonType.Disabled, onPressed: authBloc.signupEmail,); + } ), - AppButton(buttonText: 'Signup',buttonType: ButtonType.LightBlue,), + SizedBox(height: 6.0,), Center(child: Text('Or',style: TextStyles.suggestion),), + SizedBox(height: 6.0,), Padding( padding: BaseStyles.listPadding, child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ AppSocialButton(socialType: SocialType.Facebook,), + SizedBox(width:15.0), AppSocialButton(socialType: SocialType.Google), ],), ), @@ -85,5 +126,4 @@ class Signup extends StatelessWidget{ ], ); } - } \ No newline at end of file diff --git a/lib/src/screens/vendor.dart b/lib/src/screens/vendor.dart new file mode 100644 index 0000000..483d8e3 --- /dev/null +++ b/lib/src/screens/vendor.dart @@ -0,0 +1,24 @@ +import 'package:farmers_market/src/widgets/navbar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'dart:io'; + +class Vendor extends StatelessWidget { + @override + Widget build(BuildContext context) { + + if (Platform.isIOS) { + return CupertinoPageScaffold( + child: NestedScrollView( + headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled){ + return [ + AppNavbar.cupertinoNavBar(title: 'Vendor Name', context:context), + ]; + }, + body: Center(child: Text('Placeholder'),)), + ); + } else { + return Center(child: Scaffold(body: Text('Material'),)); + } + } +} \ No newline at end of file diff --git a/lib/src/services/firestore_service.dart b/lib/src/services/firestore_service.dart new file mode 100644 index 0000000..50e5ebf --- /dev/null +++ b/lib/src/services/firestore_service.dart @@ -0,0 +1,14 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:farmers_market/src/models/user.dart'; + +class FirestoreService { + Firestore _db = Firestore.instance; + + Future addUser(User user){ + return _db.collection('users').document(user.userId).setData(user.toMap()); + } + + Future fetchUser(String userId){ + return _db.collection('users').document(userId).get().then((snapshot) => User.fromFirestore(snapshot.data)); + } +} \ No newline at end of file diff --git a/lib/src/styles/base.dart b/lib/src/styles/base.dart index 9596b82..1ae3048 100644 --- a/lib/src/styles/base.dart +++ b/lib/src/styles/base.dart @@ -11,6 +11,10 @@ abstract class BaseStyles{ static double get listFieldVertical => 8.0; + static double get animationOffset => 2.0; + + + static EdgeInsets get listPadding { return EdgeInsets.symmetric(horizontal: listFieldHorizontal, vertical: listFieldVertical); } @@ -25,4 +29,14 @@ abstract class BaseStyles{ ]; } + static List get boxShadowPressed { + return [ + BoxShadow( + color: AppColors.darkgray.withOpacity(.5), + offset: Offset(1.0, 1.0), + blurRadius: 1.0, + ) + ]; + } + } \ No newline at end of file diff --git a/lib/src/styles/text.dart b/lib/src/styles/text.dart index 75bf3e8..e43273d 100644 --- a/lib/src/styles/text.dart +++ b/lib/src/styles/text.dart @@ -21,6 +21,11 @@ abstract class TextStyles { textStyle: TextStyle(color: AppColors.lightgray, fontSize: 14.0)); } + static TextStyle get error { + return GoogleFonts.roboto( + textStyle: TextStyle(color: AppColors.red, fontSize: 12.0)); + } + static TextStyle get buttonTextLight { return GoogleFonts.roboto( textStyle: TextStyle( diff --git a/lib/src/styles/textfields.dart b/lib/src/styles/textfields.dart index 9426d8e..a7999f8 100644 --- a/lib/src/styles/textfields.dart +++ b/lib/src/styles/textfields.dart @@ -32,12 +32,24 @@ abstract class TextFieldStyles { borderRadius: BorderRadius.circular(BaseStyles.borderRadius)); } - static InputDecoration materialDecoration(String hintText, IconData icon) { + static BoxDecoration get cupertinoErrorDecoration { + return BoxDecoration( + border: Border.all( + color: AppColors.red, + width: BaseStyles.borderWidth, + ), + borderRadius: BorderRadius.circular(BaseStyles.borderRadius)); + } + + static InputDecoration materialDecoration( + String hintText, IconData icon, String errorText) { return InputDecoration( contentPadding: EdgeInsets.all(8.0), hintText: hintText, hintStyle: TextFieldStyles.placeholder, border: InputBorder.none, + errorText: errorText, + errorStyle: TextStyles.error, focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: AppColors.straw, width: BaseStyles.borderWidth), @@ -46,6 +58,14 @@ abstract class TextFieldStyles { borderSide: BorderSide(color: AppColors.straw, width: BaseStyles.borderWidth), borderRadius: BorderRadius.circular(BaseStyles.borderRadius)), + focusedErrorBorder: OutlineInputBorder( + borderSide: + BorderSide(color: AppColors.straw, width: BaseStyles.borderWidth), + borderRadius: BorderRadius.circular(BaseStyles.borderRadius)), + errorBorder: OutlineInputBorder( + borderSide: + BorderSide(color: AppColors.red, width: BaseStyles.borderWidth), + borderRadius: BorderRadius.circular(BaseStyles.borderRadius)), prefixIcon: iconPrefix(icon), ); } diff --git a/lib/src/widgets/button.dart b/lib/src/widgets/button.dart index d0bdc04..5e218ad 100644 --- a/lib/src/widgets/button.dart +++ b/lib/src/widgets/button.dart @@ -4,21 +4,30 @@ import 'package:farmers_market/src/styles/colors.dart'; import 'package:farmers_market/src/styles/text.dart'; import 'package:flutter/material.dart'; -class AppButton extends StatelessWidget{ +class AppButton extends StatefulWidget{ final String buttonText; final ButtonType buttonType; + final void Function() onPressed; AppButton({ @required this.buttonText, - this.buttonType + this.buttonType, + this.onPressed }); + @override + _AppButtonState createState() => _AppButtonState(); +} + +class _AppButtonState extends State { + bool pressed = false; + @override Widget build(BuildContext context) { TextStyle fontStyle; Color buttonColor; - switch (buttonType) { + switch (widget.buttonType) { case ButtonType.Straw: fontStyle = TextStyles.buttonTextLight; buttonColor = AppColors.straw; @@ -31,8 +40,8 @@ class AppButton extends StatelessWidget{ fontStyle = TextStyles.buttonTextLight; buttonColor = AppColors.darkblue; break; - case ButtonType.LightGray: - fontStyle = TextStyles.buttonTextDark; + case ButtonType.Disabled: + fontStyle = TextStyles.buttonTextLight; buttonColor = AppColors.lightgray; break; case ButtonType.DarkGray: @@ -45,20 +54,43 @@ class AppButton extends StatelessWidget{ break; } - return Padding( - padding: BaseStyles.listPadding, - child: Container( - height: ButtonStyles.buttonHeight, - width: MediaQuery.of(context).size.width, - decoration: BoxDecoration( - color: buttonColor, - borderRadius: BorderRadius.circular(BaseStyles.borderRadius), - boxShadow: BaseStyles.boxShadow + return AnimatedContainer( + padding: EdgeInsets.only( + top: (pressed) ? BaseStyles.listFieldVertical + BaseStyles.animationOffset : BaseStyles.listFieldVertical, + bottom: (pressed) ? BaseStyles.listFieldVertical - BaseStyles.animationOffset : BaseStyles.listFieldVertical, + left: BaseStyles.listFieldHorizontal, + right: BaseStyles.listFieldHorizontal + ), + child: GestureDetector( + child: Container( + height: ButtonStyles.buttonHeight, + width: MediaQuery.of(context).size.width, + decoration: BoxDecoration( + color: buttonColor, + borderRadius: BorderRadius.circular(BaseStyles.borderRadius), + boxShadow: pressed ? BaseStyles.boxShadowPressed : BaseStyles.boxShadow + ), + child: Center(child: Text(widget.buttonText,style: fontStyle,)), ), - child: Center(child: Text(buttonText,style: fontStyle,)), + onTapDown: (details){ + setState(() { + if (widget.buttonType != ButtonType.Disabled) pressed = !pressed; + }); + }, + onTapUp: (details){ + setState(() { + if (widget.buttonType != ButtonType.Disabled) pressed = !pressed; + }); + }, + onTap: (){ + if (widget.buttonType != ButtonType.Disabled) { + widget.onPressed(); + } + }, ), + duration: Duration(milliseconds: 20), ); } } -enum ButtonType { LightBlue,Straw, LightGray, DarkGray, DarkBlue } \ No newline at end of file +enum ButtonType { LightBlue,Straw, Disabled, DarkGray, DarkBlue } \ No newline at end of file diff --git a/lib/src/widgets/navbar.dart b/lib/src/widgets/navbar.dart new file mode 100644 index 0000000..be1f807 --- /dev/null +++ b/lib/src/widgets/navbar.dart @@ -0,0 +1,10 @@ +import 'package:flutter/cupertino.dart'; + +abstract class AppNavbar { + + static CupertinoSliverNavigationBar cupertinoNavBar ({String title, BuildContext context}) { + return CupertinoSliverNavigationBar( + largeTitle: Text(title), + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/textfield.dart b/lib/src/widgets/textfield.dart index aae8908..3032132 100644 --- a/lib/src/widgets/textfield.dart +++ b/lib/src/widgets/textfield.dart @@ -1,14 +1,17 @@ +import 'package:farmers_market/src/styles/text.dart'; import 'package:farmers_market/src/styles/textfields.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; -class AppTextField extends StatelessWidget{ +class AppTextField extends StatefulWidget{ final bool isIOS; final String hintText; final IconData materialIcon; final IconData cupertinoIcon; final TextInputType textInputType; final bool obscureText; + final void Function(String) onChanged; + final String errorText; AppTextField({ @required this.isIOS, @@ -16,37 +19,89 @@ class AppTextField extends StatelessWidget{ @required this.cupertinoIcon, @required this.materialIcon, this.textInputType = TextInputType.text, - this.obscureText = false + this.obscureText = false, + this.onChanged, + this.errorText, }); + @override + _AppTextFieldState createState() => _AppTextFieldState(); +} + +class _AppTextFieldState extends State { + FocusNode _node; + bool displayCupertinoErrorBorder; + TextEditingController _controller; + + + @override + void initState() { + _node = FocusNode(); + _controller = TextEditingController(); + _node.addListener(_handleFocusChange); + displayCupertinoErrorBorder = false; + super.initState(); + } + + void _handleFocusChange(){ + if (_node.hasFocus==false && widget.errorText != null){ + displayCupertinoErrorBorder = true; + } else { + displayCupertinoErrorBorder = false; + } + + widget.onChanged(_controller.text); + + } + + @override + void dispose() { + _node.removeListener(_handleFocusChange); + _node.dispose(); + _controller.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - if (isIOS){ + if (widget.isIOS){ return Padding( padding: EdgeInsets.symmetric(horizontal: TextFieldStyles.textBoxHorizontal, vertical: TextFieldStyles.textBoxVertical), - child: CupertinoTextField( - keyboardType: textInputType, - padding: EdgeInsets.all(12.0), - placeholder: hintText, - placeholderStyle: TextFieldStyles.placeholder, - style: TextFieldStyles.text, - textAlign: TextFieldStyles.textAlign, - cursorColor: TextFieldStyles.cursorColor, - decoration: TextFieldStyles.cupertinoDecoration, - prefix: TextFieldStyles.iconPrefix(cupertinoIcon), - obscureText: obscureText, + child: Column( + children: [ + CupertinoTextField( + keyboardType: widget.textInputType, + padding: EdgeInsets.all(12.0), + placeholder: widget.hintText, + placeholderStyle: TextFieldStyles.placeholder, + style: TextFieldStyles.text, + textAlign: TextFieldStyles.textAlign, + cursorColor: TextFieldStyles.cursorColor, + decoration: (displayCupertinoErrorBorder) ? TextFieldStyles.cupertinoErrorDecoration : TextFieldStyles.cupertinoDecoration, + prefix: TextFieldStyles.iconPrefix(widget.cupertinoIcon), + obscureText: widget.obscureText, + onChanged: widget.onChanged, + focusNode: _node, + controller: _controller, + ), + (widget.errorText !=null) ? Padding( + padding: const EdgeInsets.only(top: 5.0,left:10.0), + child: Row(children: [Text(widget.errorText,style: TextStyles.error,)],), + ) : Container() + ], ), ); } else { return Padding( padding: EdgeInsets.symmetric(horizontal: TextFieldStyles.textBoxHorizontal, vertical: TextFieldStyles.textBoxVertical), child: TextField( - keyboardType: textInputType, + keyboardType: widget.textInputType, cursorColor: TextFieldStyles.cursorColor, style:TextFieldStyles.text, textAlign: TextFieldStyles.textAlign, - decoration: TextFieldStyles.materialDecoration(hintText, materialIcon), - obscureText: obscureText, + decoration: TextFieldStyles.materialDecoration(widget.hintText, widget.materialIcon,widget.errorText), + obscureText: widget.obscureText, + onChanged: widget.onChanged, ), ); } diff --git a/pubspec.lock b/pubspec.lock index 5747234..2b32d60 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,35 +7,35 @@ packages: name: archive url: "https://pub.dartlang.org" source: hosted - version: "2.0.11" + version: "2.0.13" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.5.2" + version: "1.6.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.0" + version: "2.4.1" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "2.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.2" + version: "1.1.3" cloud_firestore: dependency: "direct main" description: @@ -63,7 +63,7 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.11" + version: "1.14.12" convert: dependency: transitive description: @@ -77,7 +77,7 @@ packages: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.4" cupertino_icons: dependency: "direct main" description: @@ -183,7 +183,7 @@ packages: name: image url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.12" js: dependency: transitive description: @@ -205,6 +205,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.1.8" + nested: + dependency: transitive + description: + name: nested + url: "https://pub.dartlang.org" + source: hosted + version: "0.0.4" path: dependency: transitive description: @@ -261,13 +268,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.2" + provider: + dependency: "direct main" + description: + name: provider + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.5+1" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.0.5" + version: "2.1.3" + rxdart: + dependency: "direct main" + description: + name: rxdart + url: "https://pub.dartlang.org" + source: hosted + version: "0.24.0" sky_engine: dependency: transitive description: flutter @@ -279,7 +300,7 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.5.5" + version: "1.7.0" stack_trace: dependency: transitive description: @@ -314,7 +335,7 @@ packages: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.11" + version: "0.2.15" typed_data: dependency: transitive description: @@ -335,7 +356,7 @@ packages: name: xml url: "https://pub.dartlang.org" source: hosted - version: "3.5.0" + version: "3.6.1" sdks: dart: ">=2.7.0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 50c8fc6..d7f6636 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -28,6 +28,8 @@ dependencies: cloud_firestore: ^0.13.5 google_fonts: ^1.0.0 font_awesome_flutter: ^8.8.1 + rxdart: ^0.24.0 + provider: ^4.0.5+1 dev_dependencies: flutter_test: