diff --git a/packages/wyatt_authentication_bloc/.gitignore b/packages/wyatt_authentication_bloc/.gitignore new file mode 100644 index 00000000..23d156c3 --- /dev/null +++ b/packages/wyatt_authentication_bloc/.gitignore @@ -0,0 +1,31 @@ +# 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 +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ +.flutter-plugins* +coverage/ \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/.metadata b/packages/wyatt_authentication_bloc/.metadata new file mode 100644 index 00000000..af84dae5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/.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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + channel: stable + +project_type: package diff --git a/packages/wyatt_authentication_bloc/AUTHORS b/packages/wyatt_authentication_bloc/AUTHORS new file mode 100644 index 00000000..b6d7d765 --- /dev/null +++ b/packages/wyatt_authentication_bloc/AUTHORS @@ -0,0 +1,7 @@ +# Below is a list of people and organizations that have contributed +# to this project. Names should be added to the list like so: +# +# Name/Organization + +Wyatt Group S.A.S +Hugo Pointcheval \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/CHANGELOG.md b/packages/wyatt_authentication_bloc/CHANGELOG.md new file mode 100644 index 00000000..65b374a8 --- /dev/null +++ b/packages/wyatt_authentication_bloc/CHANGELOG.md @@ -0,0 +1,25 @@ +## 0.1.0 + +Custom states + +- Add SignUpState customization with FormData +- Add UserData in AuthenticationState +- Start/Stop AuthenticationCubit on demand + +## 0.0.2 + +Add some Sugar. + +- Add AuthenticationBuilder +- Add isNewUser +- Replace AuthenticationBloc by AuthenticationCubit +- Update firebase_auth + +## 0.0.1 + +Initial release + +- New repo: Firebase Auth + * [sign up] email/password + * [sign in] email/password +- Form Validator diff --git a/packages/wyatt_authentication_bloc/LICENSE b/packages/wyatt_authentication_bloc/LICENSE new file mode 100644 index 00000000..e72bfdda --- /dev/null +++ b/packages/wyatt_authentication_bloc/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/README.md b/packages/wyatt_authentication_bloc/README.md new file mode 100644 index 00000000..0eead7cd --- /dev/null +++ b/packages/wyatt_authentication_bloc/README.md @@ -0,0 +1,303 @@ + + +# Flutter - Authentication BLoC + +[![style: wyatt analysis](https://img.shields.io/badge/Style-Wyatt%20Analysis-blue)](https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt_analysis) + +Authentication Bloc for Flutter. + +## Features + +- UserInterface + * UserFirebase : FirebaseAuth user implementation +- AuthenticationRepositoryInterface + * AuthenticationRepositoryFirebase : FirebaseAuth implementation +- ExceptionsInterface + * ExceptionsFirebase : FirebaseAuth Exception parsing implementation +- AuthenticationBloc + * Tracks every user changes + - Right after the listener has been registered. + - When a user is signed in. + - When the current user is signed out. + - When there is a change in the current user's token. + - On `refresh()` + * Start/Stop listening on demand + - `start()` to listen to user changes + - `stop()` to cancel listener +- Form + * FormInput: validation of one input + * Form: validation of all form inputs + * FormEntry: contains data of one input + * FormData: contains all entries +- SignUpCubit + * Handles email/password validation and password confirmation + * Handles register with email/password + * Handles custom form fields + - Use `entries` to pass a `FormData` object + - You can use several pre configured `FormInput` for validation + - You can use `updateFormData()` to change FormData and validators during runtime (intersection, union, difference or replace) +- SignInCubit + * Handles email/password validation + * Handles login with email/password +- EmailVerificationCubit + * Handles send email verification process + * Handles email verification check +- PasswordResetCubit + * Handles send password reset email process +- Builders + * AuthenticationBuilder to build widgets on user state changes +- Consistent + * Every class have same naming convention +- Tested + * Partially tested with *bloc_test* + +## Getting started + +Simply add `wyatt_authentication_bloc` in `pubspec.yaml`, then + +```dart +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +``` + +## Usage + +Create an authentication repository: + +```dart +final AuthenticationRepositoryInterface _authenticationRepository = AuthenticationRepositoryFirebase(); +``` + +Create an authentication cubit: + +```dart +final AuthenticationCubit _authenticationCubit = AuthenticationCubit( + authenticationRepository: _authenticationRepository, +); +``` + +Create a sign up cubit: + +```dart +final SignUpCubit _signUpCubit = SignUpCubit( + authenticationRepository: _authenticationRepository, + authenticationCubit: _authenticationCubit, +); +``` + +You can use `AuthenticationBloc` to route your app. + +```dart +return MultiRepositoryProvider( + providers: [ + RepositoryProvider( + create: (context) => _authenticationRepository, + ), + ], + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => _authenticationCubit..init(), + ), + BlocProvider( + create: (context) => _signUpCubit, + ), + ], + child: const AppView(), + ), +); +``` +> Don't forget to call `init()` on authentication cubit. + +And in `AppView` use an `AuthenticationBuilder`: + +```dart +AuthenticationBuilder( + unknown: (context) => const LoadingPage(), + unauthenticated: (context) => const LoginPage(), + authenticated: (context, user, userData) => const HomePage(), +) +``` + +To create a `SignInCubit` you'll need the same `AuthenticationRepository`, you can use the `context`: + +```dart +BlocProvider( + create: (_) => SignInCubit(context.read()), + child: const LoginForm(), +), +``` + +> In practice it's better to create it in the main `MultiBlocProvider` because the LoginPage can be destroyed, and cubit closed, before login flow ends + +## Recipes + +### Password confirmation + +In this recipe we'll se how to create a custom `FormEntry` to confirm password. + +First, create an entry at the SignUpCubit creation: + +```dart +SignUpCubit _signUpCubit = SignUpCubit( + authenticationRepository: _authenticationRepository, + authenticationCubit: _authenticationCubit, + entries: const FormData([ + FormEntry('form_field_confirmPassword', ConfirmedPassword.pure()), + ]), +); +``` + +Then, in the sign up form, create an input for password confirmation: + +- `ConfirmedPassword` validator need password value and confirm password value to compare. + +```dart +return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (confirmPassword) => context + .read() + .dataChanged( + 'form_field_confirmPassword', + ConfirmedPassword.dirty( + password: context.read().state.password.value, + value: confirmPassword, + ), + ), + obscureText: true, + decoration: InputDecoration( + labelText: 'confirm password', + errorText: state.data!.input('form_field_confirmPassword').invalid + ? 'passwords do not match' + : null, + ), + ); + }, +); +``` + +> `form_field_confirmPassword` is the field identifier used in all application to retrieve data. You can use a constant to avoid typos. + +You'll need to update password input to update confirm state on password update ! + +```dart +return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (password) { + context.read().passwordChanged(password); + context.read().dataChanged( + 'form_field_confirmPassword', + ConfirmedPassword.dirty( + password: password, + value: context + .read() + .state + .data! + .input('form_field_confirmPassword') + .value, + ), + ); + }, + obscureText: true, + decoration: InputDecoration( + labelText: 'password', + errorText: state.password.invalid ? 'invalid password' : null, + ), + ); + }, +); +``` + +> Here you call standard `passwordChanged()` AND `dataChanged()`. + +And voilà ! + +### Create Firestore Document on Sign Up + +In this recipe we'll se how to create a Firestore Document on sign up success. + +First create a callback function: + +```dart +Future onSignUpSuccess(SignUpState state, String? uid) async { + if (uid != null) { + final user = { + 'uid': uid, + 'email': state.email.value, + ...state.data.toMap(), + }; + await FirebaseFirestore.instance.collection('users').doc(uid).set(user); + } +} +``` + +Then create SignUpCubit with custom entries and register callback: + +```dart +SignUpCubit _signUpCubit = SignUpCubit( + authenticationRepository: _authenticationRepository, + authenticationCubit: _authenticationCubit, + entries: const FormData([ + FormEntry('form_field_name', Name.pure(), fieldName: 'name'), + FormEntry('form_field_phone', Phone.pure(), fieldName: 'phone'), + FormEntry('form_field_confirmPassword', ConfirmedPassword.pure(), export: false), + ]), + onSignUpSuccess: onSignUpSuccess, +); +``` + +> Use `fieldName` and `export` to control `.toMap()` result on FormData ! Useful to disable exportation of sensible data like passwords. + +Create widgets for each inputs: + +```dart +class _PhoneInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (phone) => context + .read() + .dataChanged('form_field_phone', Phone.dirty(phone)), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + labelText: 'phone', + helperText: '', + errorText: state.data!.input('form_field_phone').invalid + ? 'invalid phone' + : null, + ), + ); + }, + ); + } +} +``` + +> Create widgets for Name and ConfirmedPassword too. + +Then add a sign up button with: + +```dart +context.read().signUpFormSubmitted() +``` + +And voilà, a document with `uid` as id, and fields `email`, `name`, `phone`, `uid` will be create in `users` collection. \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/analysis_options.yaml b/packages/wyatt_authentication_bloc/analysis_options.yaml new file mode 100644 index 00000000..41337b90 --- /dev/null +++ b/packages/wyatt_authentication_bloc/analysis_options.yaml @@ -0,0 +1,6 @@ +include: package:wyatt_analysis/analysis_options.flutter.1.1.1.yaml + +linter: + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/example/.gitignore b/packages/wyatt_authentication_bloc/example/.gitignore new file mode 100644 index 00000000..0fa6b675 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/.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/packages/wyatt_authentication_bloc/example/.metadata b/packages/wyatt_authentication_bloc/example/.metadata new file mode 100644 index 00000000..fd70cabc --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/.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: 77d935af4db863f6abd0b9c31c7e6df2a13de57b + channel: stable + +project_type: app diff --git a/packages/wyatt_authentication_bloc/example/README.md b/packages/wyatt_authentication_bloc/example/README.md new file mode 100644 index 00000000..a1356260 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/README.md @@ -0,0 +1,16 @@ +# example + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/wyatt_authentication_bloc/example/analysis_options.yaml b/packages/wyatt_authentication_bloc/example/analysis_options.yaml new file mode 100644 index 00000000..61b6c4de --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/packages/wyatt_authentication_bloc/example/android/.gitignore b/packages/wyatt_authentication_bloc/example/android/.gitignore new file mode 100644 index 00000000..6f568019 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/packages/wyatt_authentication_bloc/example/android/app/build.gradle b/packages/wyatt_authentication_bloc/example/android/app/build.gradle new file mode 100644 index 00000000..c1b80db7 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/build.gradle @@ -0,0 +1,68 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.example" + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/debug/AndroidManifest.xml b/packages/wyatt_authentication_bloc/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..c208884f --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/AndroidManifest.xml b/packages/wyatt_authentication_bloc/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..3f41384d --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/packages/wyatt_authentication_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 00000000..e793a000 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/drawable-v21/launch_background.xml b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/drawable/launch_background.xml b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/values-night/styles.xml b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..3db14bb5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/main/res/values/styles.xml b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..d460d1e9 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/app/src/profile/AndroidManifest.xml b/packages/wyatt_authentication_bloc/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..c208884f --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/wyatt_authentication_bloc/example/android/build.gradle b/packages/wyatt_authentication_bloc/example/android/build.gradle new file mode 100644 index 00000000..4256f917 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:4.1.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/wyatt_authentication_bloc/example/android/gradle.properties b/packages/wyatt_authentication_bloc/example/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/wyatt_authentication_bloc/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/wyatt_authentication_bloc/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..bc6a58af --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip diff --git a/packages/wyatt_authentication_bloc/example/android/settings.gradle b/packages/wyatt_authentication_bloc/example/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/wyatt_authentication_bloc/example/ios/.gitignore b/packages/wyatt_authentication_bloc/example/ios/.gitignore new file mode 100644 index 00000000..7a7f9873 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/wyatt_authentication_bloc/example/ios/Flutter/AppFrameworkInfo.plist b/packages/wyatt_authentication_bloc/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..8d4492f9 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Flutter/Debug.xcconfig b/packages/wyatt_authentication_bloc/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/wyatt_authentication_bloc/example/ios/Flutter/Release.xcconfig b/packages/wyatt_authentication_bloc/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/wyatt_authentication_bloc/example/ios/Podfile b/packages/wyatt_authentication_bloc/example/ios/Podfile new file mode 100644 index 00000000..9411102b --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Podfile @@ -0,0 +1,41 @@ +# Uncomment this line to define a global platform for your project +platform :ios, '10.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/packages/wyatt_authentication_bloc/example/ios/Podfile.lock b/packages/wyatt_authentication_bloc/example/ios/Podfile.lock new file mode 100644 index 00000000..5c71db22 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Podfile.lock @@ -0,0 +1,97 @@ +PODS: + - Firebase/Auth (8.10.0): + - Firebase/CoreOnly + - FirebaseAuth (~> 8.10.0) + - Firebase/CoreOnly (8.10.0): + - FirebaseCore (= 8.10.0) + - firebase_auth (3.3.5): + - Firebase/Auth (= 8.10.0) + - firebase_core + - Flutter + - firebase_core (1.11.0): + - Firebase/CoreOnly (= 8.10.0) + - Flutter + - FirebaseAuth (8.10.0): + - FirebaseCore (~> 8.0) + - GoogleUtilities/AppDelegateSwizzler (~> 7.6) + - GoogleUtilities/Environment (~> 7.6) + - GTMSessionFetcher/Core (~> 1.5) + - FirebaseCore (8.10.0): + - FirebaseCoreDiagnostics (~> 8.0) + - GoogleUtilities/Environment (~> 7.6) + - GoogleUtilities/Logger (~> 7.6) + - FirebaseCoreDiagnostics (8.10.0): + - GoogleDataTransport (~> 9.1) + - GoogleUtilities/Environment (~> 7.6) + - GoogleUtilities/Logger (~> 7.6) + - nanopb (~> 2.30908.0) + - Flutter (1.0.0) + - GoogleDataTransport (9.1.2): + - GoogleUtilities/Environment (~> 7.2) + - nanopb (~> 2.30908.0) + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/AppDelegateSwizzler (7.7.0): + - GoogleUtilities/Environment + - GoogleUtilities/Logger + - GoogleUtilities/Network + - GoogleUtilities/Environment (7.7.0): + - PromisesObjC (< 3.0, >= 1.2) + - GoogleUtilities/Logger (7.7.0): + - GoogleUtilities/Environment + - GoogleUtilities/Network (7.7.0): + - GoogleUtilities/Logger + - "GoogleUtilities/NSData+zlib" + - GoogleUtilities/Reachability + - "GoogleUtilities/NSData+zlib (7.7.0)" + - GoogleUtilities/Reachability (7.7.0): + - GoogleUtilities/Logger + - GTMSessionFetcher/Core (1.7.0) + - nanopb (2.30908.0): + - nanopb/decode (= 2.30908.0) + - nanopb/encode (= 2.30908.0) + - nanopb/decode (2.30908.0) + - nanopb/encode (2.30908.0) + - PromisesObjC (2.0.0) + +DEPENDENCIES: + - firebase_auth (from `.symlinks/plugins/firebase_auth/ios`) + - firebase_core (from `.symlinks/plugins/firebase_core/ios`) + - Flutter (from `Flutter`) + +SPEC REPOS: + trunk: + - Firebase + - FirebaseAuth + - FirebaseCore + - FirebaseCoreDiagnostics + - GoogleDataTransport + - GoogleUtilities + - GTMSessionFetcher + - nanopb + - PromisesObjC + +EXTERNAL SOURCES: + firebase_auth: + :path: ".symlinks/plugins/firebase_auth/ios" + firebase_core: + :path: ".symlinks/plugins/firebase_core/ios" + Flutter: + :path: Flutter + +SPEC CHECKSUMS: + Firebase: 44213362f1dcc52555b935dc925ed35ac55f1b20 + firebase_auth: 9d959bddc3b255ddfb67cf630a5bbb6186964356 + firebase_core: 2ca2d6cc8a11693e701b37126afae4800be5dbac + FirebaseAuth: 59a2d2b933b5b79e18fb1e6ad230c6abdaa73d26 + FirebaseCore: 04186597c095da37d90ff9fd3e53bc61a1ff2440 + FirebaseCoreDiagnostics: 56fb7216d87e0e6ec2feddefa9d8a392fe8b2c18 + Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940 + GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 + GTMSessionFetcher: 43748f93435c2aa068b1cbe39655aaf600652e91 + nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 + PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 + +PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea + +COCOAPODS: 1.11.2 diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.pbxproj b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..0b90f66a --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,549 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + E0ACB97A2AC1EAE305AB816E /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 17811D4CD54BBE5A5A617A67 /* Pods_Runner.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 08CA0F9F86D48F857AE8F6A9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 17811D4CD54BBE5A5A617A67 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 5464F99483A75A45363198CF /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 60123E8F332B681F9E8E65D0 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E0ACB97A2AC1EAE305AB816E /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 868018A8DFEB24F4BBF7C643 /* Pods */ = { + isa = PBXGroup; + children = ( + 5464F99483A75A45363198CF /* Pods-Runner.debug.xcconfig */, + 08CA0F9F86D48F857AE8F6A9 /* Pods-Runner.release.xcconfig */, + 60123E8F332B681F9E8E65D0 /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 868018A8DFEB24F4BBF7C643 /* Pods */, + A25D987A122B465AA7E625CC /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + A25D987A122B465AA7E625CC /* Frameworks */ = { + isa = PBXGroup; + children = ( + 17811D4CD54BBE5A5A617A67 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + FDCB4921C84D1E40D80F2BC5 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + EA7D75F2512D4CFC69FD736B /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + EA7D75F2512D4CFC69FD736B /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + FDCB4921C84D1E40D80F2BC5 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..c87d15a3 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/AppDelegate.swift b/packages/wyatt_authentication_bloc/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..28c6bf03 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..2ccbfd96 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..f091b6b0 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cde1211 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..d0ef06e7 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..dcdc2306 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..2ccbfd96 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..c8f9ed8f Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..a6d6b860 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..a6d6b860 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..75b2d164 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..c4df70d3 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..6a84f41e Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..d0e1f585 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/wyatt_authentication_bloc/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Base.lproj/Main.storyboard b/packages/wyatt_authentication_bloc/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Info.plist b/packages/wyatt_authentication_bloc/example/ios/Runner/Info.plist new file mode 100644 index 00000000..27491e8a --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Info.plist @@ -0,0 +1,47 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/wyatt_authentication_bloc/example/ios/Runner/Runner-Bridging-Header.h b/packages/wyatt_authentication_bloc/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/wyatt_authentication_bloc/example/lib/app/app.dart b/packages/wyatt_authentication_bloc/example/lib/app/app.dart new file mode 100644 index 00000000..07e74a59 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/app/app.dart @@ -0,0 +1,149 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:example/constants.dart'; +import 'package:example/home/home_page.dart'; +import 'package:example/login/login_page.dart'; +import 'package:example/model.dart'; +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class App extends StatelessWidget { + const App({Key? key}) : super(key: key); + + static FormData getNormalFormData() { + return const FormData([ + FormEntry(formFieldName, Name.pure()), + FormEntry(formFieldPhone, Phone.pure()), + FormEntry(formFieldPro, Boolean.pure()), + FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(), export: false), + ]); + } + + static FormData getProFormData() { + return const FormData([ + FormEntry(formFieldName, Name.pure()), + FormEntry(formFieldPhone, Phone.pure()), + FormEntry(formFieldPro, Boolean.pure()), + FormEntry(formFieldSiren, Siren.pure()), + FormEntry(formFieldIban, Iban.pure()), + FormEntry(formFieldConfirmedPassword, ConfirmedPassword.pure(), export: false), + ]); + } + + // On Authentication Success. (Authenticated or Anonymous) + // User callback. + Future> onAuthSuccess(UserInterface user) async { + if (user.isNotEmpty && !user.isAnonymous) { + // Check if user is register in Firesore. + DocumentSnapshot firestoreUser = await FirebaseFirestore.instance + .collection('users') + .doc(user.uid) + .get(); + return { + 'user': + UserFirestore.fromMap(firestoreUser.data() as Map), + ...firestoreUser.data() as Map? ?? {} + }; + } else { + return {}; + } + } + + // On Sign Up Success. + Future onSignUpSuccess(SignUpState state, String? uid) async { + if (uid != null) { + final data = state.data.toMap(); + final user = {'uid': uid, 'email': state.email.value, ...data}; + await FirebaseFirestore.instance.collection('users').doc(uid).set(user); + } + } + + @override + Widget build(BuildContext context) { + AuthenticationRepositoryInterface _authenticationRepository = + AuthenticationRepositoryFirebase(); + + AuthenticationCubit _authenticationCubit = AuthenticationCubit( + authenticationRepository: _authenticationRepository, + onAuthSuccess: onAuthSuccess, + ); + + SignUpCubit _signUpCubit = SignUpCubit( + authenticationRepository: _authenticationRepository, + authenticationCubit: _authenticationCubit, + entries: getNormalFormData(), + onSignUpSuccess: onSignUpSuccess, + ); + + SignInCubit _signInCubit = SignInCubit( + authenticationRepository: _authenticationRepository, + authenticationCubit: _authenticationCubit, + ); + + return MultiRepositoryProvider( + providers: [ + RepositoryProvider( + create: (context) => _authenticationRepository, + ), + ], + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => _authenticationCubit..init(), + ), + BlocProvider( + create: (context) => _signUpCubit, + ), + BlocProvider( + create: (context) => _signInCubit, + ), + ], + child: const AppView(), + ), + ); + } +} + +class AppView extends StatelessWidget { + const AppView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return MaterialApp( + theme: ThemeData( + primarySwatch: Colors.blue, + buttonTheme: const ButtonThemeData( + buttonColor: Colors.blue, + textTheme: ButtonTextTheme.primary, + ), + ), + home: AuthenticationBuilder( + unknown: (context) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + }, + authenticated: (context, user, userData) => const HomePage(), + unauthenticated: (context) => const LoginPage(), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/app/bloc_observer.dart b/packages/wyatt_authentication_bloc/example/lib/app/bloc_observer.dart new file mode 100644 index 00000000..83b019f8 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/app/bloc_observer.dart @@ -0,0 +1,44 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; + +class AppBlocObserver extends BlocObserver { + @override + void onEvent(Bloc bloc, Object? event) { + super.onEvent(bloc, event); + debugPrint(event.toString()); + } + + @override + void onError(BlocBase bloc, Object error, StackTrace stackTrace) { + debugPrint(error.toString()); + super.onError(bloc, error, stackTrace); + } + + @override + void onChange(BlocBase bloc, Change change) { + super.onChange(bloc, change); + debugPrint('curr:\t${change.currentState}\nnext:\t${change.nextState}'); + } + + @override + void onTransition(Bloc bloc, Transition transition) { + super.onTransition(bloc, transition); + debugPrint(transition.toString()); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/constants.dart b/packages/wyatt_authentication_bloc/example/lib/constants.dart new file mode 100644 index 00000000..953b8602 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/constants.dart @@ -0,0 +1,22 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +const String formFieldName = 'name'; +const String formFieldPhone = 'phone'; +const String formFieldPro = 'isPro'; +const String formFieldConfirmedPassword = 'confirmedPassword'; +const String formFieldSiren = 'siren'; +const String formFieldIban = 'iban'; \ No newline at end of file diff --git a/packages/wyatt_authentication_bloc/example/lib/firebase_options.dart b/packages/wyatt_authentication_bloc/example/lib/firebase_options.dart new file mode 100644 index 00000000..92a4d0da --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/firebase_options.dart @@ -0,0 +1,70 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + // ignore: missing_enum_constant_in_switch + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + } + + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyCxdDUpkpW7onOicIcIDcIVvPC8ll1twCQ', + appId: '1:136771801992:web:e757595ae62e646297203d', + messagingSenderId: '136771801992', + projectId: 'tchat-beta', + authDomain: 'tchat-beta.firebaseapp.com', + databaseURL: 'https://tchat-beta.firebaseio.com', + storageBucket: 'tchat-beta.appspot.com', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyAYS14uXupkS158Q5QAFP1864UrUN_yDSk', + appId: '1:136771801992:android:d20e0361057e815197203d', + messagingSenderId: '136771801992', + projectId: 'tchat-beta', + databaseURL: 'https://tchat-beta.firebaseio.com', + storageBucket: 'tchat-beta.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyCDbbhjbFrQwLXuIANdJzjkDk8uOETnn7w', + appId: '1:136771801992:ios:6fe69c0bea08b7b397203d', + messagingSenderId: '136771801992', + projectId: 'tchat-beta', + databaseURL: 'https://tchat-beta.firebaseio.com', + storageBucket: 'tchat-beta.appspot.com', + iosClientId: + '136771801992-e585bm1n9b3lv89t4phrl9u0glsg52ua.apps.googleusercontent.com', + iosBundleId: 'com.example.example', + ); +} diff --git a/packages/wyatt_authentication_bloc/example/lib/forgot_password/forgot_password_page.dart b/packages/wyatt_authentication_bloc/example/lib/forgot_password/forgot_password_page.dart new file mode 100644 index 00000000..fe240987 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/forgot_password/forgot_password_page.dart @@ -0,0 +1,44 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/forgot_password/widgets/forgot_password_form.dart'; +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class ForgotPasswordPage extends StatelessWidget { + const ForgotPasswordPage({Key? key}) : super(key: key); + + static Route route() { + return MaterialPageRoute(builder: (_) => const ForgotPasswordPage()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Forgot Password')), + body: Padding( + padding: const EdgeInsets.all(8), + child: BlocProvider( + create: (_) => PasswordResetCubit( + context.read(), + ), + child: const ForgotPasswordForm(), + ), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/forgot_password/widgets/forgot_password_form.dart b/packages/wyatt_authentication_bloc/example/lib/forgot_password/widgets/forgot_password_form.dart new file mode 100644 index 00000000..d3879878 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/forgot_password/widgets/forgot_password_form.dart @@ -0,0 +1,91 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class _EmailInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.email != current.email, + builder: (context, state) { + return TextField( + onChanged: (email) => + context.read().emailChanged(email), + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'email', + helperText: '', + errorText: state.email.invalid ? 'invalid email' : null, + ), + ); + }, + ); + } +} + +class _ResetButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, + builder: (context, state) { + return state.status.isSubmissionInProgress + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: state.status.isValidated + ? () => context + .read() + .sendPasswordResetEmail() + : null, + child: const Text('SEND EMAIL'), + ); + }, + ); + } +} + +class ForgotPasswordForm extends StatelessWidget { + const ForgotPasswordForm({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state.status.isSubmissionSuccess) { + Navigator.of(context).pop(); + } else if (state.status.isSubmissionFailure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(state.errorMessage ?? 'Password reset Failure'), + ), + ); + } + }, + child: Align( + alignment: const Alignment(0, -1 / 3), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [_EmailInput(), const SizedBox(height: 8), _ResetButton()], + ), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/home/home_page.dart b/packages/wyatt_authentication_bloc/example/lib/home/home_page.dart new file mode 100644 index 00000000..1ea8cc4b --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/home/home_page.dart @@ -0,0 +1,65 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/home/widgets/infos.dart'; +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import 'package:example/home/widgets/email_verification.dart'; + +class HomePage extends StatelessWidget { + const HomePage({Key? key}) : super(key: key); + + static Route route() { + return MaterialPageRoute(builder: (_) => const HomePage()); + } + + @override + Widget build(BuildContext context) { + final user = context.select((AuthenticationCubit cubit) => cubit.state.user); + return Scaffold( + appBar: AppBar( + title: const Text('Home'), + actions: [ + IconButton( + icon: const Icon(Icons.exit_to_app), + onPressed: () => context + .read() + .logOut(), + ) + ], + ), + body: Padding( + padding: const EdgeInsets.all(8), + child: BlocProvider( + create: (_) => EmailVerificationCubit( + context.read(), + )..checkEmailVerification(), + child: BlocBuilder( + builder: (context, state) { + if (state.isVerified || user!.isAnonymous) { + return const UserInfo(); + } else { + return const EmailVerification(); + } + }, + ), + ), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/home/widgets/avatar.dart b/packages/wyatt_authentication_bloc/example/lib/home/widgets/avatar.dart new file mode 100644 index 00000000..6c9936b8 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/home/widgets/avatar.dart @@ -0,0 +1,37 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/material.dart'; + +const _avatarSize = 48.0; + +class Avatar extends StatelessWidget { + const Avatar({Key? key, this.photo}) : super(key: key); + + final String? photo; + + @override + Widget build(BuildContext context) { + final photo = this.photo; + return CircleAvatar( + radius: _avatarSize, + backgroundImage: photo != null ? NetworkImage(photo) : null, + child: photo == null + ? const Icon(Icons.person_outline, size: _avatarSize) + : null, + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/home/widgets/email_verification.dart b/packages/wyatt_authentication_bloc/example/lib/home/widgets/email_verification.dart new file mode 100644 index 00000000..22c98c30 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/home/widgets/email_verification.dart @@ -0,0 +1,53 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class EmailVerification extends StatelessWidget { + const EmailVerification({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final user = context.select((AuthenticationCubit cubit) => cubit.state.user); + final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData); + return Align( + alignment: const Alignment(0, -1 / 3), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text("Hello ${userData!['name'] ?? 'null'}!"), + const SizedBox(height: 4), + Text("Email '${user?.email ?? 'null'}' is not verified"), + const SizedBox(height: 4), + ElevatedButton( + onPressed: () { + context.read().sendEmailVerification(); + }, + child: const Text('(Re)send email'), + ), + ElevatedButton( + onPressed: () { + context.read().checkEmailVerification(); + }, + child: const Text('Refresh'), + ), + ], + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/home/widgets/infos.dart b/packages/wyatt_authentication_bloc/example/lib/home/widgets/infos.dart new file mode 100644 index 00000000..ae1ecb24 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/home/widgets/infos.dart @@ -0,0 +1,58 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/home/widgets/avatar.dart'; +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:example/constants.dart'; + +class UserInfo extends StatelessWidget { + const UserInfo({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final textTheme = Theme.of(context).textTheme; + final user = context.select((AuthenticationCubit cubit) => cubit.state.user); + final userData = context.select((AuthenticationCubit cubit) => cubit.state.userData); + return Align( + alignment: const Alignment(0, -1 / 3), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Avatar(photo: user?.photoURL), + const SizedBox(height: 4), + Text("Email: ${user?.email ?? 'null'}", style: textTheme.headline6), + const SizedBox(height: 4), + Text("Name: ${userData![formFieldName] ?? 'null'}", + style: textTheme.headline6), + const SizedBox(height: 4), + Text("Phone: ${userData[formFieldPhone] ?? 'null'}", + style: textTheme.headline6), + const SizedBox(height: 4), + Text("IsPro: ${userData[formFieldPro] ?? 'null'}", + style: textTheme.headline6), + const SizedBox(height: 4), + Text("IsAnonymous: ${user?.isAnonymous.toString() ?? 'null'}", + style: textTheme.headline6), + const SizedBox(height: 4), + Text("IsEmailVerified: ${user?.emailVerified.toString() ?? 'null'}", + style: textTheme.headline6), + ], + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/login/login_page.dart b/packages/wyatt_authentication_bloc/example/lib/login/login_page.dart new file mode 100644 index 00000000..680cc066 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/login/login_page.dart @@ -0,0 +1,37 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/login/widgets/login_form.dart'; +import 'package:flutter/material.dart'; + +class LoginPage extends StatelessWidget { + const LoginPage({Key? key}) : super(key: key); + + static Route route() { + return MaterialPageRoute(builder: (_) => const LoginPage()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Login')), + body: const Padding( + padding: EdgeInsets.all(8), + child: LoginForm(), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart b/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart new file mode 100644 index 00000000..32e0155b --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/login/widgets/login_form.dart @@ -0,0 +1,193 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/app/app.dart'; +import 'package:example/constants.dart'; +import 'package:example/forgot_password/forgot_password_page.dart'; +import 'package:example/sign_up/sign_up_page.dart'; +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class _EmailInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.email != current.email, + builder: (context, state) { + return TextField( + onChanged: (email) => context.read().emailChanged(email), + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'email', + helperText: '', + errorText: state.email.invalid ? 'invalid email' : null, + ), + ); + }, + ); + } +} + +class _PasswordInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.password != current.password, + builder: (context, state) { + return TextField( + onChanged: (password) => + context.read().passwordChanged(password), + obscureText: true, + decoration: InputDecoration( + labelText: 'password', + helperText: '', + errorText: state.password.invalid ? 'invalid password' : null, + ), + ); + }, + ); + } +} + +class _LoginAnonButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, + builder: (context, state) { + return state.status.isSubmissionInProgress + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: () => + context.read().signInAnonymously(), + child: const Text('LOGIN ANONYMOUSLY'), + ); + }, + ); + } +} + +class _LoginWithPasswordButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, + builder: (context, state) { + return state.status.isSubmissionInProgress + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: state.status.isValidated + ? () => + context.read().signInWithEmailAndPassword() + : null, + child: const Text('LOGIN WITH PASSWORD'), + ); + }, + ); + } +} + +class _SignUpButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return TextButton( + onPressed: () { + context.read().updateFormData(App.getNormalFormData()); + Navigator.of(context).push(SignUpPage.route()); + }, + child: Text( + 'CREATE ACCOUNT', + style: TextStyle(color: theme.primaryColor), + ), + ); + } +} + +class _SignUpAsProButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + return TextButton( + onPressed: () { + context.read().updateFormData(App.getProFormData()); + context.read().dataChanged( + formFieldPro, + const Boolean.dirty(value: true), + ); + Navigator.of(context).push(SignUpPage.route()); + }, + child: Text( + 'CREATE PRO ACCOUNT', + style: TextStyle(color: theme.primaryColor), + ), + ); + } +} + +class LoginForm extends StatelessWidget { + const LoginForm({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state.status.isSubmissionFailure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar( + content: Text(state.errorMessage ?? 'Authentication Failure'), + ), + ); + } + }, + child: Align( + alignment: const Alignment(0, -1 / 3), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox(height: 120), + _EmailInput(), + const SizedBox(height: 8), + _PasswordInput(), + const SizedBox(height: 8), + GestureDetector( + onTap: () { + Navigator.of(context).push(ForgotPasswordPage.route()); + }, + child: Text( + 'Forgot password ?', + style: Theme.of(context).textTheme.bodyText2, + ), + ), + const SizedBox(height: 8), + _LoginWithPasswordButton(), + const SizedBox(height: 8), + _LoginAnonButton(), + const SizedBox(height: 8), + _SignUpButton(), + const SizedBox(height: 8), + _SignUpAsProButton(), + ], + ), + ), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/main.dart b/packages/wyatt_authentication_bloc/example/lib/main.dart new file mode 100644 index 00000000..d96ed05e --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/main.dart @@ -0,0 +1,35 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/app/app.dart'; +import 'package:example/app/bloc_observer.dart'; +import 'package:example/firebase_options.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions.currentPlatform, + ); + BlocOverrides.runZoned( + () => runApp( + const App(), + ), + blocObserver: AppBlocObserver(), + ); +} diff --git a/packages/wyatt_authentication_bloc/example/lib/model.dart b/packages/wyatt_authentication_bloc/example/lib/model.dart new file mode 100644 index 00000000..9dfc1069 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/model.dart @@ -0,0 +1,52 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +class UserFirestore { + final String email; + final String name; + final String phone; + final String uid; + + UserFirestore({ + required this.email, + required this.name, + required this.phone, + required this.uid, + }); + + factory UserFirestore.fromMap(Map map) { + return UserFirestore( + uid: map['uid'], + email: map['email'], + name: map['name'], + phone: map['phone'], + ); + } + + Map toMap() { + return { + 'uid': uid, + 'email': email, + 'name': name, + 'phone': phone, + }; + } + + @override + String toString() { + return 'UserFirestore(email: $email, name: $name, phone: $phone, uid: $uid)'; + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/sign_up/sign_up_page.dart b/packages/wyatt_authentication_bloc/example/lib/sign_up/sign_up_page.dart new file mode 100644 index 00000000..df7e6377 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/sign_up/sign_up_page.dart @@ -0,0 +1,39 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:example/sign_up/widgets/sign_up_form.dart'; +import 'package:flutter/material.dart'; + +class SignUpPage extends StatelessWidget { + const SignUpPage({Key? key}) : super(key: key); + + static Route route() { + return MaterialPageRoute(builder: (_) => const SignUpPage()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Sign Up')), + body: const Padding( + padding: EdgeInsets.all(8), + child: SingleChildScrollView( + child: SignUpForm(), + ), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart b/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart new file mode 100644 index 00000000..7bd026f3 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/lib/sign_up/widgets/sign_up_form.dart @@ -0,0 +1,324 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'dart:developer'; + +import 'package:example/app/app.dart'; +import 'package:example/constants.dart'; +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class _NameInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (name) => context + .read() + .dataChanged(formFieldName, Name.dirty(name)), + keyboardType: TextInputType.name, + decoration: InputDecoration( + labelText: 'name', + helperText: '', + errorText: + state.data.input(formFieldName).invalid ? 'invalid name' : null, + ), + ); + }, + ); + } +} + +class _PhoneInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (phone) => context + .read() + .dataChanged(formFieldPhone, Phone.dirty(phone)), + keyboardType: TextInputType.phone, + decoration: InputDecoration( + labelText: 'phone', + helperText: '', + errorText: state.data.input(formFieldPhone).invalid + ? 'invalid phone' + : null, + ), + ); + }, + ); + } +} + +class _SirenInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (siren) => context + .read() + .dataChanged(formFieldSiren, Siren.dirty(siren)), + keyboardType: TextInputType.number, + decoration: InputDecoration( + labelText: 'siren', + helperText: '', + errorText: state.data.input(formFieldSiren).invalid + ? 'invalid SIREN' + : null, + ), + ); + }, + ); + } +} + +class _IbanInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (iban) => context + .read() + .dataChanged(formFieldIban, Iban.dirty(iban)), + keyboardType: TextInputType.text, + decoration: InputDecoration( + labelText: 'iban', + helperText: '', + errorText: + state.data.input(formFieldIban).invalid ? 'invalid IBAN' : null, + ), + ); + }, + ); + } +} + +class _EmailInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.email != current.email, + builder: (context, state) { + return TextField( + onChanged: (email) => context.read().emailChanged(email), + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: 'email', + helperText: '', + errorText: state.email.invalid ? 'invalid email' : null, + ), + ); + }, + ); + } +} + +class _PasswordInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (password) { + context.read().passwordChanged(password); + context.read().dataChanged( + formFieldConfirmedPassword, + ConfirmedPassword.dirty( + password: password, + value: context + .read() + .state + .data + .input(formFieldConfirmedPassword) + .value, + ), + ); + }, + obscureText: true, + decoration: InputDecoration( + labelText: 'password', + helperText: '', + errorText: state.password.invalid ? 'invalid password' : null, + ), + ); + }, + ); + } +} + +class _ConfirmPasswordInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return TextField( + onChanged: (confirmPassword) => context + .read() + .dataChanged( + formFieldConfirmedPassword, + ConfirmedPassword.dirty( + password: context.read().state.password.value, + value: confirmPassword, + ), + ), + obscureText: true, + decoration: InputDecoration( + labelText: 'confirm password', + helperText: '', + errorText: state.data.input(formFieldConfirmedPassword).invalid + ? 'passwords do not match' + : null, + ), + ); + }, + ); + } +} + +class _CheckIsProInput extends StatelessWidget { + @override + Widget build(BuildContext context) { + return ListTile( + title: const Text('Are you a pro?'), + trailing: BlocBuilder( + builder: (context, state) { + return Checkbox( + value: state.data.input(formFieldPro).value, + onChanged: (isPro) { + final value = + isPro!; // tristate is false, so value can't be null + + context.read().dataChanged( + formFieldPro, + Boolean.dirty(value: value), + ); + + if (value) { + context.read().updateFormData( + App.getProFormData(), + operation: SetOperation.union); + } else { + context.read().updateFormData( + App.getNormalFormData(), + operation: SetOperation.intersection); + } + }); + }, + ), + ); + } +} + +class _SignUpButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + buildWhen: (previous, current) => previous.status != current.status, + builder: (context, state) { + return state.status.isSubmissionInProgress + ? const CircularProgressIndicator() + : ElevatedButton( + onPressed: state.status.isValidated + ? () => context.read().signUpFormSubmitted() + : null, + child: const Text('SIGN UP'), + ); + }, + ); + } +} + +class _DebugButton extends StatelessWidget { + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + return ElevatedButton( + onPressed: () { + log(state.toString()); + } , + child: const Text('DEBUG'), + ); + }, + ); + } +} + +class SignUpForm extends StatelessWidget { + const SignUpForm({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return BlocListener( + listener: (context, state) { + if (state.status.isSubmissionSuccess) { + Navigator.of(context).pop(); + } else if (state.status.isSubmissionFailure) { + ScaffoldMessenger.of(context) + ..hideCurrentSnackBar() + ..showSnackBar( + SnackBar(content: Text(state.errorMessage ?? 'Sign Up Failure')), + ); + } + }, + child: SizedBox( + height: MediaQuery.of(context).size.height, + child: Align( + alignment: const Alignment(0, -1 / 3), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _NameInput(), + const SizedBox(height: 8), + _PhoneInput(), + const SizedBox(height: 8), + _CheckIsProInput(), + const SizedBox(height: 8), + BlocBuilder( + builder: (context, state) { + if (state.data.input(formFieldPro).value) { + return Column(children: [ + _SirenInput(), + const SizedBox(height: 8), + _IbanInput(), + const SizedBox(height: 8), + ]); + } + return const SizedBox.shrink(); + }, + ), + _EmailInput(), + const SizedBox(height: 8), + _PasswordInput(), + const SizedBox(height: 8), + _ConfirmPasswordInput(), + const SizedBox(height: 8), + _SignUpButton(), + const SizedBox(height: 8), + _DebugButton(), + ], + ), + ), + ), + ); + } +} diff --git a/packages/wyatt_authentication_bloc/example/pubspec.yaml b/packages/wyatt_authentication_bloc/example/pubspec.yaml new file mode 100644 index 00000000..3fb9f6f4 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/pubspec.yaml @@ -0,0 +1,94 @@ +name: example +description: A new Flutter project. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter 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.15.1 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + firebase_core: ^1.13.1 + flutter_bloc: ^8.0.1 + wyatt_authentication_bloc: + path: "../" + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.4 + cloud_firestore: ^3.1.10 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^1.0.4 + +# 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 + + # 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/packages/wyatt_authentication_bloc/example/test/widget_test.dart b/packages/wyatt_authentication_bloc/example/test/widget_test.dart new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/test/widget_test.dart @@ -0,0 +1 @@ + diff --git a/packages/wyatt_authentication_bloc/example/web/favicon.png b/packages/wyatt_authentication_bloc/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/web/favicon.png differ diff --git a/packages/wyatt_authentication_bloc/example/web/icons/Icon-192.png b/packages/wyatt_authentication_bloc/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/web/icons/Icon-192.png differ diff --git a/packages/wyatt_authentication_bloc/example/web/icons/Icon-512.png b/packages/wyatt_authentication_bloc/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/web/icons/Icon-512.png differ diff --git a/packages/wyatt_authentication_bloc/example/web/icons/Icon-maskable-192.png b/packages/wyatt_authentication_bloc/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/web/icons/Icon-maskable-192.png differ diff --git a/packages/wyatt_authentication_bloc/example/web/icons/Icon-maskable-512.png b/packages/wyatt_authentication_bloc/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/packages/wyatt_authentication_bloc/example/web/icons/Icon-maskable-512.png differ diff --git a/packages/wyatt_authentication_bloc/example/web/index.html b/packages/wyatt_authentication_bloc/example/web/index.html new file mode 100644 index 00000000..b6b9dd23 --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/web/index.html @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + diff --git a/packages/wyatt_authentication_bloc/example/web/manifest.json b/packages/wyatt_authentication_bloc/example/web/manifest.json new file mode 100644 index 00000000..096edf8f --- /dev/null +++ b/packages/wyatt_authentication_bloc/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/packages/wyatt_authentication_bloc/lib/src/authentication/authentication.dart b/packages/wyatt_authentication_bloc/lib/src/authentication/authentication.dart new file mode 100644 index 00000000..95e85224 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/authentication/authentication.dart @@ -0,0 +1,18 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'builder/authentication_builder.dart'; +export 'cubit/authentication_cubit.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/authentication/builder/authentication_builder.dart b/packages/wyatt_authentication_bloc/lib/src/authentication/builder/authentication_builder.dart new file mode 100644 index 00000000..dba56b2b --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/authentication/builder/authentication_builder.dart @@ -0,0 +1,56 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/material.dart'; +import 'package:wyatt_authentication_bloc/src/authentication/cubit/authentication_cubit.dart'; +import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class AuthenticationBuilder extends StatelessWidget { + const AuthenticationBuilder({ + Key? key, + required this.authenticated, + required this.unauthenticated, + required this.unknown, + }) : super(key: key); + + final Widget Function( + BuildContext context, + UserInterface user, + Map? userData, + ) authenticated; + final Widget Function(BuildContext context) unauthenticated; + final Widget Function(BuildContext context) unknown; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state.status == AuthenticationStatus.authenticated) { + if (state.user != null) { + return authenticated(context, state.user!, state.userData); + } else { + return unauthenticated(context); + } + } else if (state.status == AuthenticationStatus.unauthenticated) { + return unauthenticated(context); + } else { + return unknown(context); + } + }, + ); + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/authentication/cubit/authentication_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/authentication/cubit/authentication_cubit.dart new file mode 100644 index 00000000..018eb566 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/authentication/cubit/authentication_cubit.dart @@ -0,0 +1,76 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart'; +import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; + +part 'authentication_state.dart'; + +class AuthenticationCubit extends Cubit { + final AuthenticationRepositoryInterface _authenticationRepository; + + StreamSubscription? _userSubscription; + + final Future> Function(UserInterface user)? + _onAuthSuccess; + + AuthenticationCubit({ + required AuthenticationRepositoryInterface authenticationRepository, + Future> Function(UserInterface user)? onAuthSuccess, + }) : _authenticationRepository = authenticationRepository, + _onAuthSuccess = onAuthSuccess, + super(const AuthenticationState.unknown()); + + Future init() async { + final _firstUser = await _authenticationRepository.user.first; + start(); + return changeStatus(_firstUser); + } + + bool start() { + _userSubscription = _authenticationRepository.user.listen(changeStatus); + return true; + } + + bool stop() { + _userSubscription?.cancel(); + return true; + } + + Future changeStatus(UserInterface user) async { + if (user.isNotEmpty) { + final Map? userData = await _onAuthSuccess?.call(user); + emit(AuthenticationState.authenticated(user, userData)); + } else { + stop(); + emit(const AuthenticationState.unauthenticated()); + } + } + + void logOut() { + unawaited(_authenticationRepository.signOut()); + } + + @override + Future close() { + _userSubscription?.cancel(); + return super.close(); + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/authentication/cubit/authentication_state.dart b/packages/wyatt_authentication_bloc/lib/src/authentication/cubit/authentication_state.dart new file mode 100644 index 00000000..4e2de515 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/authentication/cubit/authentication_state.dart @@ -0,0 +1,58 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'authentication_cubit.dart'; + +enum AuthenticationStatus { + unknown, + authenticated, + unauthenticated, +} + +class AuthenticationState extends Equatable { + final AuthenticationStatus status; + final UserInterface? user; + final Map? userData; + + const AuthenticationState._({ + required this.status, + this.user, + this.userData, + }); + + const AuthenticationState.unknown() + : this._(status: AuthenticationStatus.unknown); + + const AuthenticationState.authenticated( + UserInterface user, + Map? userData, + ) : this._( + status: AuthenticationStatus.authenticated, + user: user, + userData: userData, + ); + + const AuthenticationState.unauthenticated() + : this._(status: AuthenticationStatus.unauthenticated); + + @override + List get props => [status, user, userData]; + + @override + // ignore: lines_longer_than_80_chars + String toString() => + 'AuthenticationState(status: $status, user: $user, userData: $userData)'; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/email_verification/cubit/email_verification_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/email_verification/cubit/email_verification_cubit.dart new file mode 100644 index 00000000..fa99aeca --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/email_verification/cubit/email_verification_cubit.dart @@ -0,0 +1,69 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_status.dart'; +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart'; +import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; + +part 'email_verification_state.dart'; + +class EmailVerificationCubit extends Cubit { + final AuthenticationRepositoryInterface _authenticationRepository; + + EmailVerificationCubit(this._authenticationRepository) + : super(const EmailVerificationState()); + + Future sendEmailVerification() async { + emit(state.copyWith(status: FormStatus.submissionInProgress)); + try { + await _authenticationRepository.sendEmailVerification(); + emit(state.copyWith(status: FormStatus.submissionSuccess)); + } on SendEmailVerificationFailureInterface catch (e) { + emit( + state.copyWith( + errorMessage: e.message, + status: FormStatus.submissionFailure, + ), + ); + } catch (_) { + emit(state.copyWith(status: FormStatus.submissionFailure)); + } + } + + Future checkEmailVerification() async { + emit(state.copyWith(status: FormStatus.submissionInProgress)); + try { + await _authenticationRepository.refresh(); + emit( + state.copyWith( + isVerified: _authenticationRepository.currentUser.emailVerified, + status: FormStatus.submissionSuccess, + ), + ); + } on RefreshFailureInterface catch (e) { + emit( + state.copyWith( + errorMessage: e.message, + status: FormStatus.submissionFailure, + ), + ); + } catch (_) { + emit(state.copyWith(status: FormStatus.submissionFailure)); + } + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/email_verification/cubit/email_verification_state.dart b/packages/wyatt_authentication_bloc/lib/src/email_verification/cubit/email_verification_state.dart new file mode 100644 index 00000000..0470429e --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/email_verification/cubit/email_verification_state.dart @@ -0,0 +1,44 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'email_verification_cubit.dart'; + +class EmailVerificationState extends Equatable { + final bool isVerified; + final FormStatus status; + final String? errorMessage; + + const EmailVerificationState({ + this.isVerified = false, + this.status = FormStatus.pure, + this.errorMessage, + }); + + EmailVerificationState copyWith({ + bool? isVerified, + FormStatus? status, + String? errorMessage, + }) { + return EmailVerificationState( + isVerified: isVerified ?? this.isVerified, + status: status ?? this.status, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [isVerified, status]; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/email_verification/email_verification.dart b/packages/wyatt_authentication_bloc/lib/src/email_verification/email_verification.dart new file mode 100644 index 00000000..59beab8e --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/email_verification/email_verification.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'cubit/email_verification_cubit.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form.dart b/packages/wyatt_authentication_bloc/lib/src/form/form.dart new file mode 100644 index 00000000..62dfe624 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form.dart @@ -0,0 +1,22 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'form_data.dart'; +export 'form_entry.dart'; +export 'form_input.dart'; +export 'form_input_status.dart'; +export 'form_status.dart'; +export 'form_validator.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form_data.dart b/packages/wyatt_authentication_bloc/lib/src/form/form_data.dart new file mode 100644 index 00000000..9054233b --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form_data.dart @@ -0,0 +1,137 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/foundation.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_entry.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +@immutable +class FormData { + const FormData(this._entries); + + FormData.empty() : this([]); + + final List _entries; + + List get entries => _entries; + + List> inputs() { + return _entries + .map((FormEntry entry) => entry.input as FormInput) + .toList(); + } + + FormInput input(String field) { + if (contains(field)) { + return _entries + .firstWhere((FormEntry entry) => entry.field == field) + .input as FormInput; + } else { + throw Exception('Field $field does not exist in form'); + } + } + + bool contains(String field) { + return _entries.any((FormEntry entry) => entry.field == field); + } + + FormData intersection(FormData other) { + final List entries = []; + + for (final FormEntry entry in _entries) { + if (other.contains(entry.field)) { + entries.add(entry); + } + } + + return FormData(entries); + } + + FormData difference(FormData other) { + final List entries = []; + + for (final FormEntry otherEntry in other._entries) { + if (!contains(otherEntry.field)) { + entries.add(otherEntry); + } + } + + for (final FormEntry entry in _entries) { + if (!other.contains(entry.field)) { + entries.add(entry); + } + } + + return FormData(entries); + } + + FormData union(FormData other) { + final List entries = []; + + for (final FormEntry entry in _entries) { + entries.add(entry); + } + + for (final FormEntry otherEntry in other._entries) { + if (!contains(otherEntry.field)) { + entries.add(otherEntry); + } + } + + return FormData(entries); + } + + void update(String field, FormInput input) { + if (contains(field)) { + final index = _entries.indexOf( + _entries.firstWhere((FormEntry entry) => entry.field == field), + ); + _entries[index] = _entries[index].copyWith(input: input); + } + } + + FormData clone() { + return FormData( + _entries + .map((FormEntry entry) => entry.clone()) + .toList(), + ); + } + + Map toMap() { + final map = {}; + for (final entry in _entries) { + if (entry.export) { + map[entry.fieldName ?? entry.field] = entry.input.value; + } + } + return map; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is FormData && listEquals(other._entries, _entries); + } + + @override + int get hashCode => _entries.hashCode; + + @override + String toString() => 'FormData(entries: $_entries)'; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form_entry.dart b/packages/wyatt_authentication_bloc/lib/src/form/form_entry.dart new file mode 100644 index 00000000..2a27b9f5 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form_entry.dart @@ -0,0 +1,77 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/foundation.dart'; + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; + +@immutable +class FormEntry { + const FormEntry(this.field, this.input, {this.export = true, this.fieldName}); + + final String field; + final FormInput input; + final bool export; + final String? fieldName; + + FormEntry copyWith({ + String? field, + FormInput? input, + bool? export, + String? fieldName, + }) { + return FormEntry( + field ?? this.field, + input ?? this.input, + export: export ?? this.export, + fieldName: fieldName ?? this.fieldName, + ); + } + + FormEntry clone() { + return FormEntry( + field, + input, + export: export, + fieldName: fieldName, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is FormEntry && + other.field == field && + other.input == input && + other.export == export && + other.fieldName == fieldName; + } + + @override + int get hashCode { + return field.hashCode ^ + input.hashCode ^ + export.hashCode ^ + fieldName.hashCode; + } + + @override + String toString() { + // ignore: lines_longer_than_80_chars + return 'FormEntry(field: $field, input: $input, export: $export, fieldName: $fieldName)'; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form_input.dart b/packages/wyatt_authentication_bloc/lib/src/form/form_input.dart new file mode 100644 index 00000000..1b5cbb53 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form_input.dart @@ -0,0 +1,111 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:flutter/foundation.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_input_status.dart'; + +/// {@template form_input} +/// A [FormInput] represents the value of a single form input field. +/// It contains information about the [FormInputStatus], [value], as well +/// as validation status. +/// +/// [FormInput] should be extended to define custom [FormInput] instances. +/// +/// ```dart +/// enum FirstNameError { empty } +/// class FirstName extends FormInput { +/// const FirstName.pure({String value = ''}) : super.pure(value); +/// const FirstName.dirty({String value = ''}) : super.dirty(value); +/// +/// @override +/// FirstNameError? validator(String value) { +/// return value.isEmpty ? FirstNameError.empty : null; +/// } +/// } +/// ``` +/// {@endtemplate} +@immutable +abstract class FormInput { + const FormInput._(this.value, [this.pure = true]); + + /// Constructor which create a `pure` [FormInput] with a given value. + const FormInput.pure(T value) : this._(value); + + /// Constructor which create a `dirty` [FormInput] with a given value. + const FormInput.dirty(T value) : this._(value, false); + + /// The value of the given [FormInput]. + /// For example, if you have a `FormInput` for `FirstName`, + /// the value could be 'Joe'. + final T value; + + /// If the [FormInput] is pure (has been touched/modified). + /// Typically when the `FormInput` is initially created, + /// it is created using the `FormInput.pure` constructor to + /// signify that the user has not modified it. + /// + /// For subsequent changes (in response to user input), the + /// `FormInput.dirty` constructor should be used to signify that + /// the `FormInput` has been manipulated. + final bool pure; + + /// The [FormInputStatus] which can be one of the following: + /// * [FormInputStatus.pure] + /// - if the input has not been modified. + /// * [FormInputStatus.invalid] + /// - if the input has been modified and validation failed. + /// * [FormInputStatus.valid] + /// - if the input has been modified and validation succeeded. + FormInputStatus get status => pure + ? FormInputStatus.pure + : valid + ? FormInputStatus.valid + : FormInputStatus.invalid; + + /// Returns a validation error if the [FormInput] is invalid. + /// Returns `null` if the [FormInput] is valid. + E? get error => validator(value); + + /// Whether the [FormInput] value is valid according to the + /// overridden `validator`. + /// + /// Returns `true` if `validator` returns `null` for the + /// current [FormInput] value and `false` otherwise. + bool get valid => validator(value) == null; + + /// Whether the [FormInput] value is not valid. + /// A value is invalid when the overridden `validator` + /// returns an error (non-null value). + bool get invalid => status == FormInputStatus.invalid; + + /// A function that must return a validation error if the provided + /// [value] is invalid and `null` otherwise. + E? validator(T value); + + @override + int get hashCode => value.hashCode ^ pure.hashCode; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) return false; + return other is FormInput && + other.value == value && + other.pure == pure; + } + + @override + String toString() => '$runtimeType($value, $pure)'; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form_input_status.dart b/packages/wyatt_authentication_bloc/lib/src/form/form_input_status.dart new file mode 100644 index 00000000..f8abb53e --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form_input_status.dart @@ -0,0 +1,27 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/// Enum representing the status of a form input at any given point in time. +enum FormInputStatus { + /// The form input has not been touched. + pure, + + /// The form input is valid. + valid, + + /// The form input is not valid. + invalid, +} diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form_status.dart b/packages/wyatt_authentication_bloc/lib/src/form/form_status.dart new file mode 100644 index 00000000..8e0709be --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form_status.dart @@ -0,0 +1,79 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/// Enum representing the status of a form at any given point in time. +enum FormStatus { + /// The form has not been touched. + pure, + + /// The form has been completely validated. + valid, + + /// The form contains one or more invalid inputs. + invalid, + + /// The form is in the process of being submitted. + submissionInProgress, + + /// The form has been submitted successfully. + submissionSuccess, + + /// The form submission failed. + submissionFailure, + + /// The form submission has been canceled. + submissionCanceled +} + +const Set _validatedFormStatuses = { + FormStatus.valid, + FormStatus.submissionInProgress, + FormStatus.submissionSuccess, + FormStatus.submissionFailure, + FormStatus.submissionCanceled, +}; + +/// Useful extensions on [FormStatus] +extension FormStatusX on FormStatus { + /// Indicates whether the form is untouched. + bool get isPure => this == FormStatus.pure; + + /// Indicates whether the form is completely validated. + bool get isValid => this == FormStatus.valid; + + /// Indicates whether the form has been validated successfully. + /// This means the [FormStatus] is either: + /// * `FormStatus.valid` + /// * `FormStatus.submissionInProgress` + /// * `FormStatus.submissionSuccess` + /// * `FormStatus.submissionFailure` + bool get isValidated => _validatedFormStatuses.contains(this); + + /// Indicates whether the form contains one or more invalid inputs. + bool get isInvalid => this == FormStatus.invalid; + + /// Indicates whether the form is in the process of being submitted. + bool get isSubmissionInProgress => this == FormStatus.submissionInProgress; + + /// Indicates whether the form has been submitted successfully. + bool get isSubmissionSuccess => this == FormStatus.submissionSuccess; + + /// Indicates whether the form submission failed. + bool get isSubmissionFailure => this == FormStatus.submissionFailure; + + /// Indicates whether the form submission has been canceled. + bool get isSubmissionCanceled => this == FormStatus.submissionCanceled; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/form/form_validator.dart b/packages/wyatt_authentication_bloc/lib/src/form/form_validator.dart new file mode 100644 index 00000000..72a57b9e --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/form/form_validator.dart @@ -0,0 +1,32 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_input_status.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_status.dart'; + +/// Class which contains methods that help manipulate and manage +/// [FormStatus] and [FormInputStatus] instances. +class FormValidator { + /// Returns a [FormStatus] given a list of [FormInput]. + static FormStatus validate(List inputs) { + return inputs.every((FormInput element) => element.pure) + ? FormStatus.pure + : inputs.any((FormInput input) => input.valid == false) + ? FormStatus.invalid + : FormStatus.valid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/boolean.dart b/packages/wyatt_authentication_bloc/lib/src/models/boolean.dart new file mode 100644 index 00000000..279216dc --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/boolean.dart @@ -0,0 +1,35 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template boolean} +/// Form input for a bool input +/// {@endtemplate} +class Boolean extends FormInput { + /// {@macro boolean} + const Boolean.pure({bool? defaultValue = false}) + : super.pure(defaultValue ?? false); + + /// {@macro boolean} + const Boolean.dirty({bool value = false}) : super.dirty(value); + + @override + ValidationError? validator(bool? value) { + return value != null ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/confirmed_password.dart b/packages/wyatt_authentication_bloc/lib/src/models/confirmed_password.dart new file mode 100644 index 00000000..24cbb6d6 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/confirmed_password.dart @@ -0,0 +1,39 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template confirmed_password} +/// Form input for a confirmed password input. +/// {@endtemplate} +class ConfirmedPassword + extends FormInput { + /// {@macro confirmed_password} + const ConfirmedPassword.pure({this.password = ''}) : super.pure(''); + + /// {@macro confirmed_password} + const ConfirmedPassword.dirty({required this.password, String value = ''}) + : super.dirty(value); + + /// The original password. + final String password; + + @override + ValidationError? validator(String? value) { + return password == value ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/email.dart b/packages/wyatt_authentication_bloc/lib/src/models/email.dart new file mode 100644 index 00000000..aa9ae10d --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/email.dart @@ -0,0 +1,40 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template email} +/// Form input for an email input. +/// {@endtemplate} +class Email extends FormInput { + /// {@macro email} + const Email.pure() : super.pure(''); + + /// {@macro email} + const Email.dirty([String value = '']) : super.dirty(value); + + static final RegExp _emailRegExp = RegExp( + r'^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$', + ); + + @override + ValidationError? validator(String? value) { + return _emailRegExp.hasMatch(value ?? '') + ? null + : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions.dart b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions.dart new file mode 100644 index 00000000..75da2bc7 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions.dart @@ -0,0 +1,18 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'exceptions_firebase.dart'; +export 'exceptions_interface.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart new file mode 100644 index 00000000..9a842cbc --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_firebase.dart @@ -0,0 +1,252 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart'; + +class ApplyActionCodeFailureFirebase extends ApplyActionCodeFailureInterface { + ApplyActionCodeFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + ApplyActionCodeFailureFirebase.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'expired-action-code': + message = 'Action code has expired.'; + break; + case 'invalid-action-code': + message = 'Action code is invalid.'; + break; + case 'user-disabled': + message = + 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + message = 'Email is not found, please create an account.'; + break; + + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class SignUpWithEmailAndPasswordFailureFirebase + extends SignUpWithEmailAndPasswordFailureInterface { + SignUpWithEmailAndPasswordFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + SignUpWithEmailAndPasswordFailureFirebase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + message = 'The email address is badly formatted.'; + break; + case 'user-disabled': + message = + 'This user has been disabled. Please contact support for help.'; + break; + case 'email-already-in-use': + message = 'An account already exists for that email.'; + break; + case 'operation-not-allowed': + message = 'Operation is not allowed. Please contact support.'; + break; + case 'weak-password': + message = 'Please enter a stronger password.'; + break; + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class FetchSignInMethodsForEmailFailureFirebase + extends FetchSignInMethodsForEmailFailureInterface { + FetchSignInMethodsForEmailFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + FetchSignInMethodsForEmailFailureFirebase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + message = 'The email address is badly formatted.'; + break; + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class SignInAnonymouslyFailureFirebase + extends SignInAnonymouslyFailureInterface { + SignInAnonymouslyFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + SignInAnonymouslyFailureFirebase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'operation-not-allowed': + message = 'Operation is not allowed. Please contact support.'; + break; + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class SignInWithGoogleFailureFirebase extends SignInWithGoogleFailureInterface { + SignInWithGoogleFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + SignInWithGoogleFailureFirebase.fromCode(String code) : super.fromCode(code) { + switch (code) { + case 'account-exists-with-different-credential': + message = 'Account exists with different credentials.'; + break; + case 'invalid-credential': + message = 'The credential received is malformed or has expired.'; + break; + case 'operation-not-allowed': + message = 'Operation is not allowed. Please contact support.'; + break; + case 'user-disabled': + message = + 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + message = 'Email is not found, please create an account.'; + break; + case 'wrong-password': + message = 'Incorrect password, please try again.'; + break; + case 'invalid-verification-code': + message = 'The credential verification code received is invalid.'; + break; + case 'invalid-verification-id': + message = 'The credential verification ID received is invalid.'; + break; + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class SignInWithEmailLinkFailureFirebase + extends SignInWithEmailLinkFailureInterface { + SignInWithEmailLinkFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + SignInWithEmailLinkFailureFirebase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'expired-action-code': + message = 'Action code has expired.'; + break; + case 'invalid-email': + message = 'Email is not valid or badly formatted.'; + break; + case 'user-disabled': + message = + 'This user has been disabled. Please contact support for help.'; + break; + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class SignInWithEmailAndPasswordFailureFirebase + extends SignInWithEmailAndPasswordFailureInterface { + SignInWithEmailAndPasswordFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + SignInWithEmailAndPasswordFailureFirebase.fromCode(String code) + : super.fromCode(code) { + switch (code) { + case 'invalid-email': + message = 'Email is not valid or badly formatted.'; + break; + case 'user-disabled': + message = + 'This user has been disabled. Please contact support for help.'; + break; + case 'user-not-found': + message = 'Email is not found, please create an account.'; + break; + case 'wrong-password': + message = 'Incorrect password, please try again.'; + break; + default: + this.code = 'unknown'; + message = 'An unknown error occurred.'; + } + } +} + +class SendEmailVerificationFailureFirebase + extends SendEmailVerificationFailureInterface { + SendEmailVerificationFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + + SendEmailVerificationFailureFirebase.fromCode(String code) + : super.fromCode(code); +} + +class SendPasswordResetEmailFailureFirebase + extends SendPasswordResetEmailFailureInterface { + SendPasswordResetEmailFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + SendPasswordResetEmailFailureFirebase.fromCode(String code) + : super.fromCode(code); +} + +class SendSignInLinkEmailFailureFirebase + extends SendSignInLinkEmailFailureInterface { + SendSignInLinkEmailFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + + SendSignInLinkEmailFailureFirebase.fromCode(String code) + : super.fromCode(code); +} + +class ConfirmPasswordResetFailureFirebase + extends ConfirmPasswordResetFailureInterface { + ConfirmPasswordResetFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + + ConfirmPasswordResetFailureFirebase.fromCode(String code) + : super.fromCode(code); +} + +class VerifyPasswordResetCodeFailureFirebase + extends VerifyPasswordResetCodeFailureInterface { + VerifyPasswordResetCodeFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + + VerifyPasswordResetCodeFailureFirebase.fromCode(String code) + : super.fromCode(code); +} + +class RefreshFailureFirebase extends RefreshFailureInterface { + RefreshFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + RefreshFailureFirebase.fromCode(String code) : super.fromCode(code); +} + +class SignOutFailureFirebase extends SignOutFailureInterface { + SignOutFailureFirebase([String? code, String? message]) + : super(code ?? 'unknown', message ?? 'An unknown error occurred.'); + + SignOutFailureFirebase.fromCode(String code) : super.fromCode(code); +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart new file mode 100644 index 00000000..bb3e9b44 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/exceptions/exceptions_interface.dart @@ -0,0 +1,212 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +abstract class AuthenticationFailureInterface implements Exception { + String code; + String message; + + AuthenticationFailureInterface(this.code, this.message); + AuthenticationFailureInterface.fromCode(this.code) + : message = 'An unknown error occurred.'; +} + +/// {@template apply_action_code_failure} +/// Thrown if during the apply action code process if a failure occurs. +/// {@endtemplate} +abstract class ApplyActionCodeFailureInterface + extends AuthenticationFailureInterface { + /// {@macro apply_action_code_failure} + ApplyActionCodeFailureInterface(String code, String message) + : super(code, message); + + /// {@macro apply_action_code_failure} + ApplyActionCodeFailureInterface.fromCode(String code) : super.fromCode(code); +} + +/// {@template sign_up_with_email_and_password_failure} +/// Thrown if during the sign up process if a failure occurs. +/// {@endtemplate} +abstract class SignUpWithEmailAndPasswordFailureInterface + extends AuthenticationFailureInterface { + /// {@macro sign_up_with_email_and_password_failure} + SignUpWithEmailAndPasswordFailureInterface(String code, String message) + : super(code, message); + + /// {@macro sign_up_with_email_and_password_failure} + SignUpWithEmailAndPasswordFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template fetch_sign_in_methods_failure} +/// Thrown during the fetch sign in methods if a failure occurs. +/// {@endtemplate} +abstract class FetchSignInMethodsForEmailFailureInterface + extends AuthenticationFailureInterface { + /// {@macro fetch_sign_in_methods_failure} + FetchSignInMethodsForEmailFailureInterface(String code, String message) + : super(code, message); + + /// {@macro fetch_sign_in_methods_failure} + FetchSignInMethodsForEmailFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template sign_in_anonymously_failure} +/// Thrown during the sign in process if a failure occurs. +/// {@endtemplate} +abstract class SignInAnonymouslyFailureInterface + extends AuthenticationFailureInterface { + /// {@macro sign_in_anonymously_failure} + SignInAnonymouslyFailureInterface(String code, String message) + : super(code, message); + + /// {@macro sign_in_anonymously_failure} + SignInAnonymouslyFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template sign_in_with_google_failure} +/// Thrown during the sign in process if a failure occurs. +/// {@endtemplate} +abstract class SignInWithGoogleFailureInterface + extends AuthenticationFailureInterface { + /// {@macro sign_in_with_google_failure} + SignInWithGoogleFailureInterface(String code, String message) + : super(code, message); + + /// {@macro sign_in_with_google_failure} + SignInWithGoogleFailureInterface.fromCode(String code) : super.fromCode(code); +} + +/// {@template sign_in_with_email_link_failure} +/// Thrown during the sign in process if a failure occurs. +/// {@endtemplate} +abstract class SignInWithEmailLinkFailureInterface + extends AuthenticationFailureInterface { + /// {@macro sign_in_with_email_link_failure} + SignInWithEmailLinkFailureInterface(String code, String message) + : super(code, message); + + /// {@macro sign_in_with_email_link_failure} + SignInWithEmailLinkFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template sign_in_with_email_and_password_failure} +/// Thrown during the sign in process if a failure occurs. +/// {@endtemplate} +abstract class SignInWithEmailAndPasswordFailureInterface + extends AuthenticationFailureInterface { + /// {@macro sign_in_with_email_and_password_failure} + SignInWithEmailAndPasswordFailureInterface(String code, String message) + : super(code, message); + + /// {@macro sign_in_with_email_and_password_failure} + SignInWithEmailAndPasswordFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template send_email_verification_failure} +/// Thrown during the email verification process if a failure occurs. +/// {@endtemplate} +abstract class SendEmailVerificationFailureInterface + extends AuthenticationFailureInterface { + /// {@macro send_email_verification_failure} + SendEmailVerificationFailureInterface(String code, String message) + : super(code, message); + + /// {@macro send_email_verification_failure} + SendEmailVerificationFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template send_password_reset_email_failure} +/// Thrown during the password reset process if a failure occurs. +/// {@endtemplate} +abstract class SendPasswordResetEmailFailureInterface + extends AuthenticationFailureInterface { + /// {@macro send_password_reset_email_failure} + SendPasswordResetEmailFailureInterface(String code, String message) + : super(code, message); + + /// {@macro send_password_reset_email_failure} + SendPasswordResetEmailFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template send_sign_in_link_email_failure} +/// Thrown during the sign in link process if a failure occurs. +/// {@endtemplate} +abstract class SendSignInLinkEmailFailureInterface + extends AuthenticationFailureInterface { + /// {@macro send_sign_in_link_email_failure} + SendSignInLinkEmailFailureInterface(String code, String message) + : super(code, message); + + /// {@macro send_sign_in_link_email_failure} + SendSignInLinkEmailFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template confirm_password_reset_failure} +/// Thrown during the password reset process if a failure occurs. +/// {@endtemplate} +abstract class ConfirmPasswordResetFailureInterface + extends AuthenticationFailureInterface { + /// {@macro confirm_password_reset_failure} + ConfirmPasswordResetFailureInterface(String code, String message) + : super(code, message); + + /// {@macro confirm_password_reset_failure} + ConfirmPasswordResetFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template verify_password_reset_code_failure} +/// Thrown during the password reset process if a failure occurs. +/// {@endtemplate} +abstract class VerifyPasswordResetCodeFailureInterface + extends AuthenticationFailureInterface { + /// {@macro verify_password_reset_code_failure} + VerifyPasswordResetCodeFailureInterface(String code, String message) + : super(code, message); + + /// {@macro verify_password_reset_code_failure} + VerifyPasswordResetCodeFailureInterface.fromCode(String code) + : super.fromCode(code); +} + +/// {@template refresh_failure} +/// Thrown during the refresh process if a failure occurs. +/// {@endtemplate} +abstract class RefreshFailureInterface extends AuthenticationFailureInterface { + /// {@macro refresh_failure} + RefreshFailureInterface(String code, String message) : super(code, message); + + /// {@macro refresh_failure} + RefreshFailureInterface.fromCode(String code) : super.fromCode(code); +} + +/// {@template sign_out_failure} +/// Thrown during the sign out process if a failure occurs. +/// {@endtemplate} +abstract class SignOutFailureInterface extends AuthenticationFailureInterface { + /// {@macro sign_out_failure} + SignOutFailureInterface(String code, String message) : super(code, message); + + /// {@macro sign_out_failure} + SignOutFailureInterface.fromCode(String code) : super.fromCode(code); +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/iban.dart b/packages/wyatt_authentication_bloc/lib/src/models/iban.dart new file mode 100644 index 00000000..cdca734b --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/iban.dart @@ -0,0 +1,38 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template iban} +/// Form input for an IBAN input. +/// {@endtemplate} +class Iban extends FormInput { + /// {@macro iban} + const Iban.pure() : super.pure(''); + + /// {@macro iban} + const Iban.dirty([String value = '']) : super.dirty(value); + + static final RegExp _regExp = RegExp( + r'^(?:((?:IT|SM)\d{2}[A-Z]{1}\d{22})|(NL\d{2}[A-Z]{4}\d{10})|(LV\d{2}[A-Z]{4}\d{13})|((?:BG|GB|IE)\d{2}[A-Z]{4}\d{14})|(GI\d{2}[A-Z]{4}\d{15})|(RO\d{2}[A-Z]{4}\d{16})|(MT\d{2}[A-Z]{4}\d{23})|(NO\d{13})|((?:DK|FI)\d{16})|((?:SI)\d{17})|((?:AT|EE|LU|LT)\d{18})|((?:HR|LI|CH)\d{19})|((?:DE|VA)\d{20})|((?:AD|CZ|ES|MD|SK|SE)\d{22})|(PT\d{23})|((?:IS)\d{24})|((?:BE)\d{14})|((?:FR|MC|GR)\d{25})|((?:PL|HU|CY)\d{26}))$', + ); + + @override + ValidationError? validator(String? value) { + return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/models.dart b/packages/wyatt_authentication_bloc/lib/src/models/models.dart new file mode 100644 index 00000000..310b85d7 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/models.dart @@ -0,0 +1,29 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'boolean.dart'; +export 'confirmed_password.dart'; +export 'email.dart'; +export 'exceptions/exceptions.dart'; +export 'iban.dart'; +export 'name.dart'; +export 'operations.dart'; +export 'password.dart'; +export 'phone.dart'; +export 'siren.dart'; +export 'text_string.dart'; +export 'user/user.dart'; +export 'validation_error.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/models/name.dart b/packages/wyatt_authentication_bloc/lib/src/models/name.dart new file mode 100644 index 00000000..727ef6c9 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/name.dart @@ -0,0 +1,36 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template name} +/// Form input for a name input. +/// {@endtemplate} +class Name extends FormInput { + /// {@macro name} + const Name.pure() : super.pure(''); + + /// {@macro name} + const Name.dirty([String value = '']) : super.dirty(value); + + static final RegExp _nameRegExp = RegExp(r"^([ \u00c0-\u01ffa-zA-Z'\-])+$"); + + @override + ValidationError? validator(String? value) { + return _nameRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/operations.dart b/packages/wyatt_authentication_bloc/lib/src/models/operations.dart new file mode 100644 index 00000000..09658a52 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/operations.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +enum SetOperation { replace, intersection, difference, union } diff --git a/packages/wyatt_authentication_bloc/lib/src/models/password.dart b/packages/wyatt_authentication_bloc/lib/src/models/password.dart new file mode 100644 index 00000000..a79b4a87 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/password.dart @@ -0,0 +1,39 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template password} +/// Form input for a password input. +/// {@endtemplate} +class Password extends FormInput { + /// {@macro password} + const Password.pure() : super.pure(''); + + /// {@macro password} + const Password.dirty([String value = '']) : super.dirty(value); + + static final RegExp _passwordRegExp = + RegExp(r'^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$'); + + @override + ValidationError? validator(String? value) { + return _passwordRegExp.hasMatch(value ?? '') + ? null + : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/phone.dart b/packages/wyatt_authentication_bloc/lib/src/models/phone.dart new file mode 100644 index 00000000..ebd81d8c --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/phone.dart @@ -0,0 +1,37 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template phone} +/// Form input for a phone input. +/// {@endtemplate} +class Phone extends FormInput { + /// {@macro phone} + const Phone.pure() : super.pure(''); + + /// {@macro phone} + const Phone.dirty([String value = '']) : super.dirty(value); + + static final RegExp _phoneRegExp = + RegExp(r'^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$'); + + @override + ValidationError? validator(String? value) { + return _phoneRegExp.hasMatch(value ?? '') ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/siren.dart b/packages/wyatt_authentication_bloc/lib/src/models/siren.dart new file mode 100644 index 00000000..8e67335b --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/siren.dart @@ -0,0 +1,36 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template siren} +/// Form input for a SIREN input. +/// {@endtemplate} +class Siren extends FormInput { + /// {@macro siren} + const Siren.pure() : super.pure(''); + + /// {@macro siren} + const Siren.dirty([String value = '']) : super.dirty(value); + + static final RegExp _regExp = RegExp(r'(\d{9}|\d{3}[ ]\d{3}[ ]\d{3})$'); + + @override + ValidationError? validator(String? value) { + return _regExp.hasMatch(value ?? '') ? null : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/text_string.dart b/packages/wyatt_authentication_bloc/lib/src/models/text_string.dart new file mode 100644 index 00000000..c461ce9a --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/text_string.dart @@ -0,0 +1,36 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; + +/// {@template text_string} +/// Form input for a text input +/// {@endtemplate} +class TextString extends FormInput { + /// {@macro text_string} + const TextString.pure() : super.pure(''); + + /// {@macro text_string} + const TextString.dirty([String value = '']) : super.dirty(value); + + @override + ValidationError? validator(String? value) { + return (value?.isNotEmpty ?? false) + ? null + : ValidationError.invalid; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/user/user.dart b/packages/wyatt_authentication_bloc/lib/src/models/user/user.dart new file mode 100644 index 00000000..2a470388 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/user/user.dart @@ -0,0 +1,18 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'user_firebase.dart'; +export 'user_interface.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart new file mode 100644 index 00000000..df1b78bc --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/user/user_firebase.dart @@ -0,0 +1,80 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:firebase_auth/firebase_auth.dart'; + +import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart'; + +class UserFirebase implements UserInterface { + final User? _user; + + const UserFirebase(User user) : _user = user; + + User? get inner => _user; + + @override + const UserFirebase.empty() : _user = null; + + @override + DateTime? get creationTime => _user?.metadata.creationTime; + + @override + String? get displayName => _user?.displayName; + + @override + String? get email => _user?.email; + + @override + bool get emailVerified => _user?.emailVerified ?? false; + + @override + bool get isAnonymous => _user?.isAnonymous ?? false; + + @override + bool get isEmpty => _user == null; + + @override + bool get isNotEmpty => _user != null; + + @override + DateTime? get lastSignInTime => _user?.metadata.lastSignInTime; + + @override + String? get phoneNumber => _user?.phoneNumber; + + @override + String? get photoURL => _user?.photoURL; + + @override + String? get refreshToken => _user?.refreshToken; + + @override + String get uid => _user?.uid ?? ''; + + @override + bool? get isNewUser { + if (_user?.metadata.lastSignInTime == null || + _user?.metadata.creationTime == null) { + return null; + } else { + return _user?.metadata.lastSignInTime == _user?.metadata.creationTime; + } + } + + @override + // ignore: lines_longer_than_80_chars + String toString() => 'UserFirebase(creationTime: $creationTime, displayName: $displayName, email: $email, emailVerified: $emailVerified, isAnonymous: $isAnonymous, lastSignInTime: $lastSignInTime, phoneNumber: $phoneNumber, photoURL: $photoURL, refreshToken: $refreshToken, uid: $uid)'; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart b/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart new file mode 100644 index 00000000..5bbcc3f7 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/user/user_interface.dart @@ -0,0 +1,82 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +abstract class UserInterface { + /// The empty user constructor. + const UserInterface.empty(); + + /// The users display name. + /// + /// Will be `null` if signing in anonymously or via password authentication. + String? get displayName; + + /// The users email address. + /// + /// Will be `null` if signing in anonymously. + String? get email; + + /// Returns whether the users email address has been verified. + /// + /// To send a verification email, see `SendEmailVerification`. + /// + /// Once verified, call `reload` to ensure the latest user information is + /// retrieved from Firebase. + bool get emailVerified; + + /// Returns whether the user is a anonymous. + bool get isAnonymous; + + /// Returns the users account creation time. + /// + /// When this account was created as dictated by the server clock. + DateTime? get creationTime; + + /// When the user last signed in as dictated by the server clock. + /// + /// This is only accurate up to a granularity of 2 minutes for consecutive + /// sign-in attempts. + DateTime? get lastSignInTime; + + /// Returns the users phone number. + /// + /// This property will be `null` if the user has not signed in or been has + /// their phone number linked. + String? get phoneNumber; + + /// Returns a photo URL for the user. + /// + /// This property will be populated if the user has signed in or been linked + /// with a 3rd party OAuth provider (such as Google). + String? get photoURL; + + /// Returns a JWT refresh token for the user. + /// + /// This property maybe `null` or empty if the underlying platform does not + /// support providing refresh tokens. + String? get refreshToken; + + /// The user's unique ID. + String get uid; + + /// Whether the user account has been recently created. + bool? get isNewUser; + + /// Convenience getter to determine whether the current user is empty. + bool get isEmpty; + + /// Convenience getter to determine whether the current user is not empty. + bool get isNotEmpty; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/models/validation_error.dart b/packages/wyatt_authentication_bloc/lib/src/models/validation_error.dart new file mode 100644 index 00000000..d5ae6088 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/models/validation_error.dart @@ -0,0 +1,20 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +enum ValidationError { + /// Generic invalid error. + invalid +} diff --git a/packages/wyatt_authentication_bloc/lib/src/password_reset/cubit/password_reset_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/password_reset/cubit/password_reset_cubit.dart new file mode 100644 index 00000000..179adb31 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/password_reset/cubit/password_reset_cubit.dart @@ -0,0 +1,62 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_status.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_validator.dart'; +import 'package:wyatt_authentication_bloc/src/models/email.dart'; +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart'; +import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; + +part 'password_reset_state.dart'; + +class PasswordResetCubit extends Cubit { + final AuthenticationRepositoryInterface _authenticationRepository; + + PasswordResetCubit(this._authenticationRepository) + : super(const PasswordResetState()); + + void emailChanged(String value) { + final Email email = Email.dirty(value); + emit( + state.copyWith( + email: email, + status: FormValidator.validate([email]), + ), + ); + } + + Future sendPasswordResetEmail() async { + if (!state.status.isValidated) return; + emit(state.copyWith(status: FormStatus.submissionInProgress)); + try { + await _authenticationRepository.sendPasswordResetEmail( + email: state.email.value, + ); + emit(state.copyWith(status: FormStatus.submissionSuccess)); + } on SendPasswordResetEmailFailureInterface catch (e) { + emit( + state.copyWith( + errorMessage: e.message, + status: FormStatus.submissionFailure, + ), + ); + } catch (_) { + emit(state.copyWith(status: FormStatus.submissionFailure)); + } + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/password_reset/cubit/password_reset_state.dart b/packages/wyatt_authentication_bloc/lib/src/password_reset/cubit/password_reset_state.dart new file mode 100644 index 00000000..5d3d7179 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/password_reset/cubit/password_reset_state.dart @@ -0,0 +1,44 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'password_reset_cubit.dart'; + +class PasswordResetState extends Equatable { + final Email email; + final FormStatus status; + final String? errorMessage; + + const PasswordResetState({ + this.email = const Email.pure(), + this.status = FormStatus.pure, + this.errorMessage, + }); + + PasswordResetState copyWith({ + Email? email, + FormStatus? status, + String? errorMessage, + }) { + return PasswordResetState( + email: email ?? this.email, + status: status ?? this.status, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [email, status]; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/password_reset/password_reset.dart b/packages/wyatt_authentication_bloc/lib/src/password_reset/password_reset.dart new file mode 100644 index 00000000..43919e02 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/password_reset/password_reset.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'cubit/password_reset_cubit.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart new file mode 100644 index 00000000..20f6e2e4 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_firebase.dart @@ -0,0 +1,226 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_firebase.dart'; +import 'package:wyatt_authentication_bloc/src/models/user/user_firebase.dart'; +import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart'; +import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; + +class AuthenticationRepositoryFirebase + implements AuthenticationRepositoryInterface { + final FirebaseAuth _firebaseAuth; + + UserFirebase _userCache = const UserFirebase.empty(); + + AuthenticationRepositoryFirebase({FirebaseAuth? firebaseAuth}) + : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance; + + @override + Stream get user { + return _firebaseAuth.userChanges().map((User? firebaseUser) { + final UserFirebase user = firebaseUser == null + ? const UserFirebase.empty() + : firebaseUser.model; + _userCache = user; + return user; + }); + } + + @override + UserInterface get currentUser { + return _userCache; + } + + @override + Future applyActionCode(String code) async { + try { + await _firebaseAuth.applyActionCode(code); + } on FirebaseAuthException catch (e) { + throw ApplyActionCodeFailureFirebase.fromCode(e.code); + } catch (_) { + throw ApplyActionCodeFailureFirebase(); + } + } + + @override + Future signUp({ + required String email, + required String password, + }) async { + try { + final creds = await _firebaseAuth.createUserWithEmailAndPassword( + email: email, + password: password, + ); + return creds.user?.uid; + } on FirebaseAuthException catch (e) { + throw SignUpWithEmailAndPasswordFailureFirebase.fromCode(e.code); + } catch (_) { + throw SignUpWithEmailAndPasswordFailureFirebase(); + } + } + + @override + Future> fetchSignInMethodsForEmail({ + required String email, + }) async { + try { + return await _firebaseAuth.fetchSignInMethodsForEmail(email); + } on FirebaseAuthException catch (e) { + throw FetchSignInMethodsForEmailFailureFirebase.fromCode(e.code); + } catch (_) { + throw FetchSignInMethodsForEmailFailureFirebase(); + } + } + + @override + Future signInAnonymously() async { + try { + await _firebaseAuth.signInAnonymously(); + } on FirebaseAuthException catch (e) { + throw SignInAnonymouslyFailureFirebase.fromCode(e.code); + } catch (_) { + throw SignInAnonymouslyFailureFirebase(); + } + } + + @override + Future signInWithGoogle() { + // TODO(hpcl): implement signInWithGoogle + throw UnimplementedError(); + } + + @override + Future signInWithEmailLink(String email, String emailLink) async { + try { + await _firebaseAuth.signInWithEmailLink( + email: email, + emailLink: emailLink, + ); + } on FirebaseAuthException catch (e) { + throw SignInWithEmailLinkFailureFirebase.fromCode(e.code); + } catch (_) { + throw SignInWithEmailLinkFailureFirebase(); + } + } + + @override + Future signInWithEmailAndPassword({ + required String email, + required String password, + }) async { + try { + await _firebaseAuth.signInWithEmailAndPassword( + email: email, + password: password, + ); + } on FirebaseAuthException catch (e) { + throw SignInWithEmailAndPasswordFailureFirebase.fromCode(e.code); + } catch (_) { + throw SignInWithEmailAndPasswordFailureFirebase(); + } + } + + @override + Future sendEmailVerification() async { + try { + await _userCache.inner!.sendEmailVerification(); + } catch (e) { + throw SendEmailVerificationFailureFirebase(); + } + } + + @override + Future sendPasswordResetEmail({required String email}) async { + try { + await _firebaseAuth.sendPasswordResetEmail(email: email); + } on FirebaseAuthException catch (e) { + throw SendPasswordResetEmailFailureFirebase.fromCode(e.code); + } catch (_) { + throw SendPasswordResetEmailFailureFirebase(); + } + } + + @override + Future sendSignInLinkEmail({required String email}) async { + try { + // TODO(hpcl): implement sendSignInLinkEmail + } on FirebaseAuthException catch (e) { + throw SendSignInLinkEmailFailureFirebase.fromCode(e.code); + } catch (_) { + throw SendSignInLinkEmailFailureFirebase(); + } + } + + @override + Future confirmPasswordReset({ + required String code, + required String newPassword, + }) async { + try { + await _firebaseAuth.confirmPasswordReset( + code: code, + newPassword: newPassword, + ); + } on FirebaseAuthException catch (e) { + throw ConfirmPasswordResetFailureFirebase.fromCode(e.code); + } catch (_) { + throw ConfirmPasswordResetFailureFirebase(); + } + throw UnimplementedError(); + } + + @override + Future verifyPasswordResetCode({required String code}) async { + try { + await _firebaseAuth.verifyPasswordResetCode(code); + } on FirebaseAuthException catch (e) { + throw VerifyPasswordResetCodeFailureFirebase.fromCode(e.code); + } catch (_) { + throw VerifyPasswordResetCodeFailureFirebase(); + } + } + + @override + Future signOut() async { + try { + await Future.wait([ + _firebaseAuth.signOut(), + ]); + _userCache = const UserFirebase.empty(); + } catch (_) { + throw SignOutFailureFirebase(); + } + } + + @override + Future refresh() async { + try { + await _userCache.inner!.reload(); + } on FirebaseAuthException catch (e) { + throw RefreshFailureFirebase.fromCode(e.code); + } catch (_) { + throw RefreshFailureFirebase(); + } + } +} + +extension on User { + UserFirebase get model { + return UserFirebase(this); + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart new file mode 100644 index 00000000..ecdcd03b --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/repositories/authentication_repository_interface.dart @@ -0,0 +1,117 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart'; +import 'package:wyatt_authentication_bloc/src/models/user/user_interface.dart'; + +/// {@template authentication_repository} +/// Repository which manages user authentication. +/// {@endtemplate} +abstract class AuthenticationRepositoryInterface { + /// Stream of [UserInterface] which will emit the current user when + /// the authentication state changes. + /// + /// Emits [UserInterface.empty] if the user is not authenticated. + Stream get user; + + /// Returns the current cached account. + /// Defaults to [UserInterface.empty] if there is no cached user. + UserInterface get currentUser; + + /// Applies action code + /// + /// [code] - The action code sent to the user's email address. + /// Throw [ApplyActionCodeFailureInterface] if an exception occurs. + Future applyActionCode(String code); + + /// Creates a new user with the provided [email] and [password]. + /// + /// Returns the newly created user's unique identifier. + /// + /// Throws a [SignUpWithEmailAndPasswordFailureInterface] if + /// an exception occurs. + Future signUp({required String email, required String password}); + + /// Fetches sign in methods for [email]. + /// + /// Throws a [FetchSignInMethodsForEmailFailureInterface] if + /// an exception occurs. + Future> fetchSignInMethodsForEmail({required String email}); + + /// Sign in anonimously. + /// + /// Throws a [SignInAnonymouslyFailureInterface] if an exception occurs. + Future signInAnonymously(); + + /// Starts the Sign In with Google Flow. + /// + /// Throws a [SignInWithGoogleFailureInterface] if an exception occurs. + Future signInWithGoogle(); + + /// Signs in using an email address and email sign-in link. + /// + /// Throws a [SignInWithEmailLinkFailureInterface] if an exception occurs. + Future signInWithEmailLink( + String email, + String emailLink, + ); + + /// Signs in with the provided [email] and [password]. + /// + /// Throws a [SignInWithEmailAndPasswordFailureInterface] if + /// an exception occurs. + Future signInWithEmailAndPassword({ + required String email, + required String password, + }); + + /// Sends verification email to the provided [user]. + /// + /// Throws a [SendEmailVerificationFailureInterface] if an exception occurs. + Future sendEmailVerification(); + + /// Sends a password reset email to the provided [email]. + /// + /// Throws a [SendPasswordResetEmailFailureInterface] if an exception occurs. + Future sendPasswordResetEmail({required String email}); + + /// Sends link to login. + /// + /// Throws a [SendSignInLinkEmailFailureInterface] if an exception occurs. + Future sendSignInLinkEmail({required String email}); + + /// Confirms the password reset with the provided [newPassword] and [code]. + /// + /// Throws a [ConfirmPasswordResetFailureInterface] if an exception occurs. + Future confirmPasswordReset({ + required String code, + required String newPassword, + }); + + /// Verify password reset code. + /// + /// Throws a [VerifyPasswordResetCodeFailureInterface] if an exception occurs. + Future verifyPasswordResetCode({required String code}); + + /// Signs out the current user which will emit + /// [UserInterface.empty] from the [user] Stream. + Future signOut(); + + /// Refreshes the current user. + /// + /// Throws a [RefreshFailureInterface] if an exception occurs. + Future refresh(); +} diff --git a/packages/wyatt_authentication_bloc/lib/src/repositories/repositories.dart b/packages/wyatt_authentication_bloc/lib/src/repositories/repositories.dart new file mode 100644 index 00000000..2f4f5294 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/repositories/repositories.dart @@ -0,0 +1,18 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'authentication_repository_firebase.dart'; +export 'authentication_repository_interface.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart new file mode 100644 index 00000000..93d649f0 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_cubit.dart @@ -0,0 +1,104 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:wyatt_authentication_bloc/src/authentication/cubit/authentication_cubit.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_status.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_validator.dart'; +import 'package:wyatt_authentication_bloc/src/models/email.dart'; +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart'; +import 'package:wyatt_authentication_bloc/src/models/password.dart'; +import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; + +part 'sign_in_state.dart'; + +class SignInCubit extends Cubit { + final AuthenticationRepositoryInterface _authenticationRepository; + final AuthenticationCubit _authenticationCubit; + + SignInCubit({ + required AuthenticationRepositoryInterface authenticationRepository, + required AuthenticationCubit authenticationCubit, + }) : _authenticationRepository = authenticationRepository, + _authenticationCubit = authenticationCubit, + super(const SignInState()); + + void emailChanged(String value) { + final Email email = Email.dirty(value); + emit( + state.copyWith( + email: email, + status: FormValidator.validate([email, state.password]), + ), + ); + } + + void passwordChanged(String value) { + final Password password = Password.dirty(value); + emit( + state.copyWith( + password: password, + status: FormValidator.validate([state.email, password]), + ), + ); + } + + Future signInAnonymously() async { + // TODO(hpcl): Maybe check this in UI. + if (state.status.isSubmissionInProgress) { + return; + } + + emit(state.copyWith(status: FormStatus.submissionInProgress)); + try { + await _authenticationRepository.signInAnonymously(); + _authenticationCubit.start(); + emit(state.copyWith(status: FormStatus.submissionSuccess)); + } on SignInAnonymouslyFailureInterface catch (e) { + emit( + state.copyWith( + errorMessage: e.message, + status: FormStatus.submissionFailure, + ), + ); + } catch (_) { + emit(state.copyWith(status: FormStatus.submissionFailure)); + } + } + + Future signInWithEmailAndPassword() async { + if (!state.status.isValidated) return; + emit(state.copyWith(status: FormStatus.submissionInProgress)); + try { + await _authenticationRepository.signInWithEmailAndPassword( + email: state.email.value, + password: state.password.value, + ); + _authenticationCubit.start(); + emit(state.copyWith(status: FormStatus.submissionSuccess)); + } on SignInWithEmailAndPasswordFailureInterface catch (e) { + emit( + state.copyWith( + errorMessage: e.message, + status: FormStatus.submissionFailure, + ), + ); + } catch (_) { + emit(state.copyWith(status: FormStatus.submissionFailure)); + } + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart new file mode 100644 index 00000000..f6cfb863 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/sign_in/cubit/sign_in_state.dart @@ -0,0 +1,48 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'sign_in_cubit.dart'; + +class SignInState extends Equatable { + final Email email; + final Password password; + final FormStatus status; + final String? errorMessage; + + const SignInState({ + this.email = const Email.pure(), + this.password = const Password.pure(), + this.status = FormStatus.pure, + this.errorMessage, + }); + + SignInState copyWith({ + Email? email, + Password? password, + FormStatus? status, + String? errorMessage, + }) { + return SignInState( + email: email ?? this.email, + password: password ?? this.password, + status: status ?? this.status, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [email, password, status]; +} diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_in/sign_in.dart b/packages/wyatt_authentication_bloc/lib/src/sign_in/sign_in.dart new file mode 100644 index 00000000..77700ab6 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/sign_in/sign_in.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'cubit/sign_in_cubit.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_up/cubit/sign_up_cubit.dart b/packages/wyatt_authentication_bloc/lib/src/sign_up/cubit/sign_up_cubit.dart new file mode 100644 index 00000000..27a20cb1 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/sign_up/cubit/sign_up_cubit.dart @@ -0,0 +1,166 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'dart:async'; + +import 'package:bloc/bloc.dart'; +import 'package:flutter/foundation.dart'; +import 'package:wyatt_authentication_bloc/src/authentication/cubit/authentication_cubit.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_data.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_input.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_status.dart'; +import 'package:wyatt_authentication_bloc/src/form/form_validator.dart'; +import 'package:wyatt_authentication_bloc/src/models/email.dart'; +import 'package:wyatt_authentication_bloc/src/models/exceptions/exceptions_interface.dart'; +import 'package:wyatt_authentication_bloc/src/models/operations.dart'; +import 'package:wyatt_authentication_bloc/src/models/password.dart'; +import 'package:wyatt_authentication_bloc/src/models/validation_error.dart'; +import 'package:wyatt_authentication_bloc/src/repositories/authentication_repository_interface.dart'; + +part 'sign_up_state.dart'; + +class SignUpCubit extends Cubit { + final AuthenticationRepositoryInterface _authenticationRepository; + final AuthenticationCubit _authenticationCubit; + + final Future Function(SignUpState state, String? uid)? _onSignUpSuccess; + + SignUpCubit({ + required AuthenticationRepositoryInterface authenticationRepository, + required AuthenticationCubit authenticationCubit, + required FormData entries, + Future Function(SignUpState state, String? uid)? onSignUpSuccess, + }) : _authenticationRepository = authenticationRepository, + _authenticationCubit = authenticationCubit, + _onSignUpSuccess = onSignUpSuccess, + super(SignUpState(data: entries)); + + void emailChanged(String value) { + final Email email = Email.dirty(value); + + final List> inputsToValidate = [ + email, + state.password, + ...state.data.inputs(), + ]; + + emit( + state.copyWith( + email: email, + status: FormValidator.validate(inputsToValidate), + ), + ); + } + + void passwordChanged(String value) { + final Password password = Password.dirty(value); + + final List> inputsToValidate = [ + state.email, + password, + ...state.data.inputs(), + ]; + + emit( + state.copyWith( + password: password, + status: FormValidator.validate(inputsToValidate), + ), + ); + } + + void dataChanged(String field, FormInput dirtyValue) { + final _form = state.data.clone(); + + if (_form.contains(field)) { + _form.update(field, dirtyValue); + } else { + throw Exception('Form field $field not found'); + } + + emit( + state.copyWith( + data: _form, + status: FormValidator.validate( + [state.email, state.password, ..._form.inputs()], + ), + ), + ); + } + + void updateFormData( + FormData data, { + SetOperation operation = SetOperation.replace, + }) { + FormData _form = data; + + switch (operation) { + case SetOperation.replace: + _form = data; + break; + case SetOperation.difference: + _form = state.data.difference(data); + break; + case SetOperation.intersection: + _form = state.data.intersection(data); + break; + case SetOperation.union: + _form = state.data.union(data); + break; + } + + emit( + state.copyWith( + data: _form, + status: FormValidator.validate( + [state.email, state.password, ..._form.inputs()], + ), + ), + ); + } + + Future signUpFormSubmitted() async { + if (!state.status.isValidated) return; + emit(state.copyWith(status: FormStatus.submissionInProgress)); + try { + final uid = await _authenticationRepository.signUp( + email: state.email.value, + password: state.password.value, + ); + await _onSignUpSuccess?.call(state, uid); + if (_authenticationCubit.start()) { + emit(state.copyWith(status: FormStatus.submissionSuccess)); + } else { + emit( + state.copyWith( + errorMessage: 'Failed to start authentication cubit', + status: FormStatus.submissionFailure, + ), + ); + } + } on SignUpWithEmailAndPasswordFailureInterface catch (e) { + emit( + state.copyWith( + errorMessage: e.message, + status: FormStatus.submissionFailure, + ), + ); + } catch (e) { + debugPrint(e.toString()); + emit(state.copyWith(status: FormStatus.submissionFailure)); + } + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_up/cubit/sign_up_state.dart b/packages/wyatt_authentication_bloc/lib/src/sign_up/cubit/sign_up_state.dart new file mode 100644 index 00000000..ce493838 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/sign_up/cubit/sign_up_state.dart @@ -0,0 +1,77 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +part of 'sign_up_cubit.dart'; + +@immutable +class SignUpState { + final Email email; + final Password password; + final FormStatus status; + final FormData data; + final String? errorMessage; + + const SignUpState({ + this.email = const Email.pure(), + this.password = const Password.pure(), + this.status = FormStatus.pure, + required this.data, + this.errorMessage, + }); + + SignUpState copyWith({ + Email? email, + Password? password, + FormStatus? status, + FormData? data, + String? errorMessage, + }) { + return SignUpState( + email: email ?? this.email, + password: password ?? this.password, + status: status ?? this.status, + data: data ?? this.data, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is SignUpState && + other.email == email && + other.password == password && + other.status == status && + other.data == data && + other.errorMessage == errorMessage; + } + + @override + int get hashCode { + return email.hashCode ^ + password.hashCode ^ + status.hashCode ^ + data.hashCode ^ + errorMessage.hashCode; + } + + @override + String toString() { + // ignore: lines_longer_than_80_chars + return 'SignUpState(email: $email, password: $password, status: $status, data: $data, errorMessage: $errorMessage)'; + } +} diff --git a/packages/wyatt_authentication_bloc/lib/src/sign_up/sign_up.dart b/packages/wyatt_authentication_bloc/lib/src/sign_up/sign_up.dart new file mode 100644 index 00000000..798c644d --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/sign_up/sign_up.dart @@ -0,0 +1,17 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'cubit/sign_up_cubit.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/src/src.dart b/packages/wyatt_authentication_bloc/lib/src/src.dart new file mode 100644 index 00000000..e3025da1 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/src/src.dart @@ -0,0 +1,24 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +export 'authentication/authentication.dart'; +export 'email_verification/email_verification.dart'; +export 'form/form.dart'; +export 'models/models.dart'; +export 'password_reset/password_reset.dart'; +export 'repositories/repositories.dart'; +export 'sign_in/sign_in.dart'; +export 'sign_up/sign_up.dart'; diff --git a/packages/wyatt_authentication_bloc/lib/wyatt_authentication_bloc.dart b/packages/wyatt_authentication_bloc/lib/wyatt_authentication_bloc.dart new file mode 100644 index 00000000..e40338b2 --- /dev/null +++ b/packages/wyatt_authentication_bloc/lib/wyatt_authentication_bloc.dart @@ -0,0 +1,20 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +/// An authentication library for BLoC. +library wyatt_authentication_bloc; + +export 'src/src.dart'; diff --git a/packages/wyatt_authentication_bloc/pubspec.yaml b/packages/wyatt_authentication_bloc/pubspec.yaml new file mode 100644 index 00000000..96915eac --- /dev/null +++ b/packages/wyatt_authentication_bloc/pubspec.yaml @@ -0,0 +1,27 @@ +name: wyatt_authentication_bloc +description: Authentication BLoC for Flutter +repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_authentication_bloc +version: 0.1.0 + +environment: + sdk: ">=2.15.1 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + + flutter_bloc: ^8.0.1 + equatable: ^2.0.3 + firebase_auth: ^3.3.14 + +dev_dependencies: + flutter_test: + sdk: flutter + bloc_test: ^9.0.3 + + wyatt_analysis: + git: + url: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages + ref: wyatt_analysis-v2.0.0 + path: packages/wyatt_analysis diff --git a/packages/wyatt_authentication_bloc/test/authentication/authentication_cubit_test.dart b/packages/wyatt_authentication_bloc/test/authentication/authentication_cubit_test.dart new file mode 100644 index 00000000..58fc9402 --- /dev/null +++ b/packages/wyatt_authentication_bloc/test/authentication/authentication_cubit_test.dart @@ -0,0 +1,99 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc_test/bloc_test.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthenticationRepository extends Mock + implements AuthenticationRepositoryInterface {} + +class MockUser extends Mock implements UserInterface {} + +void main() { + group('AuthenticationCubit', () { + final MockUser user = MockUser(); + late AuthenticationRepositoryInterface authenticationRepository; + + setUp(() { + authenticationRepository = MockAuthenticationRepository(); + when(() => authenticationRepository.user).thenAnswer( + (_) => const Stream.empty(), + ); + when( + () => authenticationRepository.currentUser, + ).thenReturn(const UserFirebase.empty()); + }); + + test('initial state is unknown', () { + expect( + AuthenticationCubit(authenticationRepository: authenticationRepository) + .state, + const AuthenticationState.unknown(), + ); + }); + + group('UserChanged', () { + blocTest( + 'emits authenticated when user is not empty', + setUp: () { + when(() => user.isNotEmpty).thenReturn(true); + when(() => authenticationRepository.user).thenAnswer( + (_) => Stream.value(user), + ); + }, + build: () => AuthenticationCubit( + authenticationRepository: authenticationRepository, + )..init(), + seed: () => const AuthenticationState.unknown(), + expect: () => [AuthenticationState.authenticated(user, null)], + ); + + blocTest( + 'emits unauthenticated when user is empty', + setUp: () { + when(() => authenticationRepository.user).thenAnswer( + (_) => Stream.value(const UserFirebase.empty()), + ); + }, + build: () => AuthenticationCubit( + authenticationRepository: authenticationRepository, + )..init(), + seed: () => const AuthenticationState.unknown(), + expect: () => [const AuthenticationState.unauthenticated()], + ); + }); + + group('LogoutRequested', () { + blocTest( + 'invokes signOut', + setUp: () { + when( + () => authenticationRepository.signOut(), + ).thenAnswer((_) async {}); + }, + build: () => AuthenticationCubit( + authenticationRepository: authenticationRepository, + ), + act: (AuthenticationCubit cubit) => cubit.logOut(), + verify: (_) { + verify(() => authenticationRepository.signOut()).called(1); + }, + ); + }); + }); +} diff --git a/packages/wyatt_authentication_bloc/test/authentication/authentication_state_test.dart b/packages/wyatt_authentication_bloc/test/authentication/authentication_state_test.dart new file mode 100644 index 00000000..b9e8072f --- /dev/null +++ b/packages/wyatt_authentication_bloc/test/authentication/authentication_state_test.dart @@ -0,0 +1,43 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockUser extends Mock implements UserInterface {} + +void main() { + group('AuthenticationState', () { + group('unauthenticated', () { + test('has correct status', () { + const AuthenticationState state = AuthenticationState.unauthenticated(); + expect(state.status, AuthenticationStatus.unauthenticated); + expect(state.user, null); + }); + }); + + group('authenticated', () { + test('has correct status', () { + final MockUser user = MockUser(); + final AuthenticationState state = + AuthenticationState.authenticated(user, null); + expect(state.status, AuthenticationStatus.authenticated); + expect(state.user, user); + }); + }); + }); +} diff --git a/packages/wyatt_authentication_bloc/test/sign_in/sign_in_cubit_test.dart b/packages/wyatt_authentication_bloc/test/sign_in/sign_in_cubit_test.dart new file mode 100644 index 00000000..98b7c155 --- /dev/null +++ b/packages/wyatt_authentication_bloc/test/sign_in/sign_in_cubit_test.dart @@ -0,0 +1,232 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc_test/bloc_test.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthenticationRepository extends Mock + implements AuthenticationRepositoryInterface {} + +class MockAuthenticationCubit extends Mock implements AuthenticationCubit {} + +void main() { + const String invalidEmailString = 'invalid'; + const Email invalidEmail = Email.dirty(invalidEmailString); + + const String validEmailString = 'test@gmail.com'; + const Email validEmail = Email.dirty(validEmailString); + + const String invalidPasswordString = 'invalid'; + const Password invalidPassword = Password.dirty(invalidPasswordString); + + const String validPasswordString = 't0pS3cret1234'; + const Password validPassword = Password.dirty(validPasswordString); + + group('SignInCubit', () { + late AuthenticationRepositoryInterface authenticationRepository; + late AuthenticationCubit authenticationCubit; + + setUp(() { + authenticationRepository = MockAuthenticationRepository(); + authenticationCubit = MockAuthenticationCubit(); + + when( + () => authenticationRepository.signInWithEmailAndPassword( + email: any(named: 'email'), + password: any(named: 'password'), + ), + ).thenAnswer((_) async {}); + + when( + () => authenticationCubit.start(), + ).thenReturn(true); + }); + + test('initial state is SignInState', () { + expect( + SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ).state, + const SignInState(), + ); + }); + + group('emailChanged', () { + blocTest( + 'emits [invalid] when email/password are invalid', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + act: (SignInCubit cubit) => cubit.emailChanged(invalidEmailString), + expect: () => const [ + SignInState(email: invalidEmail, status: FormStatus.invalid), + ], + ); + + blocTest( + 'emits [valid] when email/password are valid', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + seed: () => const SignInState(password: validPassword), + act: (SignInCubit cubit) => cubit.emailChanged(validEmailString), + expect: () => const [ + SignInState( + email: validEmail, + password: validPassword, + status: FormStatus.valid, + ), + ], + ); + }); + + group('passwordChanged', () { + blocTest( + 'emits [invalid] when email/password are invalid', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + act: (SignInCubit cubit) => + cubit.passwordChanged(invalidPasswordString), + expect: () => const [ + SignInState( + password: invalidPassword, + status: FormStatus.invalid, + ), + ], + ); + + blocTest( + 'emits [valid] when email/password are valid', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + seed: () => const SignInState(email: validEmail), + act: (SignInCubit cubit) => cubit.passwordChanged(validPasswordString), + expect: () => const [ + SignInState( + email: validEmail, + password: validPassword, + status: FormStatus.valid, + ), + ], + ); + }); + + group('logInWithCredentials', () { + blocTest( + 'does nothing when status is not validated', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + act: (SignInCubit cubit) => cubit.signInWithEmailAndPassword(), + expect: () => const [], + ); + + blocTest( + 'calls signInWithEmailAndPassword with correct email/password', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + seed: () => const SignInState( + status: FormStatus.valid, + email: validEmail, + password: validPassword, + ), + act: (SignInCubit cubit) => cubit.signInWithEmailAndPassword(), + verify: (_) { + verify( + () => authenticationRepository.signInWithEmailAndPassword( + email: validEmailString, + password: validPasswordString, + ), + ).called(1); + }, + ); + + blocTest( + 'emits [submissionInProgress, submissionSuccess] ' + 'when signInWithEmailAndPassword succeeds', + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + seed: () => const SignInState( + status: FormStatus.valid, + email: validEmail, + password: validPassword, + ), + act: (SignInCubit cubit) => cubit.signInWithEmailAndPassword(), + expect: () => const [ + SignInState( + status: FormStatus.submissionInProgress, + email: validEmail, + password: validPassword, + ), + SignInState( + status: FormStatus.submissionSuccess, + email: validEmail, + password: validPassword, + ) + ], + ); + + blocTest( + 'emits [submissionInProgress, submissionFailure] ' + 'when signInWithEmailAndPassword fails', + setUp: () { + when( + () => authenticationRepository.signInWithEmailAndPassword( + email: any(named: 'email'), + password: any(named: 'password'), + ), + ).thenThrow(Exception('oops')); + }, + build: () => SignInCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + ), + seed: () => const SignInState( + status: FormStatus.valid, + email: validEmail, + password: validPassword, + ), + act: (SignInCubit cubit) => cubit.signInWithEmailAndPassword(), + expect: () => const [ + SignInState( + status: FormStatus.submissionInProgress, + email: validEmail, + password: validPassword, + ), + SignInState( + status: FormStatus.submissionFailure, + email: validEmail, + password: validPassword, + ) + ], + ); + }); + }); +} diff --git a/packages/wyatt_authentication_bloc/test/sign_in/sign_in_state_test.dart b/packages/wyatt_authentication_bloc/test/sign_in/sign_in_state_test.dart new file mode 100644 index 00000000..f2a0cb0d --- /dev/null +++ b/packages/wyatt_authentication_bloc/test/sign_in/sign_in_state_test.dart @@ -0,0 +1,54 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const Email email = Email.dirty('email'); + const Password password = Password.dirty('password'); + + group('SignInState', () { + test('supports value comparisons', () { + expect(const SignInState(), const SignInState()); + }); + + test('returns same object when no properties are passed', () { + expect(const SignInState().copyWith(), const SignInState()); + }); + + test('returns object with updated status when status is passed', () { + expect( + const SignInState().copyWith(status: FormStatus.pure), + const SignInState(), + ); + }); + + test('returns object with updated email when email is passed', () { + expect( + const SignInState().copyWith(email: email), + const SignInState(email: email), + ); + }); + + test('returns object with updated password when password is passed', () { + expect( + const SignInState().copyWith(password: password), + const SignInState(password: password), + ); + }); + }); +} diff --git a/packages/wyatt_authentication_bloc/test/sign_up/sign_up_cubit_test.dart b/packages/wyatt_authentication_bloc/test/sign_up/sign_up_cubit_test.dart new file mode 100644 index 00000000..d7ee457f --- /dev/null +++ b/packages/wyatt_authentication_bloc/test/sign_up/sign_up_cubit_test.dart @@ -0,0 +1,266 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:bloc_test/bloc_test.dart'; +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAuthenticationRepository extends Mock + implements AuthenticationRepositoryInterface {} + +class MockAuthenticationCubit extends Mock implements AuthenticationCubit {} + +void main() { + const String invalidEmailString = 'invalid'; + const Email invalidEmail = Email.dirty(invalidEmailString); + + const String validEmailString = 'test@gmail.com'; + const Email validEmail = Email.dirty(validEmailString); + + const String invalidPasswordString = 'invalid'; + const Password invalidPassword = Password.dirty(invalidPasswordString); + + const String validPasswordString = 't0pS3cret1234'; + const Password validPassword = Password.dirty(validPasswordString); + + group('SignUpCubit', () { + late AuthenticationRepositoryInterface authenticationRepository; + late AuthenticationCubit authenticationCubit; + + setUp(() { + authenticationRepository = MockAuthenticationRepository(); + authenticationCubit = MockAuthenticationCubit(); + when( + () => authenticationRepository.signUp( + email: any(named: 'email'), + password: any(named: 'password'), + ), + ).thenAnswer((_) async { + return 'uid'; + }); + + when( + () => authenticationCubit.start(), + ).thenReturn(true); + + when( + () => authenticationCubit.stop(), + ).thenReturn(true); + }); + + test('initial state is SignUpState', () { + expect( + SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ).state, + SignUpState(data: FormData.empty()), + ); + }); + + group('emailChanged', () { + blocTest( + 'emits [invalid] when email/password are invalid', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.emailChanged(invalidEmailString), + expect: () => [ + SignUpState( + email: invalidEmail, + status: FormStatus.invalid, + data: FormData.empty(), + ), + ], + ); + + blocTest( + 'emits [valid] when email/password are valid', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + seed: () => SignUpState( + password: validPassword, + data: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.emailChanged(validEmailString), + expect: () => [ + SignUpState( + email: validEmail, + password: validPassword, + status: FormStatus.valid, + data: FormData.empty(), + ), + ], + ); + }); + + group('passwordChanged', () { + blocTest( + 'emits [invalid] when email/password are invalid', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + act: (SignUpCubit cubit) => + cubit.passwordChanged(invalidPasswordString), + expect: () => [ + SignUpState( + password: invalidPassword, + status: FormStatus.invalid, + data: FormData.empty(), + ), + ], + ); + + blocTest( + 'emits [valid] when email/password are valid', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + seed: () => SignUpState( + email: validEmail, + data: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.passwordChanged(validPasswordString), + expect: () => [ + SignUpState( + email: validEmail, + password: validPassword, + status: FormStatus.valid, + data: FormData.empty(), + ), + ], + ); + }); + + group('signUpFormSubmitted', () { + blocTest( + 'does nothing when status is not validated', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(), + expect: () => const [], + ); + + blocTest( + 'calls signUp with correct email/password/confirmedPassword', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + seed: () => SignUpState( + status: FormStatus.valid, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(), + verify: (_) { + verify( + () => authenticationRepository.signUp( + email: validEmailString, + password: validPasswordString, + ), + ).called(1); + }, + ); + + blocTest( + 'emits [submissionInProgress, submissionSuccess] ' + 'when signUp succeeds', + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + seed: () => SignUpState( + status: FormStatus.valid, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(), + expect: () => [ + SignUpState( + status: FormStatus.submissionInProgress, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ), + SignUpState( + status: FormStatus.submissionSuccess, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ) + ], + ); + + blocTest( + 'emits [submissionInProgress, submissionFailure] ' + 'when signUp fails', + setUp: () { + when( + () => authenticationRepository.signUp( + email: any(named: 'email'), + password: any(named: 'password'), + ), + ).thenThrow(Exception('oops')); + }, + build: () => SignUpCubit( + authenticationRepository: authenticationRepository, + authenticationCubit: authenticationCubit, + entries: FormData.empty(), + ), + seed: () => SignUpState( + status: FormStatus.valid, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ), + act: (SignUpCubit cubit) => cubit.signUpFormSubmitted(), + expect: () => [ + SignUpState( + status: FormStatus.submissionInProgress, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ), + SignUpState( + status: FormStatus.submissionFailure, + email: validEmail, + password: validPassword, + data: FormData.empty(), + ) + ], + ); + }); + }); +} diff --git a/packages/wyatt_authentication_bloc/test/sign_up/sign_up_state_test.dart b/packages/wyatt_authentication_bloc/test/sign_up/sign_up_state_test.dart new file mode 100644 index 00000000..e4d00c8a --- /dev/null +++ b/packages/wyatt_authentication_bloc/test/sign_up/sign_up_state_test.dart @@ -0,0 +1,112 @@ +// Copyright (C) 2022 WYATT GROUP +// Please see the AUTHORS file for details. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +import 'package:wyatt_authentication_bloc/wyatt_authentication_bloc.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + const Email email = Email.dirty('email'); + const String passwordString = 'password'; + const Password password = Password.dirty(passwordString); + + group('SignUpState', () { + test('supports value comparisons', () { + expect( + SignUpState( + data: FormData.empty(), + ), + SignUpState( + data: FormData.empty(), + ), + ); + }); + + test('returns same object when no properties are passed', () { + expect( + SignUpState( + data: FormData.empty(), + ).copyWith(), + SignUpState( + data: FormData.empty(), + ), + ); + }); + + test('returns object with updated status when status is passed', () { + expect( + SignUpState( + data: FormData.empty(), + ).copyWith(status: FormStatus.pure), + SignUpState( + data: FormData.empty(), + ), + ); + }); + + test('returns object with updated email when email is passed', () { + expect( + SignUpState( + data: FormData.empty(), + ).copyWith(email: email), + SignUpState( + email: email, + data: FormData.empty(), + ), + ); + }); + + test('returns object with updated password when password is passed', () { + expect( + SignUpState( + data: FormData.empty(), + ).copyWith(password: password), + SignUpState( + password: password, + data: FormData.empty(), + ), + ); + }); + + test( + 'returns object with updated data' + ' when data is passed', () { + expect( + SignUpState( + data: FormData.empty(), + ).copyWith( + data: const FormData( + [ + FormEntry( + 'field', + Name.pure(), + ), + ], + ), + ), + const SignUpState( + data: FormData( + [ + FormEntry( + 'field', + Name.pure(), + ), + ], + ), + ), + ); + }); + }); +}