Skip to content

Commit

Permalink
feat: implement Auth functionality (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
aman-singh7 committed Sep 26, 2021
1 parent f16f16e commit 75c3059
Show file tree
Hide file tree
Showing 22 changed files with 738 additions and 14 deletions.
3 changes: 2 additions & 1 deletion android/app/build.gradle
Expand Up @@ -22,6 +22,7 @@ if (flutterVersionName == null) {
}

apply plugin: 'com.android.application'
apply plugin: 'com.google.gms.google-services'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"

Expand All @@ -35,7 +36,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.borderhacks"
minSdkVersion 16
minSdkVersion 21
targetSdkVersion 30
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
1 change: 1 addition & 0 deletions android/build.gradle
Expand Up @@ -8,6 +8,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.google.gms:google-services:4.3.10'
}
}

Expand Down
16 changes: 16 additions & 0 deletions lib/locator.dart
@@ -1,11 +1,27 @@
import 'package:borderhacks/services/auth_service.dart';
import 'package:borderhacks/services/localstorage_service.dart';
import 'package:borderhacks/viewmodels/auth_viewmodel.dart';
import 'package:borderhacks/viewmodels/home_viewmodel.dart';
import 'package:borderhacks/viewmodels/landing_viewmodel.dart';
import 'package:borderhacks/viewmodels/profile_viewmodel.dart';
import 'package:borderhacks/viewmodels/startup_viewmodel.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:get_it/get_it.dart';

GetIt locator = GetIt.instance;

Future<void> setupLocator() async {
var localStorage = await LocalStorageService.getInstance();
locator.registerSingleton<LocalStorageService>(localStorage);
var _firebaseAuth = FirebaseAuth.instance;
locator.registerSingleton<FirebaseAuth>(_firebaseAuth);
var _firebaseFirestore = FirebaseFirestore.instance;
locator.registerSingleton<FirebaseFirestore>(_firebaseFirestore);
locator.registerSingleton<FirebaseAuthService>(FirebaseAuthService());

locator.registerFactory<StartUpViewModel>(() => StartUpViewModel());
locator.registerFactory<AuthViewModel>(() => AuthViewModel());
locator.registerFactory<HomeViewModel>(() => HomeViewModel());
locator.registerFactory<LandingViewModel>(() => LandingViewModel());
locator.registerFactory<ProfileViewmodel>(() => ProfileViewmodel());
Expand Down
30 changes: 23 additions & 7 deletions lib/main.dart
@@ -1,22 +1,38 @@
import 'package:borderhacks/locator.dart';
import 'package:borderhacks/views/landing_view.dart';
import 'package:borderhacks/ui/views/auth_view.dart';
import 'package:borderhacks/ui/views/landing_view.dart';
import 'package:borderhacks/ui/views/startup_view.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
await setupLocator();
runApp(MyApp());
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LandingView(),
title: 'Admin App',
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => StartUpView()),
GetPage(
name: '/login',
page: () => const AuthView('Log in', 'Sign Up'),
),
GetPage(
name: '/signup',
page: () => const AuthView('Sign Up', 'Log In'),
),
GetPage(name: '/landing', page: () => LandingView()),
],
home: StartUpView(),
);
}
}
57 changes: 57 additions & 0 deletions lib/services/auth_service.dart
@@ -0,0 +1,57 @@
import 'package:borderhacks/locator.dart';
import 'package:borderhacks/ui/constants/error_code.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';

class FirebaseAuthService {
final FirebaseAuth _firebaseAuth = locator<FirebaseAuth>();

User get currentUser => _firebaseAuth.currentUser!;

// Sign In with email and password
Future<UserCredential> signIn(String email, String password) async {
try {
return await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
throw signInErrorCodes[e.code] ?? 'Database Error Occured!';
} catch (e) {
throw '${e.toString()} Error Occured!';
}
}

// Sign Up using email address
Future<UserCredential> signUp(String email, String password) async {
try {
var _user = await _firebaseAuth.createUserWithEmailAndPassword(
email: email,
password: password,
);
return _user;
} on FirebaseAuthException catch (e) {
throw signUpErrorCodes[e.code] ?? 'Firebase ${e.code} Error Occured!';
} catch (e) {
throw '${e.toString()} Error Occured!';
}
}

// Sign Out
Future<String> signOut() async {
await _firebaseAuth.signOut();
return 'Signed Out Successfully';
}

Future<String> sendResetPasswordEmail(String email) async {
try {
await _firebaseAuth.sendPasswordResetEmail(email: email);
return 'Email Sent Succesfully';
} on FirebaseException catch (e) {
throw passwordResetErrorCodes[e.code] ??
'Firebase ${e.code} Error Occured!';
} catch (e) {
throw '${e.toString()} Error Occured!';
}
}
}
42 changes: 42 additions & 0 deletions lib/services/localstorage_service.dart
@@ -0,0 +1,42 @@
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';

