Skip to content

Instantly share code, notes, and snippets.

@plotsklapps
Last active October 3, 2023 09:04
Show Gist options
  • Select an option

  • Save plotsklapps/6ef33145d76f284122dcc5e5011c8aa5 to your computer and use it in GitHub Desktop.

Select an option

Save plotsklapps/6ef33145d76f284122dcc5e5011c8aa5 to your computer and use it in GitHub Desktop.
Flutter Fullstack #3

Flutter Fullstack #3

Created with <3 with dartpad.dev.

import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: // For Firebase JS SDK v7.20.0 and later, measurementId is optional
const FirebaseOptions(
apiKey: "AIzaSyB1mTcVNqXCMLviCjb4NZ35l0r2u6jwv0s",
authDomain: "codelab-flutterbankapp.firebaseapp.com",
projectId: "codelab-flutterbankapp",
storageBucket: "codelab-flutterbankapp.appspot.com",
messagingSenderId: "1005151447135",
appId: "1:1005151447135:web:663e127a0473fe49a0d3ea",
measurementId: "G-MH8JWFX1LF"),
);
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) {
return LoginService();
})
],
child: const FlutterBankApp(),
),
);
}
class FlutterBankApp extends StatelessWidget {
const FlutterBankApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
textTheme: GoogleFonts.poppinsTextTheme(
Theme.of(context).textTheme,
),
),
home: const FlutterBankSplash(),
);
}
}
class FlutterBankSplash extends StatelessWidget {
const FlutterBankSplash({super.key});
@override
Widget build(BuildContext context) {
// Future.delayed takes two parameters: a Duration object with is seconds
// property set to 2, and a callback. When the 2 seconds have ellapsed, it will call the callback. The callback has inside a trigger to perform a navigation
Future.delayed(const Duration(seconds: 2), () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return const FlutterBankLogin();
}),
);
});
return const Scaffold(
backgroundColor: Utils.mainThemeColor,
body: Stack(
children: [
Center(
child: Icon(
Icons.savings,
size: 60.0,
color: Colors.white,
),
),
Center(
child: SizedBox(
height: 100.0,
width: 100.0,
child: CircularProgressIndicator(
strokeWidth: 8.0,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)),
],
),
);
}
}
class FlutterBankRegister extends StatefulWidget {
const FlutterBankRegister({super.key});
@override
State<FlutterBankRegister> createState() => _FlutterBankRegisterState();
}
class _FlutterBankRegisterState extends State<FlutterBankRegister> {
TextEditingController emailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
TextEditingController confirmPasswordController = TextEditingController();
@override
void dispose() {
// Dispose of the emailController and passwordController instances when the
// widget is disposed of.
emailController.dispose();
passwordController.dispose();
confirmPasswordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
LoginService loginService = Provider.of<LoginService>(
context,
listen: false,
);
bool validateFormFields() {
return Utils.validateEmail(emailController.text) &&
emailController.text.isNotEmpty &&
passwordController.text.isNotEmpty &&
confirmPasswordController.text.isNotEmpty &&
(passwordController.text == confirmPasswordController.text);
}
return SafeArea(
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
elevation: 0.0,
iconTheme: const IconThemeData(color: Utils.mainThemeColor),
backgroundColor: Colors.transparent,
title: const Icon(
Icons.savings,
color: Utils.mainThemeColor,
size: 40.0,
),
centerTitle: true,
),
body: Container(
padding: const EdgeInsets.all(30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(bottom: 40.0),
child: const Text(
'Create New Account',
style: TextStyle(
color: Utils.mainThemeColor,
fontSize: 20.0,
),
),
),
const SizedBox(height: 40.0),
Utils.generateInputField(
'Email Address', Icons.email, emailController, false,
(text) {
setState(() {});
}),
Utils.generateInputField(
'Password', Icons.lock, passwordController, true,
(text) {
setState(() {});
}),
Utils.generateInputField('Confirm Password', Icons.lock,
confirmPasswordController, true, (text) {
setState(() {});
}),
],
),
),
FlutterBankMainButton(
label: 'Register',
enabled: validateFormFields(),
onTap: () async {
String email = emailController.value.text;
String password = passwordController.value.text;
bool isAccountCreated = await loginService
.createUserWithEmailAndPassword(email, password);
if (isAccountCreated) {
if (mounted) Navigator.of(context).pop();
}
},
),
],
),
),
),
);
}
}
class FlutterBankLogin extends StatefulWidget {
const FlutterBankLogin({super.key});
@override
State<FlutterBankLogin> createState() {
return _FlutterBankLoginState();
}
}
class _FlutterBankLoginState extends State<FlutterBankLogin> {
// At the top of the FlutterBankLoginState class, add two
// TextEditingController instances, one for each of our fields. The
// TextEditingController allows us to controller the actions on a TextField
// widget, get notifications on text field updates, set initial values,
// get its provided input, etc.
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
validateEmailAndPassword() {
return emailController.text.isNotEmpty &&
passwordController.text.isNotEmpty &&
Utils.validateEmail(emailController.text);
}
@override
void dispose() {
// Dispose of the emailController and passwordController instances when the
// widget is disposed of.
emailController.dispose();
passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// Create an instance of the LoginService service, fetching by via the
// Provider.of factory method, passing the BuildContext and the
// listen: false flag so it is a one-time fetch and our widget doesn't
// rebuild all the time.
LoginService loginService = Provider.of<LoginService>(
context,
listen: false,
);
return SafeArea(
child: Scaffold(
body: Container(
padding: const EdgeInsets.all(30.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
width: 80.0,
height: 80.0,
decoration: BoxDecoration(
border: Border.all(
color: Utils.mainThemeColor,
width: 7.0,
),
borderRadius: BorderRadius.circular(100.0),
),
child: const Icon(
Icons.savings,
size: 45.0,
color: Utils.mainThemeColor,
),
),
const SizedBox(height: 30.0),
const Text(
'Welcome to',
style: TextStyle(
color: Colors.grey,
fontSize: 15.0,
),
),
const Text(
'Flutter\nSavings Bank',
style: TextStyle(
color: Utils.mainThemeColor,
fontSize: 30.0,
),
),
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'Sign Into Your Bank Account',
style: TextStyle(
color: Colors.grey,
fontSize: 12.0,
),
textAlign: TextAlign.center,
),
const SizedBox(height: 12.0),
Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.2),
borderRadius: BorderRadius.circular(50.0),
),
child: TextField(
onChanged: (text) {
setState(() {
// We use setState to update the state of the widget. In this case,
// we are updating the state of the emailController.
});
},
controller: emailController,
decoration: const InputDecoration(
prefixIcon: Icon(
Icons.email,
color: Utils.mainThemeColor,
),
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding:
EdgeInsets.fromLTRB(15.0, 11.0, 15.0, 11.0),
hintText: 'Email Address',
),
style: const TextStyle(
fontSize: 16.0,
),
),
),
const SizedBox(height: 16.0),
Container(
padding: const EdgeInsets.all(5.0),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.2),
borderRadius: BorderRadius.circular(50.0),
),
child: TextField(
onChanged: (text) {
setState(() {
// We use setState to update the state of the widget. In this case,
// we are updating the state of the passwordController.
});
},
controller: passwordController,
obscureText: true,
decoration: const InputDecoration(
prefixIcon: Icon(
Icons.lock,
color: Utils.mainThemeColor,
),
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding:
EdgeInsets.fromLTRB(20.0, 11.0, 15.0, 11.0),
hintText: 'Password',
),
style: const TextStyle(
fontSize: 16.0,
),
),
),
Consumer<LoginService>(
builder: (context, loginService, child) {
String errorMessage = loginService.getErrorMessage();
if (errorMessage.isEmpty) {
return const SizedBox(height: 40.0);
} else {
return Container(
padding: const EdgeInsets.all(10.0),
child: Row(
children: [
const Icon(
Icons.warning,
color: Colors.red,
),
const SizedBox(width: 10.0),
Expanded(
child: Text(
errorMessage,
style: const TextStyle(
color: Colors.red,
),
),
),
],
),
);
}
},
),
],
),
),
),
FlutterBankMainButton(
label: 'Sign In',
enabled: validateEmailAndPassword(),
onTap: () async {
String email = emailController.text;
String password = passwordController.text;
bool isLoggedIn =
await loginService.signInWithEmailAndPassword(
email,
password,
);
if (isLoggedIn) {
emailController.clear();
passwordController.clear();
if (mounted) {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return const FlutterBankMain();
}),
);
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Invalid credentials'),
behavior: SnackBarBehavior.floating,
),
);
}
}
},
),
const SizedBox(height: 16.0),
FlutterBankMainButton(
label: 'Register',
icon: Icons.account_circle,
enabled: true,
onTap: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
return const FlutterBankRegister();
}));
},
backgroundColor: Utils.mainThemeColor.withOpacity(0.1),
iconColor: Utils.mainThemeColor,
labelColor: Utils.mainThemeColor,
),
],
),
),
),
);
}
}
class FlutterBankMain extends StatelessWidget {
const FlutterBankMain({super.key});
@override
Widget build(BuildContext context) {
return const SafeArea(
child: Scaffold(
body: Center(
child: Text('Main Page'),
),
),
);
}
}
// Widgets
class FlutterBankMainButton extends StatelessWidget {
final Function? onTap;
final String? label;
final bool? enabled;
final IconData? icon;
final Color? backgroundColor;
final Color? iconColor;
final Color? labelColor;
const FlutterBankMainButton({
super.key,
this.onTap,
this.label,
this.enabled = true,
this.icon,
this.backgroundColor = Utils.mainThemeColor,
this.iconColor = Colors.white,
this.labelColor = Colors.white,
});
@override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(50.0),
child: Material(
color:
enabled! ? backgroundColor : backgroundColor!.withOpacity(0.5),
child: InkWell(
// Assign a callback that wraps the trigger this widget's onTap event only
// if the enabled property is true, null otherwise.
onTap: enabled!
? () {
onTap!();
}
: null,
highlightColor: Colors.white.withOpacity(0.2),
splashColor: Colors.white.withOpacity(0.1),
child: Container(
padding: const EdgeInsets.all(15.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(50.0),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Visibility(
visible: icon != null,
child: Container(
margin: const EdgeInsets.only(right: 20.0),
child: Icon(
icon,
color: iconColor,
size: 20.0,
),
)),
Text(
label!,
textAlign: TextAlign.center,
style: TextStyle(
color: labelColor,
fontWeight: FontWeight.bold,
),
),
],
),
),
),
),
),
],
);
}
}
class Utils {
static const Color mainThemeColor = Color(0xFF8700C3);
static bool validateEmail(String? value) {
String pattern =
r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]"
r"{0,253}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]"
r"{0,253}[a-zA-Z0-9])?)*$";
RegExp regex = RegExp(pattern);
return (value != null || value!.isNotEmpty || regex.hasMatch(value));
}
static Widget generateInputField(
String hintText,
IconData iconData,
TextEditingController textEditingController,
bool isPasswordField,
Function onChanged,
) {
return Container(
padding: const EdgeInsets.all(5.0),
margin: const EdgeInsets.only(bottom: 20.0),
decoration: BoxDecoration(
color: Colors.grey.withOpacity(0.2),
borderRadius: BorderRadius.circular(50.0),
),
child: TextField(
onChanged: (text) {
onChanged(text);
},
controller: textEditingController,
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.lock,
color: Utils.mainThemeColor,
),
border: InputBorder.none,
focusedBorder: InputBorder.none,
enabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
disabledBorder: InputBorder.none,
contentPadding: const EdgeInsets.fromLTRB(20.0, 11.0, 15.0, 11.0),
hintText: hintText,
),
obscureText: isPasswordField,
obscuringCharacter: '*',
style: const TextStyle(
fontSize: 16.0,
),
),
);
}
}
class LoginService extends ChangeNotifier {
String userId = '';
String errorMessage = '';
String getUserId() {
return userId;
}
String getErrorMessage() {
return errorMessage;
}
void setLoginErrorMessage(String message) {
errorMessage = message;
notifyListeners();
}
void setSignUpErrorMessage(String message) {
errorMessage = message;
notifyListeners();
}
Future<bool> createUserWithEmailAndPassword(
String email, String password) async {
setLoginErrorMessage('');
try {
UserCredential userCredential =
await FirebaseAuth.instance.createUserWithEmailAndPassword(
email: email,
password: password,
);
userId = userCredential.user!.uid;
return true;
} on FirebaseAuthException catch (exc) {
setSignUpErrorMessage(
'There was an error during sign-up: ${exc.message}');
return false;
}
}
Future<bool> signInWithEmailAndPassword(String email, String password) async {
setLoginErrorMessage('');
try {
UserCredential userCredential =
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
userId = userCredential.user!.uid;
return true;
} on FirebaseAuthException catch (exc) {
setLoginErrorMessage('There was an error during sign-in: ${exc.message}');
return false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment