From 6ca8c510bed094a695fe0c05ea5f5572d0ceccae Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 29 Aug 2021 23:26:28 -0700 Subject: [PATCH] IoT | Cloud Firestore - Ep 4. Firebase Client Flutter App for iOS and Android --- .../4_iot_firestore_flutter_app/.gitignore | 46 +++ .../4_iot_firestore_flutter_app/.metadata | 10 + .../4_iot_firestore_flutter_app/README.md | 8 + .../assets/images/back_arrow.png | Bin 0 -> 1705 bytes .../assets/images/humidity_icon.png | Bin 0 -> 20472 bytes .../assets/images/iot_image.png | Bin 0 -> 30451 bytes .../assets/images/temperature_icon.png | Bin 0 -> 5495 bytes .../lib/auth_helper.dart | 50 +++ .../lib/const/custom_colors.dart | 6 + .../lib/const/custom_styles.dart | 21 + .../4_iot_firestore_flutter_app/lib/main.dart | 33 ++ .../lib/model/sensor.dart | 25 ++ .../lib/route/router.dart | 29 ++ .../lib/route/routing_constants.dart | 4 + .../lib/screens/dashboard_screen.dart | 159 ++++++++ .../lib/screens/signin_screen.dart | 148 +++++++ .../lib/screens/signup_screen.dart | 169 ++++++++ .../lib/screens/splash_screen.dart | 103 +++++ .../lib/screens/undefined_screen.dart | 16 + .../lib/widgets/my_password_field.dart | 65 +++ .../lib/widgets/my_sensor_card.dart | 83 ++++ .../lib/widgets/my_text_button.dart | 42 ++ .../lib/widgets/my_text_field.dart | 46 +++ .../4_iot_firestore_flutter_app/pubspec.lock | 383 ++++++++++++++++++ .../4_iot_firestore_flutter_app/pubspec.yaml | 85 ++++ README.md | 2 + 26 files changed, 1533 insertions(+) create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.gitignore create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/.metadata create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/README.md create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/back_arrow.png create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/humidity_icon.png create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/iot_image.png create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/assets/images/temperature_icon.png create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/auth_helper.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_colors.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/const/custom_styles.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/main.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/model/sensor.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/router.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/route/routing_constants.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/dashboard_screen.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signin_screen.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/signup_screen.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/splash_screen.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/screens/undefined_screen.dart create mode 100755 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_password_field.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_sensor_card.dart create mode 100755 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_button.dart create mode 100755 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/lib/widgets/my_text_field.dart create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.lock create mode 100644 FIREBASE/Cloud_Firestore_Application/4_iot_firestore_flutter_app/pubspec.yaml 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 0000000000000000000000000000000000000000..c6c1a1235ffd880f99719455203f90dd0d130c2a GIT binary patch literal 1705 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 C7dIRL literal 0 HcmV?d00001 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)