LOGIN APP • FLUTTER - BLOC & FIREBASE Tutorial for Beginners 🔥

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
all right guys welcome back for this new video where today we are gonna create an entire authentication system using flutter block and Firebase so I know I've already created an entire Series where we were creating a insta X I don't know if you've seen all of those videos and I've covered uh Firebase authentication in this series but it was less popular on the channel and I see that this format you really guys enjoy so again I have my coffee on the left and we're just gonna code it uh together along and I hope you enjoy and it's a really important thing because I intend to do more let's say uh complexed projects that requires authentication and well in order for us to have the same base level uh to tackle those bigger projects I thought right so let's create a dedicated video for it and uh the next videos we can create I don't know a social media like recreate Instagram uh recreate your favorite apps you just tell me in the comments um for now I really urge you guys to subscribe if you like the channel and to leave a like under this video it really helps me out and uh yeah it really encourages me and you've been amazing uh in the comment section I've received so many messages of encouragement so I just wanted to like thank you again in person and uh and yeah well it's amazing the community we are creating together I'll shut up and we'll start uh building this uh this app so you see the design right here so the goal is to have this kind of a welcome screen where we can either sign in or sign up directly there okay with a little forget password screen so I've actually prepared a little bit so I've created the project so it's just a blanked flutter project so nothing really to it and for this video we'll need actually to create a new uh Firebase project so you need to navigate to your Firebase console and create a new project and basically we'll use Firebase authentication for this project and we'll as well use firestore database because it's not only a good thing to create the user in the authentication services but we want as well to store it inside our our database to be able to use it later on right and so we'll cover both in the in the same video uh it's really not difficult to understand really so yeah just I'm activating the two services and you see here I have a list of package that we'll need to use I mean it's a pretty long list but as well it's not that long so yeah so we have the firestore created and the authentication created as well so for the the authentication you need to navigate onto the sign in method and you'd see by default there is the anonymous uh provider that is enabled but we want to say email and password and enable that and save that perhaps if you want I've seen a comment asking me to cover the phone sign-in method perhaps I will cover that in a second video if I see in the comments that you want to see that that's no problem but for now we'll just keep it simple with email and password so that's pretty much it for the Firebase project I've already linked the Firebase project to my flutter project if you don't know how to do that there is a dedicated video on my channel to do just that so just go watch that and come back later on but basically you they give you the steps in Firebase and for iOS you just have to import this Google service Dash info dot list file inside there and that that's pretty much it so uh okay let's start with the project right so I think the first thing that we need to do is actually import the different dependencies that we'll need inside our perspect that yaml filed and create the app structure so the dependencies so if I open this package tab that I've created you'd see that we'll need Firebase core Firebase authentication Cloud firestore so far makes sense because those are the services that we've enabled in uh Firebase we as well need block flutter block Equitable which is uh working with block it just allows you to compare objects uh that's pretty cool with the double equal operator you see it says lag right here and then Cupertino icons as well so uh okay so that's the packages that we'll need so first let's create a let's create first the kind of app structure that we'll need so I like to have under the leap folder everything that's related to the UI as well as my blocks and I have my all the like backend type of stuff in a separated package so I want to create a new folder on my route and I call it packages okay I call that packages and under packages I'm going to create a new folder I'm going to call that user repository okay and I've had some requests onto a video to uh the app structure so for you guys that requested the app structure video that's the beginning of it so bear with me right here so under the user repository what I want to do actually is create a new file and a new perspect that yaml file right so we in the end have two perspective yaml file one under the user repository and our main one okay so inside our perspected yaml file I want to add a few different stuff so that's not test extension here but we'll call that user repository okay um the repo that handles users okay publish To None that's all right then we'll paste ourselves I can close that sorry we'll baste ourselves into the environment and all of this I can actually copy all that and I mean I can as well copy that up and I will paste that right here save that and normally that will generate some files well that's exactly what it said what it's doing perfect and now under this perspect the demo file under this user repository I actually want to create a few files so I want first to create a lead folder okay but that didn't work a folder leap folder okay so I have this sleep folder and under the Sleep folder I want to create a few things I want to create a new folder that it's called source and I want to create a user repository file user Repository Dot Dart okay so see this the structure we have the leap folder and under the leap folder I have a source folder and the user repository.dart okay don't concern yourself with what does those files are for right now I'll I'll cover that in just a minute so under the source folder I actually want to create a few stuff so I want to create the first folder that I'll call entities and I want to create another folder that called models okay so there is there are two folders entities and models okay and now I want to create two files the file that I'll call user repo.dart for example and another file I'll call Firebase user repo dot Dart okay so the file structure for this package is done okay so again if I Collapse everything under packages I have a user repository that has a perspective yaml file okay so it's its own perspective yaml file and that's pretty cool because in the user repository I might need some um like packages that I don't need inside my main perspective yaml file more front-end perspective yaml file so like for instance the Firebase authentication package I only need the package here in user repository because it's inside here that will create our classes that will create all the calls to the to the back end so yeah pretty cool so under this user repository I have a lib folder with a user repository.dart a source folder entities models Firebase user repository and user repository so under models I'll create two files the first file that I call models Dot art and another file that I'll call user.dart okay under entities I'll call entities are create entities dot darts and I will create a user entity dot Dart okay and that's pretty much it so this is where we're going to create our classes right our user classes side note your user class you want to call it my user or something different than user because the Firebase authentication class is called user so then you'd have some conflict you can modify it in your import but that's that's easier to just call it my user okay so now let's start actually to create those files themselves so perhaps we can start with the user class okay so let's start with the user class so it's going to be a very simple class okay so class my user okay and I'm gonna extend a quotable on that and I'm gonna need to import the package on the perspect that yaml file yes here okay let's navigate to the web and take the recordable I'm just going to copy that go back to the perspective yaml file and under dependencies save my Equitable package right there and just by saving it it will create a flutter prep get and I will be able to use it here okay so that is very big you can go now okay so a few parameters that we'll need for are my user class so first for and for instance every time you want to have an ID okay the ID of the user but it doesn't really matter the class that you're creating you always want to have the ID okay so I have the ID then I want to have another string not the E string it doesn't exist but a string that's going to be the email uh I want to have another string that's gonna be the name perhaps and I think that's it for this uh yeah that's it for this uh for this user and that's enough you can add different parameters if you want but for instance that's going to be enough okay so now let's create our Constructor okay the my user Constructor I like to uh make it like this so I know when I'm instantiating my class which parameter is what okay so you need to have the required keyword there as well uh it's because I'm using curly brackets if you're using curly brackets when you are calling the Constructor you will need to say user ID dot well you know column double dot is equal to that if you remove the curly braces you just pass the parameter along for the for the for the object that you want to create that makes it just a little bit more clear and it as well uh make them let's say um optional if you don't have the required parameter right here perhaps I'll go in more detail about all this let's just finish off this Constructor I'm already coding along with you guys like I could copy paste some code that I already have but I don't think it's uh it's really a good thing here so because we're extensiating uh Equitable we need these props method to override okay and this prop method is just waiting actually for um an array of the parameter of the class so I'm just going to give it to him uh right like this okay so under our Constructor a few things we want to say so we want to create an empty uh uh method okay so let's make that static constant and let's say empty is equal to my user and see that's exactly what I said earlier because my Constructor uses curly brake brackets I have those parameters and with the column and the actual parameter itself right if I didn't have those column those curly brackets here I wouldn't have that I would have just paste the the parameters that I want that I need but you know if you um if you're um like having a lot of like the same type of parameters that can be confusing at times see here it's all strings so if I pass first the email instead of passing the user ID it's not going to make any errors but it's actually going to break everything so yeah it's more clear so that's your goal in the end to be more clear when you're coding so yeah let's create a copy with method because we've said that all of our fields are final so because of of all of our fields are final we'll actually need to create a copy with method if we want to um well to modify those um those fields right so let me just take that up drink and here with the copy with method you want them to be potentially new right because well you're gonna see in the the method itself but we are returning my user right and here we're gonna say right so if inside the copy with a method was passed the user ID for instance I'm gonna get the take the user ID parameter but if the user ID is null well I'm gonna take the not the email but the user ID I already have in my object okay and we want to do that with every parameter using the this is referring to the current object that we have okay so yeah pretty straightforward that's done for the copy with method okay now we want to create another two methods that are going to be essential you could if you want create the from jeezin or from document and two document or two Json method inside your user class I like to separate them that's what the user entity is for I think it's just like more clear overall so here it's gonna be a my user entity okay it's going to be an error because we didn't created the uh my user entity class but we'll just do that after okay and uh here we're just gonna return my user entity so again that's okay if there is an error we didn't create it yet the class itself but we'll do that just after that and well basically we can take let me take just that and paste that right here and instead of having sorry up up up good so that's exactly what we want right there so my user entity and fit it with the parameters that we have perfect now we want a from entity okay so the two entity is going to convert the my user uh my user object to a myuser entity object okay and the from entity is going to convert my user entity object to a myuser object okay very simple and why would you want to convert your my user object to a my user entity object well for instance to send data to the database because you can't send objects to Firebase you need to send a map okay and the my user entity is going to do just that so now the last method as I told you from entity convert my user entity so it's going to take my user entity parameter as the parameter okay and it's going to return my user I hope you've said it that means that you are following along with me and I'm just not talking to myself so that's pretty good and here instead of just returning the parameters we want to access the entity that user ID email and name okay well that's pretty much it yeah that's it for the for the my user class so let's jump on to the user entity okay and we are going to create the user entity so class my user entity and uh here uh we'll need to create a few methods okay so uh let's extend that as well from recordable even though you don't need it but let's let's make it that way so it's consistent again up that's an array tell me in the comments if I'm moving too fast uh for you guys I hope I am not but yeah tell me in the comments if I'm moving too fast so the user entity is going to take the same parameters or of our my user class sorry and I can even take the Constructor of my user and paste it right here and just replace my user by my user entity okay and here I just want to add the class parameters so let me just do that email and name okay okay good so now here really straightforward I'm gonna create two methods and they are gonna be two documents and from document so a two document method well the goal is to return a map string okay object like this right to documents and what is it going to do well it's going to return the map obviously that's what it says just up there okay and now what what do we want we want our class parameters okay so exactly that sorry up trying to gain some times but often you try to gain some time and you are actually losing time yep like this okay so that's exactly what we want right here so we want to return a map of our parameter and this is what is going to go inside firestore okay inside our database because we can't send object right so we are just Transforming Our object onto a map okay very straightforward and now we want to be able to get the the the map from the database and transform it to my user entity object okay so let's create the from document method right so it's a my user entity from document and this takes a map yes string and you want to make that dynamic because right so a firestore is sending you some Dynamic stuff and uh well that just makes it a lot easier to say Dynamic you're gonna have types problem if you don't say that that's fine and then you want to return my user entity okay and after we have made it a little bit more beautiful uh here what do we want to say well it's not the user because we have a map right so we want to access the map itself and the user ID parameter from the map okay so let's copy that and paste that and apply it to every parameter like this save that and we're pretty much done yeah that's it okay so we have my user entity that's done that's taken care of and now inside the entities for file I want to export user entity.dart okay I want to do the same inside the models I'm going to export users a dart save that and here inside the user.tart I'm gonna import entities right here okay done we have our class parameters we have our user class parameter which is amazing right okay so we can close that close that that and that so now let's focus on to the uh user repository user repo first user repo right here so we have those two files right here user repo and Firebase user repo and you're gonna understand why because here is under user repo.dart I'm going to create an abstract class okay that I'm going to call perhaps user Repository okay and this class because it's abstract cannot be instantiated on its own right you need another class that is gonna implement this abstract user repository class okay so inside our leap folder in our blogs for instance we're going to require a user repository parameter right but we can't pass the user repository just like this we can't say that we will have to say another thing okay and we'll come to that later on sorry for uh the noise that my computer made so now I want to create a few methods uh so what do I want I want to create all that so I want to create a stream of user okay I want to create as well sign up so let's import models yeah so I want to create sign up set user data and sign in okay and as as well one stream of user and that's what I told you earlier it's here user I'm referring to the Firebase authentication class not the class that we've created so because I'm referring to the Firebase authentication class I need to import the package so Firebase authentication under the perspect that yaml file from the user repository okay I'm gonna import Firebase Authentication up and here I'm gonna import it that's it done I can save that so now for the user repository we can create a lot more like methods here like log out reset password get my user upload picture everything that's going to deal with the user it's going to be created here but you see here we are only creating uh like let's say this abstracts methods because we're not implementing those methods so we are going to implement those method under the Firebase user repo so let's create the Firebase user repo and this is gonna implement user Repository okay perfect and here you see there is an error why there is an error because we've created methods inside their user repo and we need to overwrite those methods by just clicking on that they have created our methods all of them okay so I'm just going to shuffle them around because I want that to be at the very top a stream of user be able to get our user set user data sign in sign up okay very straightforward so let me Shuffle that again I want to have the sign the set user data at the bottom okay like this perfect so those methods are we going to create them right now yes we are okay so let's first create our class parameters okay we're gonna need here a Firebase authentication parameter okay and we need then to create a Constructor for our class so let's say uh constant perhaps Firebase uh Firebase user repo and uh up like this and I'm gonna say Firebase oath so I'm requesting a Firebase oath uh parameter from my Constructor and here I'm gonna set our private if you do that it means that the parameter is private so it's only accessible inside this class right inside here that's what you want only here and I'm gonna set our Firebase authentication to the one that was passed uh to the one that was path uh directly inside the Constructor and if not I'm gonna say that it's equal to Firebase oath that instance perfect like this uh is there some kind of problem why aren't you happy invalid constant value my bad of course that's not constant okay perfect uh okay good so now we have this Constructor that is all done I want to create one more class parameter which is gonna be this uh Firebase firestore instance of collection okay our user collection okay so for that I need I'm gonna need to import Cloud firestore so up inside perspective yaml file under that cloud firestore okay perfect now that it's done uh I can import it directly here good and you can close yourself so yeah so it's imported right here so uh let's create a stream of user right first so let's create the stream of user so what do you want to do here so instead of throwing an error like a jackass let's create actually something that makes sense so we want to return a stream of user of Firebase authentication user not our user the Firebase oath user so for that we're just going to say return access our Firebase authentication user and access the method Odes State changes okay this is going to be triggered every time that there is a change in the user either the user is authenticated or it's not okay so here I'm gonna say a Firebase user so you have a better understanding of what it does okay and here up let me go to the line so we see good what do we want to do we want to find to just return return or Firebase user as simple as that so what this method does notifies about changes to the user sign-in State such as sign in or sign out so every time that the user is signing this stream is going to be triggered okay with a user okay and we're going to take this user and then ask our Cloud firestore to give us the real data that we have stored from the like account creation of the user okay so that's done for the stream of user now we want to create the sign in method very straightforward the sign in method you'll see very straightforward I like to wrap everything inside a try catch okay and log log the errors if there is a problem so you need to import that developer okay and if there is an error I'm gonna read through it because it's going to be catch inside our block later on so here it's actually because the future needs to be asynchronous right and here I want to say oh wait uh Firebase oath dot well sign in with email and password and pass the email and the password that I have inside here okay it's very straightforward and guess what it's done for the sign in method that was not too hard was it so now let's create the sign up method so the sign up method as well uh same deal okay try catch with the log and rethrow so let me just copy that and paste them and well it's going to be the same here it's going to be more user credentials okay user is equal to Firebase oath dot create user with email and password we really thanks the packages uh creators for the name of the methods that are really straightforward and here I want to say not email and password but here you see I'm having my my user object passed here so I'm gonna say my user dot email okay it's that because it's a weight okay so uh yeah create email uh create user with email and password okay passing in the email that I have stored onto my my user here and the password as well because I'm not storing the password inside the class itself I could but I won't so yeah then it's just disappearing and that's better for everyone I'm actually here returning in my user because I want to get and that's why I'm doing that right here when I'm signing up I want to get the user ID that I get from this user that's that these shoes are credential the ID is inside that so after that I want to say that my user okay is equal to my user.copy with and I want to access the user ID and say that is equal to user so this user right here okay user Dot user .u ID like this and I actually need that perfect and that's really uh that's really it perhaps I want to return my user okay and that's what we've created inside our my user my user class right to copy with method that takes the three parameters optional because the email I already have the name I already have this is going to be triggered when you click on the sign up okay I have already inside this object the email the name I don't have the user ID because the user ID comes from Firebase okay it comes it's here it's inside that okay so I pass inside the sign up method my user I get the ID from Firebase after having created my user I copy my my object my my user object with the right ID okay and I return the updated my user object okay very straightforward and because this is gonna be a changing of the status of the authentication of the user our string is going to be triggered and you're gonna be logged in okay very straightforward and the last method we want to use is the we want to create is the set user data I promise you after that we're done with the back end everything is created and we're going to create DUI so don't worry don't hate me but if you want to create apps guys the UI is a good thing it's pretty we can create a lot of stuff with with flutter but you'll need to spend a lot of time doing those back-end calls and I'd say you'd spend more time here than you'd actually spend on the back end on the front end Firebase authentication and the and and absolutely not we are creating set user data so sorry I am talking and I'm not thinking so I want to access our user collection a parameter remember the very top user collection is just referring our users collection inside our Firebase firestore or database this time and here I want to say dot document okay my user Dot why because it's called user here I'm going to keep it consistent otherwise I'm gonna lost you guys so it's my user.user ID dot set because I'm setting data okay and here I'm gonna say my user okay and well you you can you you might think right so I'm just giving my user here but here in the set method you'd see that it takes a map okay takes a map it doesn't take an object and we have here a my user object it's a my user object here but remember we have created some method we have creating the two entity method inside our my user class okay but still that's not it because this is a my user entity now okay this is a my we've converted our my user object into my user entity object but remember inside the my user entity we've created the two document method and if we access that that's exactly what we want because the two document is just creating a map so that's it straightforward uh and that's pretty much it okay you can save that and that's it for the Firebase user repository for now at least so what we can do is actually start working on to the app structure inside the leap folder so let me close all of them collapse it all and now let's navigate onto the leap folder so under the leap folder a few different things are going to happen Okay because right now which only have a main.dart file and I'm not gonna lie uh that's not very well separated so inside the r main.dart file I wanna I'm gonna go ahead and delete that okay so a few things we want to make sure uh so first we want to say uh to ensure that everything is well initialized okay and because we're using Firebase we want to say that Firebase is initialized but if we want to say that we actually need to make our main function a synchronous but we can't just call Firebase like this because this is from Firebase core so up again on the left Firebase core taking the dependency navigating back to the project and under our main perspective yaml file okay I'm gonna import Air Base core as easy as that and here I'm gonna import it that's it uh and then I'm gonna create a new file under the leap folder that I'm gonna call app.dart and because I'm shooting ahead I'm gonna create another file that it's called app view dot Dart okay we have all of our file for that so here under the app dot Dart I want to create a stateless widget that I'm going to call my app okay I'm Gonna Save that and under the main.dart I can actually call uh my app okay it's it's perfect uh yes perfect so my app right here so my app is gonna take a class parameter okay a widget parameter that's going to be a user repository so I need to import user repository but I can't well makes sense navigate to your perspect.yaml file and under the dependencies here I'm gonna actually import dependency that we have created so I'm gonna call that user underscore repository okay column and I'm gonna give it the path that it's packages slash user Repository save that he's happy he's happy he's not happy because Firebase oath depends on user repository from path which depends on is required uh what did I do so perhaps I saved that as well run pub.get can I import it now no there is a problem there is apparently a problem with Firebase Authentication uh uh yeah zero points let's make that zero one save that zero zero one and run pop get uh I will be back guys I'm just fixing that all right I've managed to make it work uh let don't ask me why but apparently inside your perspective yaml file under the user repository just comment the Firebase authentication run the perp get and then you can run the pub get inside your main perspective yaml fi and it will work so yeah don't ask me why it's just sometimes uh software development is kind of a mystery but we forgot I I saw that under our user repository we need to populate the user repository.dart file right so let's do that right now because we can't import anything if we didn't populate it this file so here we want to say library user Repository and here we want to export the important files so we want to export Source slash entities entities the dart export Source slash models models.dart we as well want to export The Source slash sorry uh user repo.dart and Export Source slash Firebase user repo save that and now inside our app.dart we can import user repository.tart yay okay good so I actually want to add the user repo to this Constructor okay save that so now I want to not give a placeholder here but I want to wrap the placeholder with a block provider okay block provider of type well I don't know if I go directly there or we create the block no yeah let's just stay like this and we'll actually create the block uh because first we need to create it but now let's navigate to our main.dart file and here we want to pass a Firebase Firebase user repo object okay perfect so now uh we are going to create the rest of the structure because we can't move forward if we don't have the rest of the structure so we are going to create under the leap folder there is now like two different kind of architecture to go for okay uh I'll go with the simpler one so let's create a folder that we're gonna call screens and let's create another folder that we're going to call blocks okay that's it so now it's the time where I actually want to create my box to create my blogs I'm gonna need to import the packages so the block package let me close the package here so under perspective yaml file I want to import the block I want to import flutter block okay and I want to import equordable okay like this and now that I'm with it yeah okay save that run up get close the perspect.aml file and here under my blog I'm gonna create uh I'm gonna create the authentication block okay so I'm going to create actually like four blocks okay so the first block and here I'm just right clicking on the folder and I have this block new block because I have the extension that it's called block this one okay you should clearly download this extension it will save you a lot of time because I just have to click on the Block folder and say new block I'm going to call that location press enter and see it's automatically creating an authentication block for me it's going to rename it authentication block so let's create all of them so again unblock new block I'm gonna create a sign in Block okay it disappeared it's here okay sign in Block let's rename that sign in Block I'm gonna create a new block that's gonna be called sign up okay sign up block here let's rename it sign up block uh and another block that we are gonna call my user okay so that's all of the blocks that we'll need for this authentication okay mainly these three and the my user block will be to feed the actual app with the the user data that we have so let's create the most important one that is going to be I mean they're all very important so let's create the authentication block perhaps first okay uh so what I like to do is open those three uh files and work my way uh one by one so first let's create the events so what we're gonna have here is just gonna be another class that's going to be called let's say Authentication user changed okay and this is gonna extend well if I can write good authentication event okay that's how block works and now here I just want to take a sorry create a class parameter that it's gonna be a user okay and here is a Firebase authentication user okay it's not our my user so what I want to do is actually import the Firebase auth Firebase authentication as well on my main perspect that yaml file right here so that's what I've done perfect what a package may not list itself as a dependency I don't understand why is there a problem with this package really breaking my ass because if I go back yeah it didn't work uh okay okay why not uh let me try to tweak this around okay I will be back all right I've discovered what happened and never happened to me because my name project was Firebase oath okay it cannot be as well inside the the the dependencies as a package right funny so I've just changed the name to Firebase oath YouTube and uh it worked wow I'm getting emails like crazy I am so sorry um so yeah uh that's done so you shouldn't have any problem if you just don't call your your uh project like a package or just rename it okay so I've actually imported the uh user here I'm gonna make it so it can be null and now I'm gonna create the Constructor so authentication user change or sound application user changed and I'm gonna pass this dot user okay perfect uh and I'm gonna as well create uh well no yeah that's it okay perfect so now let's create the state so the state e by default you get those kind of uh of Class by block okay and you have actually two ways to really handle the classes the state classes you can go like this but for this authentication I like to go with another model so I'm just gonna delete that and create first an enumeration okay that I'm going to call AutoNation stages and here I'm gonna say authenticated and unknown right these These are the stages that my app can be that the user can be in right it can either be authenticated unauthenticated or I don't know under that I'm gonna create a class that I'm gonna go up on vacation State make sense it's gonna extend equitable okay and here we have different uh class parameters we have one class parameter that is going to be a final authentication stages that we've created okay we're going to call that stages and we're going to have final user our Firebase authentication user okay so inside our Constructor we want to authentication State okay uh so let's use this typology right here we want to affect this dot stages to authenticationstages dot unknown sorry I don't know how to write unknown Perfect by default that's what we are creating right here with this um and then we're gonna say this dot user okay that's pretty much it uh okay good we have what we need now we are going to create the unknown authenticated and unauthenticated so constant authentication authentication state Dot unknown and this is gonna be returning our Constructor this is just going to return our Constructor right here and that's what it says uh now let's create the uh authenticated so authentication state DOT or dantikated perfect you English is not my mother tongue guys so uh be kind on me okay so uh the Firebase uh yeah Firebase authentication type of user and I'm just gonna call my Constructor right here private Constructor uh and pass the stages perhaps I can go here so you see better uh authentication stages it right here dot authenticated and I'm gonna give it to my user and user that we've passed right here okay uh okay good authenticated is done and now unauthenticated well we can just up take that and copy that and let's hear an authenticated uh we don't need a parameter for this because well we're unauthenticated and we don't need then the user well that's pretty much it we have an error because we are needing these props from a quotable in order to compare so that's the stages and that's the user that we need and that's it for the state okay let me show you again we have already different status right here as an enumeration and then we have our authentication State class really that's dealing with the unknown authenticated and unauthenticated okay perfect now let's create the block so now the block is where it's going to get a little bit complicated because well we've changed a lot of different stuff so before we deal with that let's create the class parameter so we need a user repository user Repository parameter we as well need a stream subscription why because remember in the back end we've created the getter forgetting or very based authentication user State okay and it was a string so we need to listen to that stream we're just going to say late because we're going to instantiate it inside uh the Constructor stream subscription and of type user okay that can be no as well and we're just going to call that user sub scription okay so for our class parameter that's good now the Constructor right here so here we are requiring our user reposit a user repository right but we can directly affect this to our user t-stop or user repo required the stuff no user repo yep that's it uh okay perfect and then here because we've changed the state class we need to say that it's not going to be that authentication States dot unknown perfect and here we want to uh said so unknown like this and let's let me delete that and here we're gonna say that the user subscription is equal to user Repository dot user that's the string that we've created okay the stream that we've created Dot listen up and here it's not like it's an event of type user okay so like I'm just going to call that user and we want to add a new event that's going to be authentication user change and pass it with the user okay that's it very straightforward so now we want to create those uh what's going to happen if this uh authentication user changes triggered right so here on authentication user change and what do we want to do well we want to say oh if events.user it is different than null we want to say what we want to emit a new state authentication state DOT authenticated and give him the events.user okay and if it's not null if it's no sorry we want to emit not authenticated but unauthenticated and that doesn't take any parameter we can make that a constant okay so that's perfect that's exactly what we need right here and perhaps I will call the close function as well uh right under that okay so we cancel the subscription when the block is closed what's exactly what we need so the authentication block is done as is and that's pretty cool now we are going to um perhaps create our app.dart okay so here app.dart wait I'll come back guys sorry I had to fly so uh up the dart file so here well we don't want to return a placeholder do we I've I've talked to you about the block provider so it's time to make it uh the blog provider is going to be authentication block okay so you need to precise the type right here and we need to import flutter block as well our authentication block takes a user repository right here and we're just going to give him what we have from our class parameter right here and we are not returning a placeholder we are actually going to return my app View my app view which is here but we need to create that so save that let me just close everything so we have app.dart and now let's create the app view.dart so let's create a status widget stateless widget my app View and here we are returning a material app because we need a material app okay the material app so the title for instance is going to be uh well uh Firebase oath yeah that work uh we're gonna go over the themes and all this later but perhaps just for now let's create home with a container okay that will be fine for now so inside our app.darts instead of returning a place order we are going to return my app View okay good good perfect so now why is it not happy oh yeah because I've changed the name sorry okay good uh let me run this let me run this perhaps I forgot some stuff on to uh the main I forgot some stuff no I just have this uh file that we can copy right here it's called simpleblock observer.dart is just when we have oncreate on event on change it's gonna show us in the log okay it's just this file so yeah I just you can post the video and actually write it yourself or it's in the GitHub if you want it but yeah I'm just gonna add that and so here in my main.dart file I need to instantiate that uh perhaps after that import it okay save it is everything running smoothly yes it is so that's good so what we want to do so the app first is going to trigger the main.dart file we're gonna do all this and then it's gonna go to my app so my app is that okay so it's gonna open this authentication block right which has a stream so it's going to all the time be because it's at the root of the app looking if our user is authenticated or not okay and return the app View the app view is right here okay and uh we need now to tell the the app view right so if I have a user uh if I if I'm a one screen and if I'm not authenticated I want you to return that screen so that's what we're gonna do in the home screen now so we are going to use a block Builder this time okay good and this block Builder is going to be of type authentication block authentication state so the Builder itself takes the context and says easy equal to and this is because we're using a quotable that we can do that okay is equal to authentication stages dot authenticated well I want to return uh I want to return the home screen okay return home screen that we didn't created but we'll create that production s will connect to you when uh my phone my emulator decide to build okay the app finally decided to build and as you can see we messed something up because we are in and we shouldn't be in because uh if it's so complicated we're in uh so let me check what what is wrong so and we're going to check together I told you guys I'm building along with you so it's really uh we're in this together uh let's take it from the beginning so we have our main.dart then we're going to our after Dart file which is fine here I'm giving him a Blog provider but actually I don't want a block provider I want a repository provider right here okay so the repository provider is going to provide this block throughout the entire uh app through the entire app so perhaps that was the problem I don't think it was but yeah ah something something is different I think I think it was just a matter of of rebuilding it welcome screen if I change the background colors to say colors.red no we have a different problem okay so now there is more problems because we are not actually triggering the block anymore okay um that's a problem with the block itself for sure so let's review our block let me close all that blocks authentication block event block and state okay let's start with the event so we have created our user our authentication user change that takes a user from Firebase authentication fair enough that's good then let's let's see our state so our state we have authentication stages okay that's fine authentication unauthenticated and unknown okay we have our parameters that are right here okay so perhaps if I move that because I know sometimes this can this can be a bit but I don't think it will change a thing but yeah let's see uh moved in at the bottom yeah change nothing um so the stages first is unknown that's fair okay then for the unknown that's good for the authenticated we are giving him that so that's fine uh and unauthenticated is ah yeah okay yeah makes more sense so now unauthenticated returns an authenticated State okay well better you might have caught that before me so here my question is why is this not the block is not triggered right here so let's review that so we have our class parameters that are right here uh my user repository okay so first the state is unknown that makes sense and then we subscribe to our user and listen to it and add the new authentication user change right there okay passing in the Firebase authentication user so that's going to trigger this part right authentication user change okay and here we are saying what we are saying if the user is different than null we return authenticated otherwise we return unauthenticated well that sounds pretty good to me so now the getter is the only thing that I see uh not working so uh we are expecting a stream of user type yes so far I agree uh Firebase authentication.aut States changes.map take our Firebase yes uh that should work that should work but I don't understand why it's not even triggered let me rebuild this perhaps that's the that's the answer let me investigate guys and I'll come back to you well sometime it's just a matter of rebuilding it because everything is working perfectly see in the welcome screen.dart we have a background that is red and so that works perfect and see here authentications the unknown now we are unauthenticated so we are in the right place so see sometimes when something is not working uh just close it and reopen it and well that worked because uh We've made it so uh it works so now let's go on to the like last part of the app which is actually uh building our uh welcome screen right here okay so a different kind of stuff remember we want to create something like this for a UI okay so to do just that we are gonna need a tab controller so under that let's create a tab controller I'm saying that it's gonna be initialized later because I'm gonna initialize it right now so a tab controller works sorry it works with [Music] um a tab controller and you need to say like how many tabs you're gonna have right so here we're gonna have two types so we need to say that okay so the length is going to be two uh we're gonna say that the initial index is going to be zero so we want to say that first we need we want to sign in uh and the vsync here I'm just going to say this and here I'm gonna say with ticker provider State mixing okay that's a thing you need to add in order for uh yeah well you can read the animation but for for the animation to work uh okay just save that it's not going to change the thing so now let's create the UI okay so we have our scaffold right here let me close that we have our scaffold so um before we do anything I'd like to create some yeah some colors yes yes yes let's go on to the app view here you have a parameter that's called theme okay theme and here we're going to say theme data that's going to be better for you guys because if you want to change some colors uh you'll be able to do it just here and then we want to access the color scheme parameter okay I'm just copying and pasting what I have and here I have added my own colors but you can add yours okay and then you can access those colors I'm going to show you how but that's pretty convenient because then yes well of course uh that's gonna make it very easy for us to instead of using those colors theme is going to do our work for us so the background color here I just want to say theme dot of context dot color scheme dot background and it's going to be white okay perfect so our scaffold let's deal with our scaffold so we are gonna have a single child scroll view as a child yeah child can't I have a child uh no body sorry yeah body single child scrolled view okay then I'm gonna say sized box as a child a size boxed that's going to take the height of uh the screen so mediaquery dot off context dot size dot height okay perfect and on top of that we're going to create this effect that I have going on right here so let's create this kind of effect so now I want a child and I want to Stack okay a stack takes some children parameters okay and for those children I'm gonna create first the first one so I'm gonna align it and access the Align parameter okay alignment parameter and I'm going to say that it's going to be alignment directional uh and here I'm going to say 20 and minus 1.2 I've already created the UI that's why I'm moving a bit faster on it but yeah and I know the coordinates that I need to put but yeah you can tweak it around if you want but you will see it makes more sense container a container a height that I'm gonna say that is equal to not that sorry the media query uh the width because I want to square and the width is going to be equal to the same exactly the same width okay perfect um now we want a decoration decoration okay so for containers it's going to be a box decoration okay uh I want to say that the shape is going to be a circle and the color is gonna be the ternary colors dot color scheme Dot ternary save that okay we have a big circle in our app and now you're gonna ask yourself how on Earth are you gonna make that look like this wait for it wait for it it's coming so we actually have three layers so we are going to copy that three times okay um but now they're just stuck on top of each other you can't see them so I'm just gonna squeeze around those uh parameters right here so first I want this one and then I want this one up okay good but now I want to tweak the size so the size is not going to be this width but it's going to be 1.3 divided by 1.3 okay and same for this one right there save that okay now the colors so the first one is the first one is a ternary the first the second one is secondary and the third one is going to be primary okay see what we have right here this primary this secondary and this is the ternary okay so one one two two three three okay perfect so now I want to add what's called a backdrop filter because I wanted this blur effect okay so backdrop filter up and I'm gonna filter it to a hundred percent so I'm gonna say here image filter dot blur and here I'm gonna say that the sigma X is going to be equal to 100 and the sigma Y is going to be equal to 100 as well and if I save that nothing's happening you know why because for the backdrop filter to work you actually need a child and here we're gonna say that it's gonna be a container okay and we're just going to say that the container is gonna be yeah just like this and just like this guys you have this wonderful effect that I really really like and I think it's really cool I think it's really really cool um yeah so that's the the thing done so now we are gonna deal with our Tap bar okay so under the backdrop filter I'm gonna align alignment again so same widget as this one here was custom but in the alignment widget you can as well take the alignment widget and you have some pre-presed down stuff and here I'm going to access the bottom center up let me go up so you can see more uh as a child I'm gonna do a side box the height is gonna be the media query dot of context dot size dot height and I'm going to divide that by 1.8 okay nothing is happening because there is no colors or anything but wait for it be patient it's coming um the child parameter of this size box is gonna be a column okay column takes the children parameter and here uh are gonna be our different stuff so first I want to be to do our tabs okay so I'm going to add some padding up this padding is going to be a edging set that symmetric horizontal 50. okay for the padding that's fine uh and now the child is going to be our tab bar tab tab bar okay good uh so the top bar takes a few parameters a controller remember we've created it it's called tab controller okay uh and it takes as well uh some well some colors here I'm gonna actually copy them because right unselected a label color okay so it's our theme of context that color scheme.background on background but I tune down the opacity of it basically it's just a light black and the label color so once one is selected uh it's gonna be black and the top bar widget actually takes some tabs which is right the Core Essence of it and to for for a little bit the process I'm just going to copy paste those things so we have to sign in and we have the sign up you've seen that already no need for me to go ahead and do it with you okay it's not happy wait second up why aren't you happy yeah I know what you why you're not happy okay because well if you use this kind of tab bar we need then to uh actually make the screen themselves um actually filled with something so here expanded because we wanted to fill up the remaining space okay and we are going to call the widget that's called tab bar View that bar review right here up Scroll up this tab bar view is taking the same controller of our uh our tab bar wow sorry what did I do controller the tap controller it takes and then of course it takes children's and here we're gonna have two screens but first before creating those screens I'm just gonna return two containers and that's gonna be fine okay let me try to rebuild that uh yes good we have our sign in and we have our sign up see that's working amazing okay good so now we are going to create actually our uh two uh sign in and sign up screen right to have those uh like on the UI those fields right there okay so under oath we're gonna create a new file and we're gonna call it sign in screen the dart and we're gonna create another file while we're at it sign up Green Dot Dart okay so those two files let's first create the sign in and then we'll create the sign up so the sign in file it's going to be a state tool we get I can tell you that much sign in screen okay so the sign in screen has quite a few parameters uh as uh um widget parameters I'm just going to copy paste them okay so you have let me work you through them a password controller an email controller that's for the two text field that we're going to create text form Fields a forum key controller for the Forum itself a Boolean to display a circular progress indicator instead of the button when you click on login an icon when you click on to the little icon on the password to see the password itself and some error message and stuff but that's that's that's okay for now so here we want to return a form okay and I'm not really really going in too much detail with the block and all this right now we'll go through that uh later so here we have a key and this is the Forum key that I have right here okay and now we have a column okay that takes some children and here because we are gonna have a few uh text Fields okay I want to actually create my own custom text field all right so under oath right here I'm going to create a new folder components okay and under this components folder I'm going to create my text field dot Dart okay my text field dot Dart and so my text field.dart what is it well it's a class that I'm gonna call my text field uh and it's gonna extend stateless widget like this perfect okay so quite a few parameters I want here so it's basically I'm just creating my my custom text field because I want to access different kind of parameters and I don't want to access all of the parameters from the text field so all of those parameters are text field parameters okay I just want to have them right here so I can either have them or not in the text field that I have okay so don't be like going crazy about it because that's really just straightforward and what do I want right here well I need to overwrite the build method now okay the build method right here and here what do we want to return well we want to return if you follow right you would have said it a text form field you want to I want to return a text form field and here I'm just gonna go ahead and copy paste what I have and it's just because it's the way I I like the design okay so I'm for each of those parameter giving it the class parameter that's already there okay that I've created and for the decoration I'm creating my own kind of decoration so it's the same everywhere okay and that allows me here in the sign in screen to be able to call my text field directly here so as a children I'm gonna say size box and here the width is going to be equal to Media query dot of context dot size dot with and I'm going to say that it's just gonna be 0.9 and here as a child I'm going to call my text field okay and my text field takes so many parameters but some of them are mandatory right okay so I'm just gonna go ahead and copy what I have and paste it right here and I'm gonna walk you through it okay so what do we have so for that's for the email okay so the validator we can come to that a bit later so uh yes it's be I was wondering why is it not uh actually doing anything here sign in screen inside our welcome screen instead of our container that will be better make that a constant okay refresh yeah yeah we have it right here okay so uh perhaps I want to uh I want to add a little size box at the top right here so we have a little bit of spacing okay good uh there is a problem with the icon but I've experienced that a few times I need to import actually manually now the personal icons if perhaps some of you knows what's happening just please tell me because I have no idea uh but perhaps it's meant that right now to import the the package itself Perhaps it is uh yeah now it works so what do we have my text field widget takes a controller right so it's this controller takes a hint text here is email okay we want to see the textbook keyboard input well it's going to be an email address so that the prefix icon we want one it's going to be this the email if there is an error message it's going to be this error message right here and the validator I want to validate the fact that it's actually an email so if the value is empty I want to say please fill in this field and if the value doesn't match this reject expression I want to say please enter a valid email and you're gonna ask me what is this reject expression well I'm going to show it to you right now up attack this is the regeck expression to check to check if this is an email okay I'm just going to save that okay that's good so we want to do kind of of the same thing for the password right so we're just gonna have to add a little size box as a divider and we're going to do the same for the password so I'm just going to take it because really it's just repeating code right here there is no really good value to it I'm just going to walk you through that in just a second so the password reject expression is a bit more complex this time because I'm actually making it a little different okay I'm gonna walk you through that so the width is the same if I save that okay the width is the same my text field controller this time is the password controller the hint text is password as you can see right here obscure text I want to see to pass the Obscure password that is uh this one okay the Boolean because if I click on this like by default I don't see it but if I click here I want to be able to see it okay and the icon icon changes okay so the keyboard type is going to be visible password the prefix icon is going to be the lock right here the message is going to be the message itself the validator if it's empty the same if it doesn't fit this long reject expression I want to return please enter a valid password and this project expression actually is just to check if your password is like eight characters long contains an uppercase a lowercase a number a special character and I think that's it but um yeah so far so good and then we have a sufix icon which is this okay and we have an icon button then which when we press it we are setting the True Value to this Optical text to true or to false okay and changing the icon itself right here okay very straightforward and the icon icon password perfect yeah fair enough well that's pretty much it for the sign in screen perhaps we need now to uh be able to actually type on the thing so let's create uh our button okay so the button is going to be well let me just add a little divider first under this size box right here okay and here I'm gonna utilize the sign-in required I'm just going to take that okay up and paste that here okay good so let me walk you through that I'm just copy pasting because we are already uh an hour and 28 minutes in and uh and we have more to do so it's just gonna be easier for now so if I save that you'd see we have a sign in button right here so I'm just giving a size box with the width that I want for my button okay if I change that it's going to be a larger button okay then as a child a text button with an unpressed method that we are going to create after we didn't create this block yet okay but first i'm checking if the Forum key dot current state is validate right because if I click right now it's not okay so uh because this is not the right valid password and this field is empty see our validator is were triggered okay so that and then it's just a matter of styling the button okay right there and as a child of the text button it's the text widget itself for the signing with some styling as well and let's say if the sign-in required is false okay I display the button if sign-in required is true I'm gonna return a circular progress indicator right here okay so now our sign in screen is done we want to do kind of the same thing for the sign up screen okay so what I propose you guys is I'm actually gonna gonna take this sign up screen and I'm gonna I'm gonna copy paste it and I'm gonna walk you through the code and you can go see on GitHub um the actual uh the actual code itself okay so here up deck uh yes sign and block the sign up block is not gonna work now but we can create that later on okay so up just two seconds yep so we have some errors because here we didn't create that block yet we will create it my text field we need to import and then we have another errors right here a special character regex yes uh it's this one let me just take that okay we have another errors right here password reject okay it's this and lastly email reject it's this one okay I'm Gonna Save that and here on to the welcome screen I'm just gonna say constant for now sign up screen okay let me reload everything I will walk you through that and then we have right here okay we have exactly what we want so let's go ahead and uh and start from the beginning so we have two uh controllers actually three controllers the email the password and for the name okay a form key okay for our form right here okay to see if it's validated that's so far is so good obscure password same thing with the button right here if the sign up is required this time okay same then we have a series of Boolean it's for those parameter because I found it useful for the user if he knows uh the kind of of password that he needs to input right and it's it's actually Dynamic here so you need all of those things but as as you are incrementing those you see that they are getting um that they are getting um a green okay so that's pretty cool yeah like this so what do we have then same same stuff for the signing a form with the column the first size box that deal with the email okay this then we have another size box with another my text field that deals with the password okay and here we have every time that the password change so the on change if it contains well not per case it's gonna set State the bull of Earth per case and put this one green okay so that's what this unchanged method is doing right there okay uh uh tactic okay okay okay then we have a few fix icon as well uh and here we have the row with all of our different text here and it's green if if the bull is Right otherwise it's black okay so that's what we have here okay pretty straightforward pretty straightforward and then we have the size box with the my text field for the name okay the name controller and then same logic we have our um our button that is going to actually um create the user with the sign up block okay same ideas really same ideas if you understood the sign in screen you'll understand the sign up screen it's a bit more complex but it's really straightforward so I hope you guys had the time to perhaps do some pose to the video in order for you to create the screen I'm just gonna go through it slowly the reject expression they are right here okay okay this reject expression as well okay but you can access all that through the GitHub okay so let's move on now because we are running out of time so uh the last thing that we need to do is actually create now the um the blocks the sign in block and the sign up block okay those are two very straightforward blocks and I want to create them fast so let's let's close let's close our UI sign up block event State and block up sign in and sign up first okay so the sign up block up we have the event that is going to be sign up required okay same same thing as always creating a new event sign up required okay just gonna import our user repository we take my user and we take a password makes sense so far okay now let's create our states so now it's not going to be the same kind of class as for the authentication we're just going to stay very simple and create three classes success failure and process okay process or loading whatever you want to call it save that up and now the sign of block itself well we're going to need a user repository as a class parameter okay we are going to do exactly that so we're going to require uh up required this user repository parameter and actually setting our user repository parameter to this user Repository okay perfect and then on sign up required not sign up event sign up required what do we want to do well we want to First emit sign up process okay and then into a try catch we want to say we want to say that if there is an error we want to sign up failures and then we want to access my user uh say that it's going to be my user user repository DOT sign up the method that we've already created and we fit the sign up method with the event.user and event.pass World okay they are here inside our event okay then we want to set the user data to Firebase firestore okay and then emit signup success okay save that for the sign up block that's all there is to it not much signing block even more simple so let's open all of them uh and let's go create those so sign in event sign in required okay now that's in the states sorry sign in event up sign in required email and password okay straightforward uh we perhaps as well wanna want to create now that we are at it sign out required okay um let's create the states so uh sign in success sign in failure and sign in progress up success failure failure we have a message okay and in progress we have a message from Firebase authentication right so either the password the password is wrong the email is wrong and we want to be able to display that to the user uh and sign-in process and while we are at it that's it up create the block now so the block same it's taking you see it's creating those kind of stuff can be really redundant if you know how to do it so yeah really created once good and take a template and then when you have project and that's more if you are start starting to do like some freelancing work and all this you always need to have some like user and and create these kind of authentication screen so I'll really recommend like having a template so you don't have to go through that every time you start the project because you're gonna get bored to hell so the sign-in required block sign require Block it's right here up straightforward uh first we emit sign-in process okay then try catch so if there is an errors on Firebase authentication exception okay we uh if there is a Firebase authentication problem we just take the message okay and display to the user otherwise it's just a simple sign-in failure and here inside the try catch we're accessing the sign in method we've created from the user repository and passing in the email and the password and if all goes good sign in success that's all there is to it while we are at it let's create the onsign out method perhaps yeah I didn't created the logout method perhaps we can create that very very fast it's really not a big method so you want to navigate to your Firebase user repository and at the end uh here let's create a method that's going to look something like this up log out straightforward log out save that and add that to the user repo right here up like this straightforward up up come back to the signing block and yeah now that works okay that's it for the signing block good so now we can actually uh see if all of those stuff are working but none of those things are gonna work if we are not passing uh those blocks to the different screen that we have so here on to the welcome screen we're just passing the sign in screen and the sign up screen but what we really want to be doing is actually passing up those two screens up block provider let me import sign in Block let me import flutter block and let me import signup block and don't say that this is constant and let me import it as well authentication block so what what are we doing here so this is for the sign in screen okay we're just wrapping it up with a block provider of the app signing block okay Opening Our signing block and the sign in Block need a user repository okay and we are accessing the user repository that we have into our authentication block so we don't create like 50 instances of Firebase authentic Firebase user repository okay we already have one in authentication block so let's just go ahead and use that and it's the exact same thing for the sign up screen okay exact same thing so if we hot reload hot restart everything and we actually try it out so let's see a ROM at gmail.com test1234 and name I'm gonna call myself ROM is there a problem up yeah of course there is a problem sign up screen of course there is one if you navigate down to the sign up screen to the button you actually need to uncomment this okay sign up and we are just context.reads of type signup block and adding a new event that's called sign up required okay and here remember at the very top of our sign up screen we had a Blog listener right at the very top I've deleted it but I'm going to recreate that a block listener and we're gonna add this okay good so our blood test under is going to be of type signup block okay and here what do we want to do well we want to say if uh uptake if the state is signed up success signup requires equal to false and we pop that screen okay otherwise uh uh yeah we pop that screen actually we don't need to pop anything because we yeah we don't need to pop anything so yeah that's it okay perfect so let me just up hot reload everything and see uh perhaps in our sign in screen right here yes we need to as well uncomment this method it's in the button right in the text button up sign in Block as well import flutter block we are we are adding the signing required event okay and passing it with the email controller.txt and the password controller.txt okay that's all there is to it okay so sign up ROM at gmail.com password test one two three four yep ROM well welcome you're in okay and if we go to Firebase authentication Let me refresh that we have a new user that has been created and if we go to firestore we have a new users class with the write ID and all the data right there right perfect that works amazing uh perhaps what we want to do is to um inside our app view here onto home screen is actually wrap that with a Blog provider and of type sign in Block yes sign in Block yep uh signing block takes a user repository that we access it in the context.read authentication block dot user repository okay it's not constant perfect and I'm doing all that because I just want to add on the home screen inside the app bar in the action sorry action I want to add the icon button and an unpress method to log out just so we can write the sign in method uh icon Cupertino icons dot out I need to import the package uh out I don't know what it's called perhaps just icons uh log out can we yeah look out that will work perfect and you need to actually say that it's an icon uh yes perfect and here I want to say uh context dot reads styling block dot add uh logout required I think log out I don't know how I called it signing block event sign out required okay sign up required okay let me rebuild everything and you see when we're rebuilding everything because we are logged in we are staying on the home screen if I click on the logout up I'm logged out and if I try to sign in let's see if that works home gmail.com test
Info
Channel: Romain Girou
Views: 25,749
Rating: undefined out of 5
Keywords:
Id: rgkaurIyso8
Channel Id: undefined
Length: 105min 45sec (6345 seconds)
Published: Wed Sep 27 2023
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.