diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.gitignore b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.gitignore new file mode 100644 index 0000000..0fa6b67 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.gitignore @@ -0,0 +1,46 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.metadata b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.metadata new file mode 100644 index 0000000..56bfc2c --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 + channel: stable + +project_type: app diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/README.md b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/README.md new file mode 100644 index 0000000..5432a3b --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/README.md @@ -0,0 +1,8 @@ +# IoT Firestore Flutter APP +https://youtu.be/nsopdabOcug + +# My Channel +www.that-project.com + +# Flutter UI Design +https://github.com/tonydavidx/signin-register-page-ui-flutter \ No newline at end of file diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/back_arrow.png b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/back_arrow.png new file mode 100644 index 0000000..c6c1a12 Binary files /dev/null and b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/back_arrow.png differ diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/humidity_icon.png b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/humidity_icon.png new file mode 100644 index 0000000..5488bbf Binary files /dev/null and b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/humidity_icon.png differ diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/iot_image.png b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/iot_image.png new file mode 100644 index 0000000..d002fc4 Binary files /dev/null and b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/iot_image.png differ diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/temperature_icon.png b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/temperature_icon.png new file mode 100644 index 0000000..0be096b Binary files /dev/null and b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/temperature_icon.png differ diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/auth_helper.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/auth_helper.dart new file mode 100644 index 0000000..a333e0b --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/auth_helper.dart @@ -0,0 +1,50 @@ +import 'dart:async'; + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; + +class AuthHelper { + static final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; + + static Future signUp(String email, String password) async { + try { + UserCredential userCredential = await _firebaseAuth + .createUserWithEmailAndPassword(email: email, password: password); + + return userCredential.user; + } on FirebaseAuthException catch (e) { + return e.message; + } + } + + static Future signIn(String email, String password) async { + try { + UserCredential userCredential = await FirebaseAuth.instance + .signInWithEmailAndPassword(email: email, password: password); + + return userCredential.user; + } on FirebaseAuthException catch (e) { + return e.message; + } + } + + static Future signOut() async { + await _firebaseAuth.signOut(); + } + + static Future resetPassword(String email) async { + await _firebaseAuth.sendPasswordResetEmail(email: email); + } + + static Future initializeFirebase({ + required BuildContext context, + }) async { + FirebaseApp firebaseApp = await Firebase.initializeApp(); + return firebaseApp; + } + + static User? currentUser() { + return FirebaseAuth.instance.currentUser; + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_colors.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_colors.dart new file mode 100644 index 0000000..aa34624 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_colors.dart @@ -0,0 +1,6 @@ +import 'package:flutter/material.dart'; + +// Colors +const kMainBG = Color(0xff191720); +const kTextFieldFill = Color(0xff1E1C24); +const kCardBG = Color(0xffffffff); diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_styles.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_styles.dart new file mode 100644 index 0000000..5f77d70 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_styles.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +const kHeadline = TextStyle( + color: Colors.white, + fontSize: 34, + fontWeight: FontWeight.bold, +); + +const kBodyText = TextStyle( + color: Colors.grey, + fontSize: 15, +); + +const kButtonText = TextStyle( + color: Colors.black87, + fontSize: 16, + fontWeight: FontWeight.bold, +); + +const kBodyText2 = +TextStyle(fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white); \ No newline at end of file diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/main.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/main.dart new file mode 100644 index 0000000..65fbdda --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/main.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:iot_firestore_flutter_app/route/router.dart' as router; +import 'package:iot_firestore_flutter_app/route/routing_constants.dart'; + +import 'const/custom_colors.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]) + .then((_) { + runApp(new MyApp()); + }); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'IoT Firestore App', + theme: ThemeData( + textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme), + scaffoldBackgroundColor: kMainBG, + primarySwatch: Colors.blue, + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + onGenerateRoute: router.generateRoute, + initialRoute: SplashScreenRoute, + ); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/model/sensor.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/model/sensor.dart new file mode 100644 index 0000000..3d9b83c --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/model/sensor.dart @@ -0,0 +1,25 @@ +import 'package:flutter/foundation.dart'; + +@immutable +class Sensor { + Sensor({ + required this.humidity, + required this.temperature, + }); + + Sensor.fromJson(Map json) + : this( + humidity: json['humidity']! as double, + temperature: json['temperature']! as double, + ); + + final double humidity; + final double temperature; + + Map toJson() { + return { + 'humidity': humidity, + 'temperature': temperature, + }; + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/router.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/router.dart new file mode 100644 index 0000000..05be6eb --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/router.dart @@ -0,0 +1,29 @@ +import 'package:iot_firestore_flutter_app/route/routing_constants.dart'; +import 'package:iot_firestore_flutter_app/screens/dashboard_screen.dart'; +import 'package:iot_firestore_flutter_app/screens/signin_screen.dart'; +import 'package:iot_firestore_flutter_app/screens/signup_screen.dart'; +import 'package:iot_firestore_flutter_app/screens/splash_screen.dart'; +import 'package:iot_firestore_flutter_app/screens/undefined_screen.dart'; +import 'package:flutter/material.dart'; + +Route generateRoute(RouteSettings settings) { + switch (settings.name) { + case SplashScreenRoute: + return MaterialPageRoute(builder: (context) => SplashScreen()); + + case SignInScreenRoute: + return MaterialPageRoute(builder: (context) => SignInScreen()); + + case SignUpScreenRoute: + return MaterialPageRoute(builder: (context) => SignUpScreen()); + + case DashboardScreenRoute: + return MaterialPageRoute(builder: (context) => DashboardScreen()); + + default: + return MaterialPageRoute( + builder: (context) => UndefinedView( + name: settings.name!, + )); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/routing_constants.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/routing_constants.dart new file mode 100644 index 0000000..f3c9036 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/routing_constants.dart @@ -0,0 +1,4 @@ +const String SplashScreenRoute = '/'; +const String SignInScreenRoute = '/SignIn'; +const String SignUpScreenRoute = '/SignUp'; +const String DashboardScreenRoute = '/Dashboard'; diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/dashboard_screen.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/dashboard_screen.dart new file mode 100644 index 0000000..345ade5 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/dashboard_screen.dart @@ -0,0 +1,159 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:iot_firestore_flutter_app/auth_helper.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; +import 'package:iot_firestore_flutter_app/model/sensor.dart'; +import 'package:iot_firestore_flutter_app/route/routing_constants.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_sensor_card.dart'; +import 'package:flutter/material.dart'; + +class DashboardScreen extends StatefulWidget { + const DashboardScreen({Key? key}) : super(key: key); + + @override + _DashboardScreenState createState() => _DashboardScreenState(); +} + +class _DashboardScreenState extends State { + List? tempList; + List? rhList; + + static String collectionName = 'House'; + final sensorRef = FirebaseFirestore.instance + .collection(collectionName) + .withConverter( + fromFirestore: (snapshots, _) => Sensor.fromJson(snapshots.data()!), + toFirestore: (movie, _) => movie.toJson(), + ); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: StreamBuilder>( + stream: sensorRef.snapshots(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(snapshot.error.toString()), + ); + } + + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + + final data = snapshot.requireData; + + if (tempList == null) { + tempList = List.filled(5, data.docs.first.data().temperature, + growable: true); + } else { + tempList!.add(data.docs.first.data().temperature); + tempList!.removeAt(0); + } + + if (rhList == null) { + rhList = + List.filled(5, data.docs.first.data().humidity, growable: true); + } else { + rhList!.add(data.docs.first.data().humidity); + rhList!.removeAt(0); + } + + return Padding( + padding: + const EdgeInsets.only(left: 16, right: 16, top: 40, bottom: 30), + child: CustomScrollView(slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Column( + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + collectionName, + style: kHeadline, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + data.docs.first.id, + style: kHeadline, + ), + ), + SizedBox( + height: 30, + ), + Center( + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + MySensorCard( + value: data.docs.first.data().humidity, + unit: '%', + name: 'Humidity', + assetImage: AssetImage( + 'assets/images/humidity_icon.png', + ), + trendData: rhList!, + linePoint: Colors.blueAccent, + ), + SizedBox( + height: 20, + ), + MySensorCard( + value: data.docs.first.data().temperature, + unit: '\'C', + name: 'Temperature', + assetImage: AssetImage( + 'assets/images/temperature_icon.png', + ), + trendData: tempList!, + linePoint: Colors.redAccent, + ) + ], + ), + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Sign out of Firebase? ", + style: kBodyText, + ), + GestureDetector( + onTap: _signOut, + child: Text( + "Sign Out", + style: kBodyText.copyWith( + color: Colors.white, + ), + ), + ), + ], + ), + SizedBox( + height: 20, + ), + ], + ), + ), + ]), + ); + }, + )); + } + + _signOut() async { + await AuthHelper.signOut(); + Navigator.pushNamedAndRemoveUntil( + context, SplashScreenRoute, (Route route) => false); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signin_screen.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signin_screen.dart new file mode 100644 index 0000000..8790df3 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signin_screen.dart @@ -0,0 +1,148 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; +import 'package:iot_firestore_flutter_app/route/routing_constants.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_password_field.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_text_button.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_text_field.dart'; +import 'package:flutter/material.dart'; + +import '../auth_helper.dart'; + +class SignInScreen extends StatefulWidget { + const SignInScreen({Key? key}) : super(key: key); + + @override + _SignInScreenState createState() => _SignInScreenState(); +} + +class _SignInScreenState extends State { + bool isPasswordVisible = true; + + final TextEditingController _email = TextEditingController(); + final TextEditingController _password = TextEditingController(); + + @override + void dispose() { + _email.dispose(); + _password.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Padding( + padding: + const EdgeInsets.only(left: 16, right: 16, top: 60, bottom: 30), + child: CustomScrollView( + reverse: true, + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Flexible( + fit: FlexFit.loose, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Welcome back.", + style: kHeadline, + ), + SizedBox( + height: 10, + ), + Text( + "You've been missed!", + style: kBodyText2, + ), + SizedBox( + height: 60, + ), + MyTextField( + textEditingController: _email, + hintText: 'Email', + inputType: TextInputType.emailAddress, + ), + MyPasswordField( + hintText: 'Password', + textEditingController: _password, + isPasswordVisible: isPasswordVisible, + onTap: () { + setState(() { + isPasswordVisible = !isPasswordVisible; + }); + }, + ), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Dont't have an account? ", + style: kBodyText, + ), + GestureDetector( + onTap: () { + Navigator.pushNamed(context, SignUpScreenRoute); + }, + child: Text( + 'Register', + style: kBodyText.copyWith( + color: Colors.white, + ), + ), + ) + ], + ), + SizedBox( + height: 20, + ), + MyTextButton( + buttonName: 'Sign In', + onTap: _signIn, + bgColor: Colors.white, + textColor: Colors.black87, + ), + ], + ), + ), + ), + ], + ), + ), + ); + } + + _signIn() async { + var email = _email.text.trim(); + var pw = _password.text.trim(); + + if (email.isEmpty || pw.isEmpty) { + await showOkAlertDialog( + context: context, + message: 'Check your email or password', + ); + return; + } + + var obj = await AuthHelper.signIn(email, pw); + + if (obj is User) { + Navigator.pushNamedAndRemoveUntil( + context, DashboardScreenRoute, (Route route) => false); + } else { + await showOkAlertDialog( + context: context, + message: obj, + ); + } + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signup_screen.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signup_screen.dart new file mode 100644 index 0000000..e8a5f77 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signup_screen.dart @@ -0,0 +1,169 @@ +import 'package:adaptive_dialog/adaptive_dialog.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:iot_firestore_flutter_app/auth_helper.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; +import 'package:iot_firestore_flutter_app/route/routing_constants.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_password_field.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_text_button.dart'; +import 'package:iot_firestore_flutter_app/widgets/my_text_field.dart'; +import 'package:flutter/material.dart'; + +class SignUpScreen extends StatefulWidget { + const SignUpScreen({Key? key}) : super(key: key); + + @override + _SignUpScreenState createState() => _SignUpScreenState(); +} + +class _SignUpScreenState extends State { + bool passwordVisibility = true; + + final TextEditingController _email = TextEditingController(); + final TextEditingController _password = TextEditingController(); + final TextEditingController _passwordConfirm = TextEditingController(); + + @override + void dispose() { + _email.dispose(); + _password.dispose(); + _passwordConfirm.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Padding( + padding: + const EdgeInsets.only(left: 16, right: 16, top: 40, bottom: 30), + child: CustomScrollView( + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + child: Column( + children: [ + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + IconButton( + onPressed: () { + Navigator.pop(context); + }, + icon: Image( + width: 24, + color: Colors.white, + image: AssetImage('assets/images/back_arrow.png'), + ), + ), + Text( + "Register", + style: kHeadline, + ), + Text( + "Create new account to get started.", + style: kBodyText2, + ), + SizedBox( + height: 50, + ), + MyTextField( + hintText: 'Email', + inputType: TextInputType.emailAddress, + textEditingController: _email, + ), + MyPasswordField( + hintText: 'Password', + textEditingController: _password, + isPasswordVisible: passwordVisibility, + onTap: () { + setState(() { + passwordVisibility = !passwordVisibility; + }); + }, + ), + MyPasswordField( + hintText: 'Password Confirm', + textEditingController: _passwordConfirm, + isPasswordVisible: passwordVisibility, + onTap: () { + setState(() { + passwordVisibility = !passwordVisibility; + }); + }, + ) + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Already have an account? ", + style: kBodyText, + ), + GestureDetector( + onTap: () { + Navigator.pushNamedAndRemoveUntil( + context, + SignInScreenRoute, + (Route route) => false); + }, + child: Text( + "Sign In", + style: kBodyText.copyWith( + color: Colors.white, + ), + ), + ), + ], + ), + SizedBox( + height: 20, + ), + MyTextButton( + buttonName: 'Register', + onTap: _signUp, + bgColor: Colors.white, + textColor: Colors.black87, + ) + ], + ), + ), + ), + ], + ), + ), + ); + } + + _signUp() async { + var email = _email.text.trim(); + var pw = _password.text.trim(); + var pwConfirm = _passwordConfirm.text.trim(); + + if (email.isEmpty || pw.isEmpty || pw != pwConfirm) { + await showOkAlertDialog( + context: context, + message: 'Check your email or password', + ); + return; + } + + var obj = await AuthHelper.signUp(email, pw); + + if (obj is User) { + Navigator.pushNamedAndRemoveUntil( + context, DashboardScreenRoute, (Route route) => false); + } else { + await showOkAlertDialog( + context: context, + message: obj, + ); + } + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/splash_screen.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/splash_screen.dart new file mode 100644 index 0000000..85bb09f --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/splash_screen.dart @@ -0,0 +1,103 @@ +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; +import 'package:iot_firestore_flutter_app/route/routing_constants.dart'; +import 'package:flutter/material.dart'; + +import '../auth_helper.dart'; + +class SplashScreen extends StatelessWidget { + const SplashScreen({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: FutureBuilder( + future: AuthHelper.initializeFirebase(context: context), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + User? user = AuthHelper.currentUser(); + if (user != null) { + Future.delayed(Duration.zero, () async { + Navigator.pushNamedAndRemoveUntil(context, DashboardScreenRoute, + (Route route) => false); + }); + } else { + return _getScreen(context); + } + } + return Center( + child: CircularProgressIndicator(), + ); + }, + )); + } + + _getScreen(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 50), + child: Column( + children: [ + Flexible( + child: Column( + children: [ + Center( + child: Container( + child: Image( + image: AssetImage( + 'assets/images/iot_image.png', + ), + color: Colors.white, + ), + ), + ), + SizedBox( + height: 20, + ), + Text( + "Firebase\nCloud Firestore", + style: kHeadline, + textAlign: TextAlign.left, + ), + SizedBox( + height: 20, + ), + Container( + width: MediaQuery.of(context).size.width * 0.8, + child: Text( + "This is a project that connects to hardware devices via Firebase and gets the readings from sensors. For detail, you can check out my channel.", + style: kBodyText, + textAlign: TextAlign.center, + ), + ) + ], + ), + ), + Container( + height: 60, + width: MediaQuery.of(context).size.width * 0.8, + decoration: BoxDecoration( + color: Colors.grey[850], + borderRadius: BorderRadius.circular(18), + ), + child: Container( + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith( + (states) => Colors.black12, + ), + ), + onPressed: () { + Navigator.pushNamedAndRemoveUntil(context, + SignInScreenRoute, (Route route) => false); + }, + child: Text( + 'GET STARTED', + style: kButtonText.copyWith(color: Colors.white), + ), + ), + )) + ], + ), + ); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/undefined_screen.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/undefined_screen.dart new file mode 100644 index 0000000..2aafa49 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/undefined_screen.dart @@ -0,0 +1,16 @@ +import 'package:flutter/material.dart'; + +class UndefinedView extends StatelessWidget { + final String name; + + const UndefinedView({Key? key, required this.name}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Text('Route for $name is not defined'), + ), + ); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_password_field.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_password_field.dart new file mode 100755 index 0000000..e9d7799 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_password_field.dart @@ -0,0 +1,65 @@ +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; +import 'package:flutter/material.dart'; + +class MyPasswordField extends StatelessWidget { + const MyPasswordField( + {Key? key, + required this.hintText, + required this.isPasswordVisible, + required this.onTap, + required this.textEditingController}) + : super(key: key); + final String hintText; + final bool isPasswordVisible; + final Function onTap; + final TextEditingController textEditingController; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TextField( + controller: textEditingController, + style: kBodyText.copyWith( + color: Colors.white, + ), + obscureText: isPasswordVisible, + keyboardType: TextInputType.text, + textInputAction: TextInputAction.done, + decoration: InputDecoration( + suffixIcon: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: IconButton( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onPressed: () { + onTap(); + }, + icon: Icon( + isPasswordVisible ? Icons.visibility : Icons.visibility_off, + color: Colors.grey, + ), + ), + ), + contentPadding: EdgeInsets.all(20), + hintText: hintText, + hintStyle: kBodyText, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + borderRadius: BorderRadius.circular(18), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1, + ), + borderRadius: BorderRadius.circular(18), + ), + ), + ), + ); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_sensor_card.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_sensor_card.dart new file mode 100644 index 0000000..3ee9b95 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_sensor_card.dart @@ -0,0 +1,83 @@ +import 'package:chart_sparkline/chart_sparkline.dart'; +import 'package:flutter/material.dart'; +import 'package:iot_firestore_flutter_app/const/custom_colors.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; + +class MySensorCard extends StatelessWidget { + const MySensorCard( + {Key? key, + required this.value, + required this.name, + required this.assetImage, + required this.unit, + required this.trendData, + required this.linePoint}) + : super(key: key); + + final double value; + final String name; + final String unit; + final List trendData; + final Color linePoint; + final AssetImage assetImage; + + @override + Widget build(BuildContext context) { + return Card( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(18), + ), + shadowColor: Colors.white, + elevation: 24, + color: kMainBG, + child: Container( + width: MediaQuery.of(context).size.width * 0.8, + height: 200, + child: Row( + children: [ + Expanded( + flex: 1, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image( + width: 60, + image: assetImage, + ), + SizedBox( + height: 10, + ), + Text(name, style: kBodyText.copyWith(color: Colors.white)), + SizedBox( + height: 10, + ), + Text('$value$unit', + style: kHeadline.copyWith(color: Colors.white)), + ], + ), + ), + Expanded( + flex: 1, + child: Container( + padding: + const EdgeInsets.symmetric(vertical: 30, horizontal: 8), + child: Sparkline( + data: trendData, + lineWidth: 5.0, + lineColor: Colors.white, + averageLine: true, + fillMode: FillMode.above, + sharpCorners: false, + pointsMode: PointsMode.last, + pointSize: 20, + pointColor: linePoint, + useCubicSmoothing: true, + ), + ), + ), + ], + ), + )); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_button.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_button.dart new file mode 100755 index 0000000..967d042 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_button.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; + +class MyTextButton extends StatelessWidget { + const MyTextButton({ + Key? key, + required this.buttonName, + required this.onTap, + required this.bgColor, + required this.textColor, + }) : super(key: key); + final String buttonName; + final Function onTap; + final Color bgColor; + final Color textColor; + + @override + Widget build(BuildContext context) { + return Container( + height: 60, + width: double.infinity, + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(18), + ), + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateProperty.resolveWith( + (states) => Colors.black12, + ), + ), + onPressed: () { + onTap(); + }, + child: Text( + buttonName, + style: kButtonText.copyWith(color: textColor), + ), + ), + ); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_field.dart b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_field.dart new file mode 100755 index 0000000..fc356d7 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_field.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; +import 'package:iot_firestore_flutter_app/const/custom_styles.dart'; + +class MyTextField extends StatelessWidget { + const MyTextField( + {Key? key, + required this.hintText, + required this.inputType, + required this.textEditingController}) + : super(key: key); + final String hintText; + final TextInputType inputType; + final TextEditingController textEditingController; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: TextField( + controller: textEditingController, + style: kBodyText.copyWith(color: Colors.white), + keyboardType: inputType, + textInputAction: TextInputAction.next, + decoration: InputDecoration( + contentPadding: EdgeInsets.all(20), + hintText: hintText, + hintStyle: kBodyText, + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1, + ), + borderRadius: BorderRadius.circular(18), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.white, + width: 1, + ), + borderRadius: BorderRadius.circular(18), + ), + ), + ), + ); + } +} diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.lock b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.lock new file mode 100644 index 0000000..3ccd05d --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.lock @@ -0,0 +1,383 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + adaptive_dialog: + dependency: "direct main" + description: + name: adaptive_dialog + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + animations: + dependency: transitive + description: + name: animations + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.6.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + characters: + dependency: transitive + description: + name: characters + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + chart_sparkline: + dependency: "direct main" + description: + name: chart_sparkline + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + clock: + dependency: transitive + description: + name: clock + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + url: "https://pub.dartlang.org" + source: hosted + version: "2.5.1" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "5.4.1" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.4.1" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.15.0" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + fake_async: + dependency: transitive + description: + name: fake_async + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.2" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.1" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "9.1.0" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.3" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" + intl: + dependency: transitive + description: + name: intl + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.3" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.10" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" + path_provider: + dependency: transitive + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.3" + pedantic: + dependency: transitive + description: + name: pedantic + url: "https://pub.dartlang.org" + source: hosted + version: "1.11.1" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.10.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.2.0" + test_api: + dependency: transitive + description: + name: test_api + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.0" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.7" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" +sdks: + dart: ">=2.13.0 <3.0.0" + flutter: ">=2.0.0" diff --git a/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.yaml b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.yaml new file mode 100644 index 0000000..acde509 --- /dev/null +++ b/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.yaml @@ -0,0 +1,85 @@ +name: iot_firestore_flutter_app +description: www.that-project.com + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.12.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + firebase_auth: ^3.1.0 + firebase_core: ^1.6.0 + cloud_firestore: ^2.5.1 + google_fonts: ^2.1.0 + adaptive_dialog: ^1.1.0 + font_awesome_flutter: ^9.1.0 + chart_sparkline: ^1.0.5 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + assets: + - assets/images/ + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/README.md b/README.md index 3a25f90..0281869 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ Thank you. * [Youtube ESP32 Project](https://www.youtube.com/playlist?list=PLnq7JUnBumAyhSBBp95MsQ5-chBAYheZw) +* [IoT | Cloud Firestore - Ep 4. Firebase Client Flutter App for iOS and Android][[Video]](https://youtu.be/nsopdabOcug)[[Source Code]](https://github.com/0015/ThatProject/tree/master/FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app) + * [Send an SMS from ESP32 (ft. Twilio)][[Video]](https://youtu.be/SP4pvYCQAfc)[[Source Code]](https://github.com/0015/ThatProject/tree/master/MESSAGE/Twilio/0_ESP32TTGO_FIRESTORE_SMS) * [ESP32 | Cloud Firestore - Ep 3. Status Icons on Display (The End)][[Video]](https://youtu.be/LR_FgObfuCw)[[Source Code]](https://github.com/0015/ThatProject/tree/master/FIREBASE/Cloud_Firestore_Application/3_Display_Done)