class LocalStorageService {
static late LocalStorageService _instance;
static late SharedPreferences _preferences;

static const String authStateKey = 'is_LoggedIn';

static Future<LocalStorageService> getInstance() async {
_instance = LocalStorageService();
_preferences = await SharedPreferences.getInstance();
return _instance;
}

dynamic _getFromDisk(String key) {
var value = _preferences.get(key);
return value;
}

void _saveToDisk<T>(String key, T value) {
if (value is bool) {
_preferences.setBool(key, value);
} else if (value is double) {
_preferences.setDouble(key, value);
} else if (value is int) {
_preferences.setInt(key, value);
} else if (value is String) {
_preferences.setString(key, value);
} else if (value is List<String>) {
_preferences.setStringList(key, value);
} else {
debugPrint('passed value is none of the specified type');
}
}

//Getter
bool get isLoggedIn => _getFromDisk(authStateKey) ?? false;

//Setter
set isLoggedIn(bool isLoggedIn) => _saveToDisk(authStateKey, isLoggedIn);
}
35 changes: 35 additions & 0 deletions lib/ui/components/buttons/primary_button.dart
@@ -0,0 +1,35 @@
import 'package:flutter/material.dart';

class PrimaryButton extends StatelessWidget {
final VoidCallback press;
final String text;
final bool isFilled;

const PrimaryButton({
required this.press,
required this.text,
required this.isFilled,
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialButton(
padding: const EdgeInsets.all(12.0),
color: isFilled ? Colors.blue : Colors.white,
minWidth: double.infinity,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(9.0),
side: const BorderSide(color: Colors.blue),
),
onPressed: press,
child: Text(
text,
style: TextStyle(
fontSize: 18,
color: isFilled ? Colors.white : Colors.blue,
),
),
);
}
}
62 changes: 62 additions & 0 deletions lib/ui/components/text_field/custom_textfield.dart
@@ -0,0 +1,62 @@
import 'package:flutter/material.dart';

class CustomTextField extends StatefulWidget {
final TextEditingController controller;
final String label;
final String? hint;
final Icon? prefix;
final String? Function(String?)? _validator;
final IconButton? _suffix;
final int? _maxlines;
final bool _isHidden;

const CustomTextField(
{required this.controller,
required this.label,
this.hint,
this.prefix,
IconButton? suffix,
bool isHidden = false,
int maxlines = 1,
Key? key,
String? Function(String?)? validator})
: _suffix = suffix,
_maxlines = maxlines,
_isHidden = isHidden,
_validator = validator,
super(key: key);

@override
_CustomTextFieldState createState() => _CustomTextFieldState();
}

class _CustomTextFieldState extends State<CustomTextField> {
@override
Widget build(BuildContext context) {
return TextFormField(
obscureText: widget._isHidden,
controller: widget.controller,
validator: widget._validator,
maxLines: widget._maxlines,
style: const TextStyle(fontSize: 18.0),
decoration: InputDecoration(
filled: true,
fillColor: Colors.grey[100],
focusColor: Colors.white,
labelText: widget.label,
hintText: widget.hint,
labelStyle: const TextStyle(
fontSize: 18.0,
),
focusedBorder: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.blue)),
border: const OutlineInputBorder(
borderRadius: BorderRadius.all(Radius.circular(10.0)),
borderSide: BorderSide(color: Colors.grey),
),
prefixIcon: widget.prefix,
suffixIcon: widget._suffix),
);
}
}
29 changes: 29 additions & 0 deletions lib/ui/components/text_field/email_textfield.dart
@@ -0,0 +1,29 @@
import 'package:borderhacks/ui/components/text_field/custom_textfield.dart';
import 'package:flutter/material.dart';

class EmailTextField extends StatelessWidget {
const EmailTextField(
{required TextEditingController emailController, Key? key})
: _emailController = emailController,
super(key: key);

final TextEditingController _emailController;

String? validateUsernameField(String? value) {
if (value!.isEmpty) {
return 'Required Field';
}
return null;
}

@override
Widget build(BuildContext context) {
return CustomTextField(
controller: _emailController,
label: 'Username',
prefix: const Icon(Icons.person),
hint: 'Enter the Username',
validator: validateUsernameField,
);
}
}
48 changes: 48 additions & 0 deletions lib/ui/components/text_field/password_textfield.dart
@@ -0,0 +1,48 @@
import 'package:borderhacks/ui/components/text_field/custom_textfield.dart';
import 'package:flutter/material.dart';

class PasswordTextField extends StatefulWidget {
const PasswordTextField({
required TextEditingController passwordController,
Key? key,
}) : _passwordController = passwordController,
super(key: key);

final TextEditingController _passwordController;

@override
_PasswordTextFieldState createState() => _PasswordTextFieldState();
}

class _PasswordTextFieldState extends State<PasswordTextField> {
bool _isHidden = true;

String? validatePasswordField(String? value) {
if (value!.isEmpty) {
return 'Required Field';
}
return null;
}

@override
Widget build(BuildContext context) {
return CustomTextField(
controller: widget._passwordController,
label: 'Password',
prefix: const Icon(Icons.lock),
hint: 'Enter the Password',
validator: validatePasswordField,
isHidden: _isHidden,
suffix: IconButton(
icon: _isHidden
? const Icon(Icons.visibility_off)
: const Icon(Icons.visibility),
onPressed: () {
setState(() {
_isHidden = !_isHidden;
});
},
),
);
}
}
14 changes: 14 additions & 0 deletions lib/ui/constants/error_code.dart
@@ -0,0 +1,14 @@
const signInErrorCodes = <String, String>{
'user-not-found': 'User not Found',
'wrong-password': 'Either eamil or password is wrong',
};

const signUpErrorCodes = <String, String>{
'weak-password': 'The password is too weak',
'email-already-in-use': 'An account already exists for this email',
};

const passwordResetErrorCodes = <String, String>{
'auth/invalid-email': 'Enter a valid email',
'auth/user-not-found': 'User not found',
};

0 comments on commit 75c3059

Please sign in to comment.