commit 95b26b4063faa56e1cee03d360233654dc2cb9e3 Author: Matyáš Caras Date: Fri Feb 10 15:53:42 2023 +0100 feat: přidat projekt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# 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/ + +# 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/.flutter b/.flutter new file mode 160000 index 0000000..9944297 --- /dev/null +++ b/.flutter @@ -0,0 +1 @@ +Subproject commit 9944297138845a94256f1cf37beb88ff9a8e811a diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..24476c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,44 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# 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/ + +# 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/.gitmodules b/.gitmodules new file mode 100644 index 0000000..ee9d776 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule ".flutter"] + path = .flutter + url = https://github.com/flutter/flutter.git + branch = stable diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..db36dec --- /dev/null +++ b/.metadata @@ -0,0 +1,36 @@ +# 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. + +version: + revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + - platform: android + create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + - platform: ios + create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + - platform: web + create_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + base_revision: b8f7f1f9869bb2d116aa6a70dbeac61000b52849 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..736e6fc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,5 @@ +FROM passsy/flutterw:base-latest +COPY . . +RUN ./flutterw config --no-analytics +ENTRYPOINT ./flutterw run --release --web-port=80 --web-hostname 0.0.0.0 -d web-server +EXPOSE 80 \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cba6f6a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,660 @@ +### GNU AFFERO GENERAL PUBLIC LICENSE + +Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + +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. + +Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + +A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + +The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + +An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing +under this license. + +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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + +Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your +version supports such interaction) an opportunity to receive the +Corresponding Source of your version by providing access to the +Corresponding Source from a network server at no charge, through some +standard or customary means of facilitating copying of software. This +Corresponding Source shall include the Corresponding Source for any +work covered by version 3 of the GNU General Public License that is +incorporated pursuant to the following paragraph. + +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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + +#### 14. Revised Versions of this License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper +mail. + +If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for +the specific requirements. + +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 AGPL, see . diff --git a/README.md b/README.md new file mode 100644 index 0000000..7571ac9 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +# KodeLog + +Deník programátora pro soutěž [Tour de App](https://tourdeapp.cz) + +Live verze na https://tourdeapp-70a92.web.app/ + +Vydáno pod licencí AGPL verze 3 +## Tým Royal Buccaneers +- [Richard Pavlikán](https://richardpavlikan.com) +- [Matyáš Caras](https://caras.cafe) hernik#9234 + +## Sestavení +**Před spuštěním/sestavením je nutné zaregistrovat si projekt na Firebase.** +### UNIX +1. `git clone https://github.com/Royal-Buccaneers/kodelog --recursive` +2. `./flutterw build web --release` +### Windows +0. Nainstalovat Flutter +1. Clone +2. Build + +## Obrázky +![Screenshot přihlašovací obrazovky](images/01.png) +![Screenshot přihlašovací obrazovky](images/02.png) +![Screenshot přihlašovací obrazovky](images/03.png) +![Screenshot přihlašovací obrazovky](images/04.png) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..61b6c4d --- /dev/null +++ b/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/flutterw b/flutterw new file mode 100755 index 0000000..60530b3 --- /dev/null +++ b/flutterw @@ -0,0 +1,113 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Flutter start up script for UN*X +## Version: v1.3.1 +## Date: 2023-02-10 15:25:45 +## +## Use this flutter wrapper to bundle Flutter within your project to make +## sure everybody builds with the same version. +## +## Read about the install and uninstall process in the README on GitHub +## https://github.com/passsy/flutter_wrapper +## +## Inspired by gradle-wrapper. +## +############################################################################## + +echoerr() { echo "$@" 1>&2; } + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG=$(dirname "$PRG")"/$link" + fi +done +SAVED="$(pwd)" +cd "$(dirname "$PRG")/" >/dev/null +APP_HOME="$(pwd -P)" +cd "$SAVED" >/dev/null + +FLUTTER_SUBMODULE_NAME='.flutter' +GIT_HOME=$(git -C "${APP_HOME}" rev-parse --show-toplevel) +FLUTTER_DIR="${GIT_HOME}/${FLUTTER_SUBMODULE_NAME}" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +# Fix not initialized flutter submodule +if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then + echoerr "$FLUTTER_SUBMODULE_NAME submodule not initialized. Initializing..." + git submodule update --init "${FLUTTER_DIR}" +fi + +# Detect detach HEAD and fix it. commands like upgrade expect a valid branch, not a detached HEAD +FLUTTER_SYMBOLIC_REF=$(git -C "${FLUTTER_DIR}" symbolic-ref -q HEAD) +if [ -z "${FLUTTER_SYMBOLIC_REF}" ]; then + FLUTTER_REV=$(git -C "${FLUTTER_DIR}" rev-parse HEAD) + FLUTTER_CHANNEL=$(git -C "${GIT_HOME}" config -f .gitmodules submodule.${FLUTTER_SUBMODULE_NAME}.branch) + + if [ -z "${FLUTTER_CHANNEL}" ]; then + echoerr "Warning: Submodule '$FLUTTER_SUBMODULE_NAME' doesn't point to an official Flutter channel \ +(one of stable|beta|dev|master). './flutterw upgrade' will fail without a channel." + echoerr "Fix this by adding a specific channel with:" + echoerr " - './flutterw channel ' or" + echoerr " - Add 'branch = ' to '$FLUTTER_SUBMODULE_NAME' submodule in .gitmodules" + else + echoerr "Fixing detached HEAD: '$FLUTTER_SUBMODULE_NAME' submodule points to a specific commit $FLUTTER_REV, not channel '$FLUTTER_CHANNEL' (as defined in .gitmodules)." + # Make sure channel is fetched + # Remove old channel branch because it might be moved to an unrelated commit where fast-forward pull isn't possible + git -C "${FLUTTER_DIR}" branch -q -D "${FLUTTER_CHANNEL}" 2> /dev/null || true + git -C "${FLUTTER_DIR}" fetch -q origin + + # bind current HEAD to channel defined in .gitmodules + git -C "${FLUTTER_DIR}" checkout -q -b "${FLUTTER_CHANNEL}" "${FLUTTER_REV}" + git -C "${FLUTTER_DIR}" branch -q -u "origin/${FLUTTER_CHANNEL}" "${FLUTTER_CHANNEL}" + echoerr "Fixed! Migrated to channel '$FLUTTER_CHANNEL' while staying at commit $FLUTTER_REV. './flutterw upgrade' now works without problems!" + git -C "${FLUTTER_DIR}" status -bs + fi +fi + +# Wrapper tasks done, call flutter binary with all args +set -e +"$FLUTTER_DIR/bin/flutter" "$@" +set +e + +# Post flutterw tasks. exit code from /bin/flutterw will be used as final exit +FLUTTER_EXIT_STATUS=$? +if [ ${FLUTTER_EXIT_STATUS} -eq 0 ]; then + + # ./flutterw channel CHANNEL + if echo "$@" | grep -q "channel"; then + if [ -n "$2" ]; then + # make sure .gitmodules is updated as well + CHANNEL=${2} # second arg + git config -f "${GIT_HOME}/.gitmodules" "submodule.${FLUTTER_SUBMODULE_NAME}.branch" "${CHANNEL}" + # makes sure nobody forgets to do commit all changed files + git add "${GIT_HOME}/.gitmodules" + git add "${FLUTTER_DIR}" + fi + fi + + # ./flutterw upgrade + if echo "$@" | grep -q "upgrade"; then + # makes sure nobody forgets to do commit the changed submodule + git add "${FLUTTER_DIR}" + # flutter packages get runs automatically. Stage those changes as well + if [ -f pubspec.lock ]; then + git add pubspec.lock + fi + fi +fi + +exit ${FLUTTER_EXIT_STATUS} diff --git a/images/01.png b/images/01.png new file mode 100644 index 0000000..2c97878 Binary files /dev/null and b/images/01.png differ diff --git a/images/02.png b/images/02.png new file mode 100644 index 0000000..f756690 Binary files /dev/null and b/images/02.png differ diff --git a/images/03.png b/images/03.png new file mode 100644 index 0000000..32768ef Binary files /dev/null and b/images/03.png differ diff --git a/images/04.png b/images/04.png new file mode 100644 index 0000000..ef8dc89 Binary files /dev/null and b/images/04.png differ diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..04f0eb7 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,76 @@ +import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +import 'firebase_options.dart'; //TODO: Přidejte si vlastní firebase nastavení + +/* + Copyright (C) 2022 Matyáš Caras a Richard Pavlikán + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: DefaultFirebaseOptions + .currentPlatform, //TODO: Přidejte si vlastní firebase nastavení + ); + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return ResponsiveSizer( + builder: (p0, p1, p2) => MaterialApp( + title: 'Deník Programátora', + theme: ThemeData( + primarySwatch: Colors.blue, + scaffoldBackgroundColor: Vzhled.backgroundColor, + textTheme: Theme.of(context).textTheme.apply( + bodyColor: Vzhled.textColor, + displayColor: Vzhled.textColor, + fontSizeDelta: 2, + fontSizeFactor: 0.8, + ), + colorScheme: const ColorScheme( + background: Vzhled.dialogColor, + onBackground: Vzhled.textColor, + brightness: Brightness.dark, + primary: Colors.blue, + onPrimary: Vzhled.textColor, + secondary: Colors.purple, + onSecondary: Vzhled.textColor, + error: Colors.red, + onError: Vzhled.textColor, + surface: Vzhled.backgroundColor, + onSurface: Vzhled.textColor), + dialogBackgroundColor: Vzhled.dialogColor, + ), + localizationsDelegates: const [ + ...GlobalMaterialLocalizations.delegates + ], + supportedLocales: const [Locale("cs")], + debugShowCheckedModeBanner: false, + home: const SignInPage(), + ), + ); + } +} diff --git a/lib/okna/all_records.dart b/lib/okna/all_records.dart new file mode 100644 index 0000000..bd4a208 --- /dev/null +++ b/lib/okna/all_records.dart @@ -0,0 +1,722 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:denikprogramatora/okna/app.dart'; +import 'package:denikprogramatora/okna/settings.dart'; +import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/utils/devicecontainer.dart'; +import 'package:denikprogramatora/utils/input_decoration.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:denikprogramatora/utils/months.dart'; +import 'package:denikprogramatora/utils/my_category.dart'; +import 'package:denikprogramatora/utils/my_container.dart'; +import 'package:denikprogramatora/utils/new_record_dialog.dart'; +import 'package:denikprogramatora/utils/programmer.dart'; +import 'package:denikprogramatora/utils/show_info_dialog.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +/* + Copyright (C) 2022 Matyáš Caras a Richard Pavlikán + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +class AllRecordsPage extends StatefulWidget { + const AllRecordsPage({super.key}); + + @override + State createState() => _AllRecordsPageState(); +} + +class _AllRecordsPageState extends State { + bool _loading = true; + Month mesic = months[0]; + int selectedDay = DateTime.now().day; + int year = DateTime.now().year; + + List categories = [MyCategory("Nic", "nic")]; + List programmers = [ + const Programmer("Nic", "nic"), + Programmer(name, userUid) + ]; + List filterJazyky = [ + {"jazyk": "Nic", "barva": 0xff8200f3}, + ]; + + late String selectedCategory; + late String selectedProgrammer; + bool newestToOldest = true; + late String selectedJazyk; + DateTime fromDate = + DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day); + DateTime toDate = + DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day); + + bool searchByFromDate = false; + bool searchByToDate = false; + + int timeHour = 0; + int timeMinute = 0; + int review = 0; + + @override + void initState() { + super.initState(); + if (FirebaseAuth.instance.currentUser == null) { + if (kDebugMode) print("user should not be here"); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (c) => const SignInPage()), + (route) => false); + return; + } + + name = FirebaseAuth.instance.currentUser!.displayName!; + + ref.collection("programmers").get().then((value) { + for (var snap in value.docs) { + var data = snap.data(); + + programmers.add(Programmer(data["name"], snap.id)); + } + }); + + ref.collection("categories").get().then((value) { + for (var snap in value.docs) { + var data = snap.data(); + categories.add(MyCategory(data["name"], snap.id)); + } + }); + + filterJazyky.addAll(jazyky); + + selectedCategory = categories[0].id; + selectedProgrammer = programmers[0].id; + selectedJazyk = "Nic"; + + mesic = months[DateTime.now().month - 1]; + + setState(() { + _loading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SizedBox( + width: 90.w, + child: (_loading) + ? const LoadingWidget() + : CustomScrollView( + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 35.w, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + if (name != "error") Text("Ahoj $name"), + TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: "Kodelog", + applicationVersion: "1.1.0", + applicationLegalese: + "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrlString( + "https://github.com/Royal-Buccaneers/kodelog"), + ) + ]), + child: const Text( + "Licence", + style: Vzhled.textBtn, + ), + ), + TextButton( + onPressed: () async { + await FirebaseAuth.instance.signOut(); + // ignore: use_build_context_synchronously + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (c) => + const SignInPage()), + (route) => false); + }, + child: const Text( + "Odhlásit se", + style: Vzhled.textBtn, + ), + ) + ], + ), + ), + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 40.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const HlavniOkno())); + }, + child: const Text( + "Denní přehled", + style: + TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () {}, + child: const Text( + "Všechny\nzáznamy", + style: TextStyle( + color: Vzhled.textColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const NastaveniOkno())); + }, + child: const Text( + "Nastavení", + style: + TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + OutlinedButton( + onPressed: () => + showCreateItemDialog(context), + style: Vzhled.orangeCudlik, + child: const Text( + "Přidat záznam", + ), + ) + ], + ), + ), + ], + ), + const SizedBox(height: 5), + Expanded( + child: MyContainer( + width: 90.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: + (Device.screenType == ScreenType.mobile) + ? 80.w + : 40.w, + child: Column( + children: [ + const Text("Filtr", + style: Vzhled.nadpis), + const SizedBox(height: 15), + Row( + children: [ + Flexible( + child: Text( + "Záznamy seřazené od ${newestToOldest ? "nejnovějších po nejstarší" : "nejstarších po nejnovější"}"), + ), + TextButton( + onPressed: () { + setState(() { + newestToOldest = + !newestToOldest; + }); + }, + child: const Text( + "Změnit", + style: Vzhled.textBtn, + ), + ) + ], + ), + const SizedBox(height: 15), + DeviceContainer( + children: [ + const Text("Kategorie"), + const SizedBox(width: 15), + DropdownButton( + value: selectedCategory, + items: categories.map((e) { + return DropdownMenuItem( + value: e.id, + child: Text(e.name)); + }).toList(), + onChanged: (value) { + setState(() { + selectedCategory = value!; + }); + }, + ), + ], + ), + const SizedBox(height: 15), + DeviceContainer( + children: [ + const Text("Jazyk"), + const SizedBox(width: 15), + DropdownButton( + value: selectedJazyk, + dropdownColor: + Vzhled.backgroundColor, + items: filterJazyky + .map( + (e) => DropdownMenuItem( + value: e["jazyk"], + child: Text(e["jazyk"]), + ), + ) + .toList(), + onChanged: (value) { + setState(() { + selectedJazyk = + (value as String?)!; + }); + }, + ), + ], + ), + const SizedBox(height: 15), + DeviceContainer( + children: [ + const Text("Programátor"), + const SizedBox(width: 15), + DropdownButton( + value: selectedProgrammer, + items: programmers.map((e) { + return DropdownMenuItem( + value: e.id, + child: Text(e.name)); + }).toList(), + onChanged: (value) { + setState(() { + selectedProgrammer = value!; + }); + }, + ), + ], + ), + const SizedBox(height: 15), + DeviceContainer( + children: [ + const Text("Strávený čas"), + const SizedBox(width: 15), + SizedBox( + width: 75, + child: TextField( + decoration: + inputDecoration("Hodin"), + onChanged: (value) { + setState(() { + timeHour = + value.trim().isEmpty + ? 0 + : int.parse(value); + }); + }, + keyboardType: + TextInputType.number, + inputFormatters: < + TextInputFormatter>[ + FilteringTextInputFormatter + .digitsOnly + ], + ), + ), + const SizedBox(width: 15), + SizedBox( + width: 75, + child: TextField( + decoration: + inputDecoration("Minut"), + onChanged: (value) { + setState(() { + timeMinute = + value.trim().isEmpty + ? 0 + : int.parse(value); + }); + }, + keyboardType: + TextInputType.number, + inputFormatters: < + TextInputFormatter>[ + FilteringTextInputFormatter + .digitsOnly + ], + ), + ), + const SizedBox(width: 15), + if (timeMinute != 0 || + timeHour != 0) + TextButton( + onPressed: () { + setState(() { + timeMinute = 0; + timeHour = 0; + }); + }, + child: const Text( + "Zrušit filtr", + style: Vzhled.textBtn, + ), + ) + ], + ), + const SizedBox(height: 15), + DeviceContainer( + children: [ + const Text("Hodnocení"), + const SizedBox(width: 15), + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: List.generate( + 5, + (index) { + return IconButton( + onPressed: () { + setState(() { + review = index + 1; + }); + }, + icon: Icon(Icons.star, + color: (index + 1) <= + review + ? Colors.yellow + : Colors.grey), + ); + }, + ), + ), + if (review != 0) + TextButton( + onPressed: () { + setState(() { + review = 0; + }); + }, + child: const Text( + "Zrušit filtr", + style: Vzhled.textBtn, + ), + ) + ], + ), + const SizedBox(height: 15), + Row( + children: [ + const Text("Od: "), + const SizedBox(width: 15), + TextButton( + onPressed: () { + showDatePicker( + context: context, + initialDate: fromDate, + firstDate: DateTime( + DateTime.now() + .year - + 5), + lastDate: DateTime( + DateTime.now() + .year + + 5)) + .then((value) { + setState(() { + fromDate = value!; + searchByFromDate = true; + }); + }).onError( + (error, stackTrace) => + null); + }, + child: Text(searchByFromDate + ? "${fromDate.day}.${fromDate.month}.${fromDate.year}" + : "Vybrat den"), + ), + const SizedBox(width: 15), + if (searchByFromDate) + TextButton( + onPressed: () { + setState(() { + searchByFromDate = false; + }); + }, + child: const Text( + ("Zrušit filtr"), + style: Vzhled.textBtn, + ), + ), + ], + ), + const SizedBox(height: 5), + Row( + children: [ + const Text("Do: "), + const SizedBox(width: 15), + TextButton( + onPressed: () { + showDatePicker( + context: context, + initialDate: toDate, + firstDate: DateTime( + DateTime.now() + .year - + 5), + lastDate: DateTime( + DateTime.now() + .year + + 5)) + .then((value) { + setState(() { + toDate = value!; + searchByToDate = true; + }); + }).onError( + (error, stackTrace) => + null); + }, + child: Text(searchByToDate + ? "${toDate.day}.${toDate.month}.${toDate.year}" + : "Vybrat den"), + ), + const SizedBox(width: 15), + if (searchByToDate) + TextButton( + onPressed: () { + setState(() { + searchByToDate = false; + }); + }, + child: const Text( + ("Zrušit filtr"), + style: Vzhled.textBtn, + ), + ), + ], + ), + ], + ), + ), + SizedBox( + width: + (Device.screenType == ScreenType.mobile) + ? 80.w + : 40.w, + child: StreamBuilder( + stream: ref + .collection("records") + .orderBy("fromDate", + descending: newestToOldest) + .snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + var docs = snapshot.data!.docs; + + if (selectedProgrammer != "nic") { + docs = docs + .where((element) => + element + .data()["programmer"] == + selectedProgrammer) + .toList(); + } + + if (selectedCategory != "nic") { + docs = docs + .where((element) => (element + .data()[ + "categories"] as List) + .contains(selectedCategory)) + .toList(); + } + + if (selectedJazyk != "Nic") { + docs = docs + .where((element) => + element.data()["language"] + ["jazyk"] == + selectedJazyk) + .toList(); + } + + if (searchByFromDate) { + docs = docs + .where((d) => + (d.data()["fromDate"] + as Timestamp) + .toDate() + .compareTo( + fromDate) == + 1 || + (d.data()["fromDate"] + as Timestamp) + .toDate() + .compareTo( + fromDate) == + 0) + .toList(); + } + + if (searchByToDate) { + docs = docs + .where((d) => + (d.data()["toDate"] + as Timestamp) + .toDate() + .compareTo( + toDate) == + -1 || + (d.data()["toDate"] + as Timestamp) + .toDate() + .compareTo( + toDate) == + 0) + .toList(); + } + + if (timeHour != 0 || + timeMinute != 0) { + if (kDebugMode) { + print( + "${timeHour == 0 ? "" : (timeHour == 1 ? "$timeHour hodina" : "$timeHour hodin")}${timeMinute == 0 ? "" : (timeMinute == 1 ? "a $timeMinute minuta" : " a $timeMinute minut")}"); + } + docs = docs + .where((element) => (element + .data()["codingTime"] == + "${timeHour == 0 ? "" : (timeHour == 1 ? "$timeHour hodina" : "$timeHour hodin")}${timeMinute == 0 ? "" : (timeMinute == 1 ? "a $timeMinute minuta" : " a $timeMinute minut")}")) + .toList(); + } + + if (review != 0) { + docs = docs + .where((element) => + element.data()["review"] == + review) + .toList(); + } + + return Column( + children: List.generate( + docs.length, + (index) { + var data = docs[index].data(); + + return Padding( + padding: + const EdgeInsets.all(8.0), + child: Container( + decoration: BoxDecoration( + borderRadius: + const BorderRadius + .all( + Radius.circular(4), + ), + color: Color( + data["language"] + ["barva"]), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => + showInfoDialog( + context, + data, + docs[index].id), + child: Padding( + padding: + const EdgeInsets + .all(8.0), + child: Row( + children: [ + Text( + "${(data["fromDate"] as Timestamp).toDate().year}.${(data["fromDate"] as Timestamp).toDate().month}.${(data["fromDate"] as Timestamp).toDate().day} ${(data["fromDate"] as Timestamp).toDate().hour < 10 ? "0${(data["fromDate"] as Timestamp).toDate().hour}" : (data["fromDate"] as Timestamp).toDate().hour}:${(data["fromDate"] as Timestamp).toDate().minute < 10 ? "0${(data["fromDate"] as Timestamp).toDate().minute}" : (data["fromDate"] as Timestamp).toDate().minute}"), + const SizedBox( + width: 20, + ), + Text( + " - ${data["language"]["jazyk"]}") + ], + ), + ), + ), + ), + ), + ); + }, + ), + ); + } + return const LoadingWidget(); + }, + ), + ), + ], + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } +} diff --git a/lib/okna/app.dart b/lib/okna/app.dart new file mode 100644 index 0000000..095e49d --- /dev/null +++ b/lib/okna/app.dart @@ -0,0 +1,554 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:denikprogramatora/okna/all_records.dart'; +import 'package:denikprogramatora/okna/settings.dart'; +import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/utils/devicecontainer.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:denikprogramatora/utils/months.dart'; +import 'package:denikprogramatora/utils/my_container.dart'; +import 'package:denikprogramatora/utils/new_record_dialog.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import '../utils/razeni.dart'; +import '../utils/show_info_dialog.dart'; + +/* + Copyright (C) 2022 Matyáš Caras a Richard Pavlikán + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +var ref = FirebaseFirestore.instance + .collection("users") + .doc(FirebaseAuth.instance.currentUser!.uid); +String name = "error"; +String userUid = "error"; + +class HlavniOkno extends StatefulWidget { + const HlavniOkno({super.key}); + + @override + State createState() => _HlavniOknoState(); +} + +class _HlavniOknoState extends State { + bool _loading = true; + + Month mesic = months[0]; + int selectedDay = DateTime.now().day; + int year = DateTime.now().year; + int vybraneRazeni = 0; + + @override + void initState() { + super.initState(); + if (FirebaseAuth.instance.currentUser == null) { + if (kDebugMode) print("user should not be here"); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (c) => const SignInPage()), + (route) => false); + return; + } + + userUid = FirebaseAuth.instance.currentUser!.uid; + name = FirebaseAuth.instance.currentUser!.displayName!; + + mesic = months[DateTime.now().month - 1]; + + setState(() { + _loading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: SizedBox( + width: 90.w, + child: (_loading) + ? const LoadingWidget() + : CustomScrollView( + slivers: [ + SliverFillRemaining( + hasScrollBody: false, + child: Column( + children: [ + DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 35.w, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + if (name != "error") Text("Ahoj $name"), + TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: "Kodelog", + applicationVersion: "1.1.0", + applicationLegalese: + "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrlString( + "https://github.com/Royal-Buccaneers/kodelog"), + ) + ]), + child: const Text( + "Licence", + style: Vzhled.textBtn, + ), + ), + TextButton( + onPressed: () async { + await FirebaseAuth.instance.signOut(); + if (!mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (c) => + const SignInPage()), + (route) => false); + }, + child: const Text( + "Odhlásit se", + style: Vzhled.textBtn, + ), + ) + ], + ), + ), + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 95.w + : 45.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () {}, + child: const Text( + "Denní přehled", + style: TextStyle( + fontWeight: FontWeight.bold, + color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const AllRecordsPage())); + }, + child: const Text( + "Všechny\nzáznamy", + style: + TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const NastaveniOkno())); + }, + child: const Text( + "Nastavení", + style: + TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + OutlinedButton( + onPressed: () => + showCreateItemDialog(context), + style: Vzhled.orangeCudlik, + child: const Text( + "Přidat záznam", + ), + ) + ], + ), + ), + ], + ), + const SizedBox(height: 5), + Expanded( + child: MyContainer( + width: 90.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 40.w, + child: StreamBuilder( + stream: + ref.collection("records").snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + var docs = snapshot.data!.docs; + var jenMesic = docs + .where((d) => + DateTime.parse( + "$year-${(mesic.position + 1 < 10) ? "0${mesic.position + 1}" : mesic.position + 1}-${selectedDay < 10 ? "0$selectedDay" : selectedDay} 00:00:00") + .isBefore( + (d.data()["toDate"] + as Timestamp) + .toDate()) && + DateTime.parse( + "$year-${(mesic.position + 1 < 10) ? "0${mesic.position + 1}" : mesic.position + 1}-${selectedDay < 10 ? "0$selectedDay" : selectedDay} 23:59:59") + .isAfter( + (d.data()["fromDate"] + as Timestamp) + .toDate())) + .toList() // vybere pouze záznamy, které probíhají ve vybraný den + ..sort( + razeni[vybraneRazeni], + ); // seřadíme podle vybrané metody řazení + if (jenMesic.isEmpty) { + return const Text( + "Nic", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 25), + ); + } + + return Column( + children: List.generate( + jenMesic.length, + (index) { + var data = + jenMesic[index].data(); + + return Padding( + padding: + const EdgeInsets.all(8.0), + child: Container( + decoration: BoxDecoration( + borderRadius: + const BorderRadius + .all( + Radius.circular(4), + ), + color: Color( + data["language"] + ["barva"]), + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => + showInfoDialog( + context, + data, + jenMesic[index] + .id), + child: Padding( + padding: + const EdgeInsets + .all(8.0), + child: Row( + children: [ + Text( + "${(data["fromDate"] as Timestamp).toDate().hour < 10 ? "0${(data["fromDate"] as Timestamp).toDate().hour}" : (data["fromDate"] as Timestamp).toDate().hour}:${(data["fromDate"] as Timestamp).toDate().minute < 10 ? "0${(data["fromDate"] as Timestamp).toDate().minute}" : (data["fromDate"] as Timestamp).toDate().minute}"), + const SizedBox( + width: 20, + ), + Text( + " - ${data["language"]["jazyk"]}") + ], + ), + ), + ), + ), + ), + ); + }, + ), + ); + } + return const LoadingWidget(); + }, + ), + ), + if (Device.screenType == ScreenType.mobile) + const SizedBox( + height: 50, + ), + SizedBox( + width: 45.w, + child: Column( + children: [ + DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceEvenly, + children: [ + const Text("Řazení:"), + DropdownButton( + items: const [ + DropdownMenuItem( + value: 0, + child: Text( + "Vzestupně dle času"), + ), + DropdownMenuItem( + value: 1, + child: + Text("Sestupně dle času"), + ), + DropdownMenuItem( + value: 2, + child: Text( + "Vzestupně dle hodnocení"), + ), + DropdownMenuItem( + value: 3, + child: Text( + "Sestupně dle hodnocení"), + ), + ], + value: vybraneRazeni, + onChanged: (v) { + if (v == null) return; + + vybraneRazeni = v; + setState(() {}); + }, + ) + ], + ), + const SizedBox(height: 30), + if (Device.screenType != + ScreenType.mobile) + Column( + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment + .spaceEvenly, + children: [ + IconButton( + onPressed: () { + setState( + () { + if (mesic.position - + 1 != + -1) { + mesic = months[ + mesic.position - + 1]; + } else { + mesic = months[11]; + year--; + } + }, + ); + }, + icon: const Icon( + Icons.arrow_back, + color: Vzhled.purple), + ), + SizedBox( + width: 150, + child: Center( + child: Text( + "${mesic.name} $year", + style: Vzhled.velkyText, + ), + ), + ), + IconButton( + onPressed: () { + setState(() { + if (mesic.position + + 1 != + 12) { + mesic = months[ + mesic.position + + 1]; + } else { + mesic = months[0]; + year++; + } + }); + }, + icon: const Icon( + Icons.arrow_forward, + color: Vzhled.purple), + ), + ], + ), + const SizedBox(height: 20), + calendarView() + ], + ) + ], + ), + ), + if (Device.screenType == ScreenType.mobile) + Column( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: + MainAxisAlignment.spaceEvenly, + children: [ + IconButton( + onPressed: () { + setState( + () { + if (mesic.position - 1 != + -1) { + mesic = months[ + mesic.position - 1]; + } else { + mesic = months[11]; + year--; + } + }, + ); + }, + icon: const Icon(Icons.arrow_back, + color: Vzhled.purple), + ), + SizedBox( + width: 150, + child: Center( + child: Text( + "${mesic.name} $year", + style: Vzhled.velkyText, + ), + ), + ), + IconButton( + onPressed: () { + setState(() { + if (mesic.position + 1 != + 12) { + mesic = months[ + mesic.position + 1]; + } else { + mesic = months[0]; + year++; + } + }); + }, + icon: const Icon( + Icons.arrow_forward, + color: Vzhled.purple), + ), + ], + ), + const SizedBox(height: 20), + calendarView() + ], + ) + ], + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + } + + Widget calendarView() { + int day = 1; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate( + ((mesic.days / 7) == 4 + ? (Device.screenType == ScreenType.mobile) + ? 10 + : 4 + : (Device.screenType == ScreenType.mobile) + ? 11 + : 5), + (index) { + return Row( + mainAxisAlignment: (Device.screenType == ScreenType.mobile) + ? MainAxisAlignment.center + : MainAxisAlignment.start, + children: List.generate( + (Device.screenType == ScreenType.mobile) ? 3 : 7, + (index) { + int thisDay = day++; + + if (thisDay > mesic.days) { + return const SizedBox(); + } + return Padding( + padding: const EdgeInsets.all(8), + child: OutlinedButton( + onPressed: () { + setState(() { + selectedDay = thisDay; + }); + }, + style: OutlinedButton.styleFrom( + textStyle: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + ), + backgroundColor: (selectedDay == thisDay) + ? Vzhled.purple + : Colors.black.withOpacity(0), + foregroundColor: (selectedDay == thisDay) + ? Vzhled.backgroundColor + : Vzhled.textColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(30.0), + ), + side: BorderSide( + color: (selectedDay != thisDay) + ? Vzhled.purple + : Colors.black.withOpacity(0))), + child: Text(thisDay.toString()), + ), + ); + }, + ), + ); + }, + ), + ); + } +} diff --git a/lib/okna/settings.dart b/lib/okna/settings.dart new file mode 100644 index 0000000..d7ac392 --- /dev/null +++ b/lib/okna/settings.dart @@ -0,0 +1,306 @@ +import 'package:denikprogramatora/okna/app.dart'; +import 'package:denikprogramatora/okna/signin_page.dart'; +import 'package:denikprogramatora/utils/devicecontainer.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import '../utils/my_container.dart'; +import '../utils/new_record_dialog.dart'; +import '../utils/vzhled.dart'; +import 'all_records.dart'; + +/* + Copyright (C) 2022 Matyáš Caras a Richard Pavlikán + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +class NastaveniOkno extends StatefulWidget { + const NastaveniOkno({super.key}); + + @override + State createState() => _NastaveniOknoState(); +} + +class _NastaveniOknoState extends State { + var _loading = true; + var name = "error"; + @override + void initState() { + super.initState(); + if (FirebaseAuth.instance.currentUser == null) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (c) => const SignInPage()), + (route) => false); + return; + } + name = FirebaseAuth.instance.currentUser!.displayName!; + + setState(() { + _loading = false; + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: SingleChildScrollView( + child: Center( + child: SizedBox( + width: 90.w, + height: 100.h, + child: (_loading) + ? const LoadingWidget() + : Column(children: [ + DeviceContainer( + mainAxisAlignmentDesktop: MainAxisAlignment.spaceBetween, + children: [ + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 35.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (name != "error") Text("Ahoj $name"), + TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: "Kodelog", + applicationVersion: "1.1.0", + applicationLegalese: + "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrlString( + "https://github.com/Royal-Buccaneers/kodelog"), + ) + ]), + child: const Text( + "Licence", + style: Vzhled.textBtn, + ), + ), + TextButton( + onPressed: () async { + await FirebaseAuth.instance.signOut(); + if (!mounted) return; + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (c) => const SignInPage()), + (route) => false); + }, + child: const Text( + "Odhlásit se", + style: Vzhled.textBtn, + ), + ) + ], + ), + ), + MyContainer( + width: (Device.screenType == ScreenType.mobile) + ? 90.w + : 40.w, + child: DeviceContainer( + mainAxisAlignmentDesktop: + MainAxisAlignment.spaceBetween, + children: [ + TextButton( + onPressed: () => Navigator.of(context) + .pushReplacement(MaterialPageRoute( + builder: (context) => + const HlavniOkno())), + child: const Text( + "Denní přehled", + style: TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => + const AllRecordsPage())); + }, + child: const Text( + "Všechny\nzáznamy", + style: TextStyle(color: Vzhled.textColor), + ), + ), + const SizedBox(height: 5), + TextButton( + onPressed: () {}, + child: const Text( + "Nastavení", + style: TextStyle( + color: Vzhled.textColor, + fontWeight: FontWeight.bold, + ), + ), + ), + const SizedBox(height: 5), + OutlinedButton( + onPressed: () => showCreateItemDialog(context), + style: Vzhled.orangeCudlik, + child: const Text( + "Přidat záznam", + ), + ) + ], + ), + ), + ], + ), + const SizedBox(height: 5), + Expanded( + child: MyContainer( + width: 90.w, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple, + ), + width: 400, + child: InkWell( + onTap: () => showProgrammersDialog(context, + jenMenit: true), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + "Upravit programátory", + style: Vzhled.nadpis, + ) + ], + ), + ), + ), + ), + const SizedBox( + height: 15, + ), + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () => showCategoriesDialog(context, [], + jenMenit: true), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + "Upravit kategorie", + style: Vzhled.nadpis, + ) + ], + ), + ), + ), + ), + const SizedBox( + height: 15, + ), + Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular( + 10, + ), + ), + color: Vzhled.purple), + width: 400, + child: InkWell( + onTap: () => showEditJazyk(), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text( + "Oblíbený jazyk", + style: Vzhled.nadpis, + ) + ], + ), + ), + ), + ), + ]), + )) + ]), + ), + )), + ); + } + + showEditJazyk() { + showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Oblíbený jazyk", style: Vzhled.velkyText), + scrollable: true, + content: SizedBox( + width: 20.w, + child: StreamBuilder( + stream: ref.snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + return DropdownButton( + value: snapshot.data!.data()!["favourite"], + dropdownColor: Vzhled.backgroundColor, + items: jazyky + .map( + (e) => DropdownMenuItem( + value: e["jazyk"], + child: SizedBox( + width: 17.w, child: Text(e["jazyk"])), + ), + ) + .toList(), + onChanged: (value) { + ref.update({"favourite": value!}); + }); + } + return const LoadingWidget(); + }), + ), + )); + } +} diff --git a/lib/okna/signin_page.dart b/lib/okna/signin_page.dart new file mode 100644 index 0000000..cdfbace --- /dev/null +++ b/lib/okna/signin_page.dart @@ -0,0 +1,436 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:denikprogramatora/okna/app.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:denikprogramatora/utils/my_container.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +/* + Copyright (C) 2022 Matyáš Caras a Richard Pavlikán + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero 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 Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +class SignInPage extends StatefulWidget { + const SignInPage({super.key}); + + @override + State createState() => _SignInPageState(); +} + +class _SignInPageState extends State { + bool showSignIn = true; + bool isLoading = true; + + TextEditingController emailCon = TextEditingController(); + TextEditingController passwordCon = TextEditingController(); + TextEditingController nameCon = TextEditingController(); + + String jazyk = "C#"; + + @override + void initState() { + super.initState(); + SchedulerBinding.instance.addPostFrameCallback((timeStamp) { + if (FirebaseAuth.instance.currentUser != null) { + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (c) => const HlavniOkno()), + ); + } + + if (mounted) { + setState(() { + isLoading = false; + }); + } + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: isLoading + ? const LoadingWidget() + : Stack( + children: [ + Center( + child: showSignIn ? signInWidget() : registerWidget(), + ), + Positioned( + bottom: 10, + left: 10, + child: TextButton( + onPressed: () => showAboutDialog( + context: context, + applicationName: "Kodelog", + applicationVersion: "1.1.0", + applicationLegalese: + "©️ 2023 Matyáš Caras a Richard Pavlikán,\n vydáno pod licencí AGPLv3", + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrlString( + "https://github.com/Royal-Buccaneers/kodelog"), + ) + ]), + child: const Text( + "Licence", + style: Vzhled.textBtn, + ), + ), + ) + ], + ), + ); + } + + Widget signInWidget() { + GlobalKey form = GlobalKey(); + return MyContainer( + height: 70.h, + width: (Device.screenType == ScreenType.mobile) ? 80.w : 40.w, + child: Form( + key: form, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Kodelog", + style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 30, + ), + SizedBox( + width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("E-mail"), + cursorColor: Vzhled.textColor, + keyboardType: TextInputType.emailAddress, + autocorrect: false, + controller: emailCon, + onFieldSubmitted: (_) { + if (form.currentState!.validate()) { + signIn(); + } + }, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } else if (!RegExp(r'[\w\.]+@[a-z0-9]+\.[a-z]{1,3}') + .hasMatch(value)) { + return "Neplatný e-mail!"; + } + return null; + }, + ), + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("Heslo"), + cursorColor: Vzhled.textColor, + autocorrect: false, + obscureText: true, + controller: passwordCon, + onFieldSubmitted: (_) { + if (form.currentState!.validate()) { + signIn(); + } + }, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), + ), + const SizedBox( + height: 20, + ), + OutlinedButton( + style: Vzhled.orangeCudlik, + onPressed: () { + if (form.currentState!.validate()) { + signIn(); + } + }, + child: const Text("Přihlásit se"), + ), + const SizedBox( + height: 10, + ), + const Text("nebo"), + const SizedBox( + height: 10, + ), + TextButton( + onPressed: () { + setState(() { + showSignIn = false; + }); + }, + child: const Text("Registrovat se", style: Vzhled.textBtn), + ) + ], + ), + ), + ); + } + + Widget registerWidget() { + GlobalKey form = GlobalKey(); + return MyContainer( + width: (Device.screenType == ScreenType.mobile) ? 80.w : 40.w, + child: Form( + key: form, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "Kodelog", + style: TextStyle(fontSize: 20.sp, fontWeight: FontWeight.bold), + textAlign: TextAlign.center, + ), + const SizedBox( + height: 30, + ), + SizedBox( + width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("Jméno"), + cursorColor: Vzhled.textColor, + controller: nameCon, + onFieldSubmitted: (_) { + if (form.currentState!.validate()) { + signUp(); + } + }, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("E-mail"), + cursorColor: Vzhled.textColor, + controller: emailCon, + keyboardType: TextInputType.emailAddress, + autocorrect: false, + onFieldSubmitted: (_) { + if (form.currentState!.validate()) { + signUp(); + } + }, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } else if (!RegExp(r'[\w\.]+@[a-z0-9]+\.[a-z]{1,3}') + .hasMatch(value)) { + return "Neplatný e-mail!"; + } + return null; + }, + ), + ), + const SizedBox( + height: 20, + ), + SizedBox( + width: (Device.screenType == ScreenType.mobile) ? 60.w : 30.w, + child: TextFormField( + decoration: Vzhled.inputDecoration("Heslo"), + cursorColor: Vzhled.textColor, + obscureText: true, + controller: passwordCon, + onFieldSubmitted: (_) { + if (form.currentState!.validate()) { + signUp(); + } + }, + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), + ), + const SizedBox( + height: 20, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Oblíbený jazyk:", + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + const SizedBox(width: 5), + DropdownButton( + value: jazyk, + dropdownColor: Vzhled.backgroundColor, + items: jazyky + .map( + (e) => DropdownMenuItem( + value: e["jazyk"], + child: Text(e["jazyk"]), + ), + ) + .toList(), + onChanged: (value) { + setState(() { + jazyk = (value as String?)!; + }); + }, + ), + ], + ), + const SizedBox( + height: 20, + ), + OutlinedButton( + style: Vzhled.orangeCudlik, + onPressed: () async { + if (form.currentState!.validate()) { + signUp(); + } + }, + child: const Text("Registrovat se"), + ), + const SizedBox( + height: 10, + ), + const Text("nebo"), + const SizedBox( + height: 10, + ), + TextButton( + onPressed: () { + setState(() { + showSignIn = true; + }); + }, + child: const Text("Přihlásit se", style: Vzhled.textBtn), + ) + ], + ), + ), + ); + } + + void signIn() { + FirebaseAuth.instance + .signInWithEmailAndPassword( + email: emailCon.text, password: passwordCon.text) + .then( + (value) => Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (c) => const HlavniOkno()), + ), + ) + .onError((e, st) { + if (e.toString().contains("firebase_auth/user-not-found")) { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Váš účet neexistuje"), + ), + ); + } else if (e.toString().contains("firebase_auth/wrong-password")) { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Zadáváte špatné heslo!"), + ), + ); + } else { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Nastala neznámá chyba."), + ), + ); + debugPrint(e.toString()); + } + }); + } + + void signUp() { + FirebaseAuth.instance + .createUserWithEmailAndPassword( + email: emailCon.text, password: passwordCon.text) + .then( + (value) async { + await FirebaseFirestore.instance + .collection("users") + .doc(FirebaseAuth.instance.currentUser!.uid) + .set({ + "name": nameCon.text, + "email": emailCon.text, + "favourite": jazyk, + }); + value.user?.updateDisplayName(nameCon.text); + + if (!mounted) return; + Navigator.pushReplacement( + context, + MaterialPageRoute(builder: (c) => const HlavniOkno()), + ); + }, + ).onError((error, stackTrace) { + if (error.toString().contains("firebase_auth/email-already-in-use")) { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("E-mail je již zaregistrovaný"), + ), + ); + } else { + showDialog( + context: context, + builder: (c) => const AlertDialog( + title: Text("Chyba"), + content: Text("Nastala neznámá chyba."), + ), + ); + debugPrint(error.toString()); + } + }); + } +} diff --git a/lib/utils/datum.dart b/lib/utils/datum.dart new file mode 100644 index 0000000..582635e --- /dev/null +++ b/lib/utils/datum.dart @@ -0,0 +1,5 @@ +extension DateString on DateTime { + String get dateString => "$day. $month. $year"; + String get dateTimeString => + "$day. $month. $year $hour:${minute < 10 ? "0$minute" : minute}"; +} diff --git a/lib/utils/devicecontainer.dart b/lib/utils/devicecontainer.dart new file mode 100644 index 0000000..ab81235 --- /dev/null +++ b/lib/utils/devicecontainer.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +// Předloha pro widgety, které se mění podle druhu zařízení +abstract class DeviceWidget + extends StatelessWidget { + const DeviceWidget({super.key}); + + @override + Widget build(BuildContext context) { + if (Device.screenType == ScreenType.mobile) { + return buildProMobily(context); + } else { + return buildProDesktop(context); + } + } + + M buildProMobily(BuildContext context); + + D buildProDesktop(BuildContext context); +} + +class DeviceContainer extends DeviceWidget { + final MainAxisAlignment mainAxisAlignmentMobile; + final MainAxisAlignment mainAxisAlignmentDesktop; + final CrossAxisAlignment crossAxisAlignment; + final List children; + + /// Vytvoří kontejner, kde na mobilech je [Column] a na ostatních [Row] + const DeviceContainer( + {super.key, + this.children = const [], + this.mainAxisAlignmentMobile = MainAxisAlignment.center, + this.mainAxisAlignmentDesktop = MainAxisAlignment.start, + this.crossAxisAlignment = CrossAxisAlignment.center}); + + @override + Row buildProDesktop(BuildContext context) => Row( + mainAxisAlignment: mainAxisAlignmentDesktop, + children: children, + ); + + @override + Column buildProMobily(BuildContext context) => Column( + mainAxisAlignment: mainAxisAlignmentMobile, + children: children, + ); +} diff --git a/lib/utils/dokument.dart b/lib/utils/dokument.dart new file mode 100644 index 0000000..7689f59 --- /dev/null +++ b/lib/utils/dokument.dart @@ -0,0 +1,12 @@ +import 'package:fleather/fleather.dart'; + +extension ActualJson on ParchmentDocument { + List> toActualJson() { + var out = >[]; + var d = toDelta().toList(); + for (var element in d) { + out.add(element.toJson()); + } + return out; + } +} diff --git a/lib/utils/input_decoration.dart b/lib/utils/input_decoration.dart new file mode 100644 index 0000000..680e386 --- /dev/null +++ b/lib/utils/input_decoration.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +InputDecoration inputDecoration(String text) { + return InputDecoration( + labelStyle: const TextStyle(color: Colors.white), + label: Text( + text, + ), + border: UnderlineInputBorder( + borderSide: const BorderSide(color: Colors.black, width: 2.0), + borderRadius: BorderRadius.circular(6.0)), + focusedBorder: UnderlineInputBorder( + borderSide: const BorderSide(color: Color(0xffCA1F3D), width: 2.0), + borderRadius: BorderRadius.circular(10.0), + ), + ); +} diff --git a/lib/utils/loading_widget.dart b/lib/utils/loading_widget.dart new file mode 100644 index 0000000..865d7f9 --- /dev/null +++ b/lib/utils/loading_widget.dart @@ -0,0 +1,29 @@ +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:flutter/material.dart'; + +class LoadingWidget extends StatelessWidget { + const LoadingWidget({super.key}); + + @override + Widget build(BuildContext context) { + return const Center( + child: CircularProgressIndicator( + color: Vzhled.purple, + ), + ); + } +} + +final jazyky = >[ + {"jazyk": "C#", "barva": 0xff8200f3}, + {"jazyk": "JavaScript", "barva": 0xfffdd700}, + {"jazyk": "Python", "barva": 0xff0080ee}, + {"jazyk": "PHP🤢", "barva": 0xff00abff}, + {"jazyk": "C++", "barva": 0xff1626ff}, + {"jazyk": "Kotlin", "barva": 0xffe34b7c}, + {"jazyk": "Java", "barva": 0xfff58219}, + {"jazyk": "Dart", "barva": 0xff40c4ff}, + {"jazyk": "F#", "barva": 0xff85ddf3}, + {"jazyk": "Elixir", "barva": 0xff543465}, + {"jazyk": "Carbon", "barva": 0xff606060} +]; diff --git a/lib/utils/months.dart b/lib/utils/months.dart new file mode 100644 index 0000000..cdc34f0 --- /dev/null +++ b/lib/utils/months.dart @@ -0,0 +1,22 @@ +final months = [ + Month("Leden", 31, 0), + Month("Únor", 28, 1), + Month("Březen", 31, 2), + Month("Duben", 30, 3), + Month("Květen", 31, 4), + Month("Červen", 30, 5), + Month("Červenec", 31, 6), + Month("Srpen", 31, 7), + Month("Září", 30, 8), + Month("Říjen", 31, 9), + Month("Listopad", 30, 10), + Month("Prosinec", 31, 11), +]; + +class Month { + String name; + int days; + int position; + + Month(this.name, this.days, this.position); +} diff --git a/lib/utils/my_category.dart b/lib/utils/my_category.dart new file mode 100644 index 0000000..e7b583c --- /dev/null +++ b/lib/utils/my_category.dart @@ -0,0 +1,6 @@ +class MyCategory { + String name; + String id; + + MyCategory(this.name, this.id); +} diff --git a/lib/utils/my_container.dart b/lib/utils/my_container.dart new file mode 100644 index 0000000..6901d33 --- /dev/null +++ b/lib/utils/my_container.dart @@ -0,0 +1,43 @@ +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +class MyContainer extends StatelessWidget { + const MyContainer( + {required this.child, this.width, this.height, this.padding, super.key}); + final Widget child; + final double? width; + final double? height; + final double? padding; + + @override + Widget build(BuildContext context) { + return Container( + margin: padding == null + ? const EdgeInsets.only(left: 30, top: 15, right: 30, bottom: 15) + : EdgeInsets.all(padding!), + width: width ?? + ((Device.screenType == ScreenType.mobile) + ? Adaptive.w(80) + : Adaptive.w(60)), + height: height, + decoration: const BoxDecoration( + color: Vzhled.dialogColor, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(10), + topRight: Radius.circular(10), + bottomLeft: Radius.circular(10), + bottomRight: Radius.circular(10)), + // boxShadow: [ + // BoxShadow( + // color: const Color.fromARGB(255, 74, 74, 74).withOpacity(0.25), + // spreadRadius: 3, + // blurRadius: 5, + // offset: const Offset(0, 3), + // ), + // ], + ), + child: Padding(padding: const EdgeInsets.all(15), child: child), + ); + } +} diff --git a/lib/utils/new_record_dialog.dart b/lib/utils/new_record_dialog.dart new file mode 100644 index 0000000..01a5dff --- /dev/null +++ b/lib/utils/new_record_dialog.dart @@ -0,0 +1,936 @@ +import 'package:denikprogramatora/okna/app.dart'; +import 'package:denikprogramatora/utils/datum.dart'; +import 'package:denikprogramatora/utils/devicecontainer.dart'; +import 'package:denikprogramatora/utils/dokument.dart'; +import 'package:denikprogramatora/utils/loading_widget.dart'; +import 'package:denikprogramatora/utils/programmer.dart'; +import 'package:denikprogramatora/utils/really_delete.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:fleather/fleather.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; +import 'package:uuid/uuid.dart'; + +import '../utils/my_category.dart'; + +Future showCreateItemDialog(context, + {DateTime? from, + DateTime? to, + String? j, + int hvezdicky = 0, + String? p, + List? k, + String? originalId, + ParchmentDocument? doc}) async { + DateTime fromDate = from ?? + DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, + DateTime.now().hour - 1); + DateTime toDate = to ?? + DateTime(DateTime.now().year, DateTime.now().month, DateTime.now().day, + DateTime.now().hour); + + // nastavení jazyka na oblíbený jazyk + String jazyk = "C#"; + if (j == null) { + await ref.get().then((value) { + jazyk = value["favourite"]; + }); + } else { + jazyk = j; + } + + int review = hvezdicky; + + Programmer programmer = p == null + ? Programmer(name, userUid) + : await Programmer.ziskatProgramatora( + FirebaseAuth.instance.currentUser!, p); + + List categories = k ?? []; + + FleatherController controller = + doc == null ? FleatherController() : FleatherController(doc); + + await showDialog( + context: context, + builder: (_) => AlertDialog( + scrollable: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + title: Text(doc == null ? "Nový záznam" : "Úprava záznamu", + style: Vzhled.velkyText), + content: StatefulBuilder( + builder: (context, setState) { + return Column( + children: [ + DeviceContainer( + mainAxisAlignmentDesktop: MainAxisAlignment.spaceEvenly, + children: [ + SizedBox( + width: + (Device.screenType == ScreenType.mobile) ? 80.w : 40.w, + child: Column( + children: [ + const Align( + alignment: Alignment.centerLeft, + child: Text( + "Datum a čas", + style: Vzhled.nadpis, + ), + ), + const SizedBox(height: 15), + Row( + children: [ + Text("Od ${toDate.dateTimeString}"), + const SizedBox(width: 15), + TextButton( + onPressed: () { + showDatePicker( + context: context, + initialDate: fromDate, + firstDate: + DateTime(DateTime.now().year - 5), + lastDate: + DateTime(DateTime.now().year + 5)) + .then((value) { + showTimePicker( + context: context, + initialTime: + TimeOfDay.fromDateTime(fromDate)) + .then((time) { + if (value!.day == toDate.day && + value.month == toDate.month && + value.year == toDate.year && + (time!.hour > toDate.hour || + (time.hour <= toDate.hour && + time.minute > toDate.minute))) { + return; + } + setState(() { + fromDate = DateTime( + value.year, + value.month, + value.day, + time!.hour, + time.minute); + }); + }); + }); + }, + child: const Text( + "Změnit", + style: Vzhled.textBtn, + ), + ) + ], + ), + const SizedBox(height: 5), + Row( + children: [ + Text("Do ${toDate.dateTimeString}"), + const SizedBox(width: 15), + TextButton( + onPressed: () { + showDatePicker( + context: context, + initialDate: toDate, + firstDate: + DateTime(DateTime.now().year - 5), + lastDate: + DateTime(DateTime.now().year + 5)) + .then((value) { + showTimePicker( + context: context, + initialTime: + TimeOfDay.fromDateTime(toDate)) + .then((time) { + if (value!.day == fromDate.day && + value.month == fromDate.month && + value.year == fromDate.year && + (time!.hour < fromDate.hour || + (time.hour >= fromDate.hour && + time.minute < + fromDate.minute))) { + return; + } + setState(() { + toDate = DateTime(value.year, value.month, + value.day, time!.hour, time.minute); + }); + }); + }); + }, + child: const Text( + ("Změnit"), + style: Vzhled.textBtn, + ), + ) + ], + ), + const SizedBox(height: 30), + const Align( + alignment: Alignment.centerLeft, + child: Text( + "Strávený čas", + style: Vzhled.nadpis, + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: Text( + "${toDate.difference(fromDate).inHours} ${toDate.difference(fromDate).inHours == 1 ? "hodina" : toDate.difference(fromDate).inHours > 1 && toDate.difference(fromDate).inHours < 5 ? "hodiny" : "hodin"}${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60 == 0) ? "" : " a ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60)} ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) == 1 ? "minuta" : (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) > 1 && (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) < 5 ? "minuty" : "minut"}"}"), + ), + const SizedBox(height: 30), + const Align( + alignment: Alignment.centerLeft, + child: Text( + "Programátor", + style: Vzhled.nadpis, + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: TextButton( + onPressed: () async { + await showProgrammersDialog(context, doc: doc) + .then((value) { + setState(() { + programmer = value; + }); + }); + }, + child: Text( + programmer.name, + style: Vzhled.textBtn, + ), + ), + ), + ], + ), + ), + SizedBox( + width: + (Device.screenType == ScreenType.mobile) ? 80.w : 40.w, + child: Column( + children: [ + const Align( + alignment: Alignment.centerLeft, + child: Text( + "Programovací jazyk", + style: Vzhled.nadpis, + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: DropdownButton( + value: jazyk, + dropdownColor: Vzhled.backgroundColor, + items: jazyky + .map( + (e) => DropdownMenuItem( + value: e["jazyk"], + child: Text(e["jazyk"]), + ), + ) + .toList(), + onChanged: (value) { + setState(() { + jazyk = (value as String?)!; + }); + }, + ), + ), + const SizedBox(height: 30), + const Align( + alignment: Alignment.centerLeft, + child: Text( + "Hodnocení", + style: Vzhled.nadpis, + ), + ), + const SizedBox(height: 15), + Align( + alignment: Alignment.centerLeft, + child: Row( + children: List.generate( + 5, + (index) { + return IconButton( + onPressed: () { + setState(() { + review = index + 1; + }); + }, + icon: Icon(Icons.star, + color: (index + 1) <= review + ? Colors.yellow + : Colors.grey), + ); + }, + ), + ), + ), + const SizedBox(height: 30), + Align( + alignment: Alignment.centerLeft, + child: Row( + children: [ + const Text( + "Kategorie", + style: Vzhled.nadpis, + ), + const SizedBox(width: 15), + TextButton( + onPressed: () async { + List newCategories = + await showCategoriesDialog( + context, categories); + setState(() { + categories = newCategories; + }); + }, + child: const Text( + "Vybrat", + style: Vzhled.textBtn, + ), + ) + ], + ), + ), + const SizedBox(height: 15), + Row( + children: List.generate( + categories.length, + (index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text(categories[index].name), + ); + }, + ), + ) + ], + ), + ), + ], + ), + const SizedBox(height: 20), + SizedBox( + height: Adaptive.h(35), + width: Adaptive.w(100), + child: Column( + children: [ + FleatherToolbar.basic( + controller: controller, hideHorizontalRule: true), + Expanded( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: FleatherEditor(controller: controller), + ), + ), + ), + ], + ), + ), + const SizedBox(height: 20), + OutlinedButton( + style: Vzhled.orangeCudlik, + onPressed: () { + if (originalId == null) { + ref.collection("records").add({ + "fromDate": fromDate, + "toDate": toDate, + "codingTime": + "${toDate.difference(fromDate).inHours} ${toDate.difference(fromDate).inHours == 1 ? "hodina" : toDate.difference(fromDate).inHours > 1 && toDate.difference(fromDate).inHours < 5 ? "hodiny" : "hodin"}${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60 == 0) ? "" : " a ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60)} ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) == 1 ? "minuta" : (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) > 1 && (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) < 5 ? "minuty" : "minut"}"}", + "programmer": programmer.id, + "programmerName": programmer.name, + "language": jazyky + .where((element) => element["jazyk"] == jazyk) + .toList()[0], + "review": review, + "categories": categories.map((e) => e.id).toList(), + "description": controller.document.toActualJson(), + }).then((value) => + Navigator.of(context, rootNavigator: true) + .pop("dialog")); + return; + } + ref.collection("records").doc(originalId).update({ + "fromDate": fromDate, + "toDate": toDate, + "codingTime": + "${toDate.difference(fromDate).inHours} ${toDate.difference(fromDate).inHours == 1 ? "hodina" : toDate.difference(fromDate).inHours > 1 && toDate.difference(fromDate).inHours < 5 ? "hodiny" : "hodin"}${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60 == 0) ? "" : " a ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60)} ${(toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) == 1 ? "minuta" : (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) > 1 && (toDate.difference(fromDate).inMinutes - toDate.difference(fromDate).inHours * 60) < 5 ? "minuty" : "minut"}"}", + "programmer": programmer.id, + "programmerName": programmer.name, + "language": jazyky + .where((element) => element["jazyk"] == jazyk) + .toList()[0], + "review": review, + "categories": categories.map((e) => e.id).toList(), + "description": controller.document.toActualJson(), + }).then((value) => + Navigator.of(context, rootNavigator: true).pop("dialog")); + }, + child: Text( + (originalId == null) ? "Vytvořit" : "Změnit", + style: const TextStyle( + fontSize: Vzhled.text, + ), + ), + ) + ], + ); + }, + ), + ), + ); +} + +Future showProgrammersDialog(context, + {ParchmentDocument? doc, bool jenMenit = false}) async { + bool showAddProgrammer = false; + bool editing = false; + GlobalKey key = GlobalKey(); + TextEditingController nameCon = TextEditingController(); + late String editId; + + Programmer programmer = Programmer(name, userUid); + + await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Programátoři", style: Vzhled.velkyText), + scrollable: true, + content: SizedBox( + width: 50.w, + child: StatefulBuilder( + builder: (context, setState) { + return showAddProgrammer + ? Form( + key: key, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: TextFormField( + controller: nameCon, + decoration: Vzhled.inputDecoration("Jméno"), + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), + ), + const SizedBox(width: 5), + TextButton( + onPressed: () { + if (key.currentState!.validate()) { + if (editing) { + ref + .collection("programmers") + .doc(editId) + .update({"name": nameCon.text}); + } else { + var uuid = const Uuid(); + String id = uuid.v1(); + + ref + .collection("programmers") + .doc(id) + .set({"name": nameCon.text, "id": id}); + } + nameCon.text = ""; + + setState(() { + editing = false; + showAddProgrammer = false; + }); + } + }, + child: Text( + editing ? "Uložit" : "Přidat", + style: Vzhled.textBtn, + ), + ), + ], + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + setState(() { + showAddProgrammer = false; + }); + }, + child: const Text( + "Zpátky", + style: Vzhled.textBtn, + ), + ), + ], + ), + ) + : Column( + children: [ + Material( + color: Vzhled.dialogColor, + child: InkWell( + splashColor: (jenMenit) ? Colors.transparent : null, + onTap: () { + if (jenMenit) return; + programmer = Programmer(name, userUid); + Navigator.of(context, rootNavigator: true) + .pop("dialog"); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(name), + if (!jenMenit) + TextButton( + onPressed: () { + programmer = Programmer(name, userUid); + Navigator.of(context, rootNavigator: true) + .pop("dialog"); + }, + child: const Text( + "Vybrat", + style: Vzhled.textBtn, + ), + ), + ], + ), + ), + ), + ), + StreamBuilder( + stream: ref.collection("programmers").snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + var docs = snapshot.data!.docs; + + return Column( + children: List.generate(docs.length, (index) { + var data = docs[index].data(); + return Material( + color: Vzhled.dialogColor, + child: InkWell( + splashColor: (jenMenit) + ? Colors.transparent + : null, + onTap: () { + if (jenMenit) return; + programmer = Programmer( + data["name"], data["id"]); + Navigator.of(context, + rootNavigator: true) + .pop("dialog"); + }, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text(data["name"]), + Row( + children: [ + if (!jenMenit) + TextButton( + onPressed: () { + programmer = Programmer( + data["name"], + data["id"]); + Navigator.of(context, + rootNavigator: + true) + .pop("dialog"); + }, + child: const Text( + "Vybrat", + style: Vzhled.textBtn, + ), + ), + IconButton( + onPressed: () { + editId = data["id"]; + + setState(() { + showAddProgrammer = true; + editing = true; + nameCon = + TextEditingController( + text: + data["name"]); + }); + }, + icon: const Icon(Icons.edit, + color: Vzhled.textColor), + ), + IconButton( + onPressed: () { + showReallyDelete(context, + () async { + Navigator.of(context, + rootNavigator: + true) + .pop("dialog"); + Navigator.of(context, + rootNavigator: + true) + .pop("dialog"); + if (doc != null) { + Navigator.of(context, + rootNavigator: + true) + .pop("dialog"); + } + + // deleting all records + await ref + .collection("records") + .where("programmer", + isEqualTo: + data["id"]) + .get() + .then((value) { + for (var snap + in value.docs) { + ref + .collection( + "records") + .doc(snap.id) + .delete(); + } + }); + + // deleting + await ref + .collection( + "programmers") + .doc(data["id"]) + .delete(); + }, + doNavigatorPop: false, + text: + "Odstranit programátora a všechny jeho záznamy?"); + }, + icon: const Icon(Icons.delete, + color: Vzhled.textColor), + ), + ], + ) + ], + ), + ), + ), + ); + }), + ); + } + return const LoadingWidget(); + }), + const SizedBox(height: 5), + TextButton( + onPressed: () { + setState(() { + showAddProgrammer = true; + }); + }, + child: const Text( + "Přidat nového programátora", + style: Vzhled.textBtn, + ), + ), + ], + ); + }, + ), + ), + ), + ); + + return programmer; +} + +Future> showCategoriesDialog( + context, List categories, + {bool jenMenit = false}) async { + bool showCategoryEdit = false; + bool editing = false; + GlobalKey key = GlobalKey(); + TextEditingController nameCon = TextEditingController(); + int color = 0; + String editId = ""; + + Map selected = {}; + + // fetching data for all categories and setting selected to false + await ref.collection("categories").get().then((value) { + for (var snap in value.docs) { + selected[snap.id] = false; + } + }); + + // already selected categories setting to true + for (var snap in categories) { + selected[snap.id] = true; + } + + await showDialog( + context: context, + builder: (_) => AlertDialog( + title: const Text("Kategorie", style: Vzhled.velkyText), + scrollable: true, + content: SizedBox( + width: 50.w, + child: StatefulBuilder( + builder: (context, setState) { + return showCategoryEdit + ? Form( + key: key, + child: Column( + children: [ + Row( + children: [ + Expanded( + child: TextFormField( + controller: nameCon, + decoration: Vzhled.inputDecoration("Název"), + validator: (value) { + if (value!.trim().isEmpty) { + return "Toto pole je povinné!"; + } + return null; + }, + ), + ), + const SizedBox(width: 5), + TextButton( + onPressed: () { + if (key.currentState!.validate()) { + if (editing) { + ref + .collection("categories") + .doc(editId) + .update({ + "name": nameCon.text, + "color": color, + }); + } else { + var uuid = const Uuid(); + String id = uuid.v1(); + + ref.collection("categories").doc(id).set({ + "name": nameCon.text, + "id": id, + "color": color + }); + + selected[id] = false; + } + + nameCon.text = ""; + + setState(() { + showCategoryEdit = false; + editing = false; + }); + } + }, + child: Text( + editing ? "Uložit" : "Přidat", + style: Vzhled.textBtn, + ), + ), + ], + ), + const SizedBox(height: 5), + TextButton( + onPressed: () { + setState(() { + showCategoryEdit = false; + }); + }, + child: const Text( + "Zpátky", + style: Vzhled.textBtn, + ), + ), + ], + ), + ) + : Column( + children: [ + StreamBuilder( + stream: ref.collection("categories").snapshots(), + builder: (context, snapshot) { + if (snapshot.hasData) { + var docs = snapshot.data!.docs; + + return Column( + children: List.generate(docs.length, (index) { + var data = docs[index].data(); + + return Material( + color: Vzhled.dialogColor, + child: InkWell( + splashColor: (jenMenit) + ? Colors.transparent + : null, + onTap: (() { + if (jenMenit) return; + setState( + () { + selected[data["id"]] = + !selected[data["id"]]; + }, + ); + }), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text(data["name"]), + (selected[data["id"]]) + ? TextButton( + onPressed: () { + setState(() { + selected[data["id"]] = + false; + }); + }, + child: const Text( + "Odebrat", + style: Vzhled.textBtn, + ), + ) + : Row( + children: [ + if (!jenMenit) + TextButton( + onPressed: () { + setState(() { + selected[data[ + "id"]] = true; + }); + }, + child: const Text( + "Vybrat", + style: + Vzhled.textBtn, + ), + ), + IconButton( + onPressed: () { + editId = data["id"]; + + setState(() { + showCategoryEdit = + true; + editing = true; + nameCon = + TextEditingController( + text: data[ + "name"]); + }); + }, + icon: const Icon( + Icons.edit, + color: Vzhled + .textColor, + size: 18), + ), + IconButton( + onPressed: () { + showReallyDelete( + context, () { + Navigator.of( + context, + rootNavigator: + true) + .pop("dialog"); + + ref + .collection( + "categories") + .doc(data["id"]) + .delete(); + }, + doNavigatorPop: + false); + }, + icon: const Icon( + Icons.delete, + color: Vzhled + .textColor, + size: 18), + ) + ], + ), + ], + ), + ), + ), + ); + }), + ); + } + return const LoadingWidget(); + }), + const SizedBox(height: 5), + TextButton( + onPressed: () { + setState(() { + showCategoryEdit = true; + }); + }, + child: const Text( + "Nová kategorie", + style: Vzhled.textBtn, + ), + ), + ], + ); + }, + ), + ), + ), + ); + + // creating new list + categories = []; + await ref.collection("categories").get().then( + (value) { + var docs = value.docs; + for (var snap in docs) { + if (selected[snap.id]) { + var data = snap.data(); + categories.add(MyCategory(data["name"], snap.id)); + } + } + }, + ); + + // popravde som prave napisal totalne shitny kod, ale nemam najmensiu chybu hladata lepsie riesenie. ak toto citas kludne to cele prepis a odstran + + return categories; +} diff --git a/lib/utils/programmer.dart b/lib/utils/programmer.dart new file mode 100644 index 0000000..897ee98 --- /dev/null +++ b/lib/utils/programmer.dart @@ -0,0 +1,23 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; + +class Programmer { + final String name; + final String id; + + const Programmer(this.name, this.id); + + static Future ziskatProgramatora(User current, String id) async { + if (id == current.uid) { + return Programmer(current.displayName!, id); + } else { + var d = await FirebaseFirestore.instance + .collection("users") + .doc(current.uid) + .collection("programmers") + .doc(id) + .get(); + return Programmer(d.data()!["name"], id); + } + } +} diff --git a/lib/utils/razeni.dart b/lib/utils/razeni.dart new file mode 100644 index 0000000..3be7eae --- /dev/null +++ b/lib/utils/razeni.dart @@ -0,0 +1,38 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; + +var razeni = [ + // VZESTUPNĚ ČAS + (QueryDocumentSnapshot> a, + QueryDocumentSnapshot> b) => + ((a.data()["fromDate"] as Timestamp).toDate().hour == + (b.data()["fromDate"] as Timestamp).toDate().hour) + ? (a.data()["fromDate"] as Timestamp) + .toDate() + .minute + .compareTo((b.data()["fromDate"] as Timestamp).toDate().minute) + : (a.data()["fromDate"] as Timestamp) + .toDate() + .hour + .compareTo((b.data()["fromDate"] as Timestamp).toDate().hour), + // SESTUPNĚ ČAS + (QueryDocumentSnapshot> a, + QueryDocumentSnapshot> b) => + ((b.data()["fromDate"] as Timestamp).toDate().hour == + (a.data()["fromDate"] as Timestamp).toDate().hour) + ? (b.data()["fromDate"] as Timestamp) + .toDate() + .minute + .compareTo((a.data()["fromDate"] as Timestamp).toDate().minute) + : (b.data()["fromDate"] as Timestamp) + .toDate() + .hour + .compareTo((a.data()["fromDate"] as Timestamp).toDate().hour), + // VZESTUPNĚ HODNOCENÍ + (QueryDocumentSnapshot> a, + QueryDocumentSnapshot> b) => + (a.data()["review"] as int).compareTo(b.data()["review"]), + // SESTUPNĚ HODNOCENÍ + (QueryDocumentSnapshot> a, + QueryDocumentSnapshot> b) => + (b.data()["review"] as int).compareTo(a.data()["review"]), +]; diff --git a/lib/utils/really_delete.dart b/lib/utils/really_delete.dart new file mode 100644 index 0000000..8cc437c --- /dev/null +++ b/lib/utils/really_delete.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +showReallyDelete(BuildContext context, void Function() onPressed, + {text = "Odstranit tuto položku?", bool doNavigatorPop = true}) async { + if (doNavigatorPop) Navigator.of(context, rootNavigator: true).pop("dialog"); + showDialog( + context: context, + builder: (ctx) => AlertDialog( + title: Text(text), + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all( + Radius.circular(10.0), + ), + ), + actions: [ + TextButton( + onPressed: () => + Navigator.of(ctx, rootNavigator: true).pop("dialog"), + child: + const Text("Ne", style: TextStyle(color: Colors.blue))), + TextButton(onPressed: onPressed, child: const Text("Ano")) + ], + )); +} diff --git a/lib/utils/show_info_dialog.dart b/lib/utils/show_info_dialog.dart new file mode 100644 index 0000000..d7b3290 --- /dev/null +++ b/lib/utils/show_info_dialog.dart @@ -0,0 +1,230 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:denikprogramatora/okna/app.dart'; +import 'package:denikprogramatora/utils/datum.dart'; +import 'package:denikprogramatora/utils/devicecontainer.dart'; +import 'package:denikprogramatora/utils/my_category.dart'; +import 'package:denikprogramatora/utils/new_record_dialog.dart'; +import 'package:denikprogramatora/utils/really_delete.dart'; +import 'package:denikprogramatora/utils/vzhled.dart'; +import 'package:firebase_auth/firebase_auth.dart'; +import 'package:fleather/fleather.dart'; +import 'package:flutter/material.dart'; +import 'package:responsive_sizer/responsive_sizer.dart'; + +void showInfoDialog( + context, Map data, String originalId) async { + var denOd = (data["fromDate"] as Timestamp).toDate().toLocal(); + var denDo = (data["toDate"] as Timestamp).toDate().toLocal(); + List categories = []; + + if ((data["categories"] as List).isNotEmpty) { + for (var category in data["categories"]) { + await ref.collection("categories").doc(category).get().then((value) { + var data = value.data(); + + categories.add(MyCategory(data!["name"], category)); + }).onError((error, stackTrace) => null); + } + } + + showDialog( + context: context, + builder: (_) { + var document = ParchmentDocument.fromJson(data["description"]); + var controller = FleatherController(document); + + return AlertDialog( + actions: [ + TextButton( + onPressed: () => showReallyDelete( + context, + () => FirebaseFirestore.instance + .collection("users") + .doc(FirebaseAuth.instance.currentUser!.uid) + .collection("records") + .doc(originalId) + .delete() + .then((_) => Navigator.of(context).pop())), + style: Vzhled.orangeCudlik, + child: const Text("Smazat")), + TextButton( + style: Vzhled.purpleCudlik, + onPressed: () { + showCreateItemDialog(context, + from: denOd, + to: denDo, + k: categories, + p: data["programmer"], + hvezdicky: data["review"], + originalId: originalId, + j: data["language"]["jazyk"], + doc: document) + .then((_) => Navigator.of(context).pop()); + }, + child: const Text("Upravit")), + TextButton( + onPressed: () => Navigator.of(context).pop(), + style: Vzhled.purpleCudlik, + child: const Text("Zavřít"), + ), + ], + title: Text( + "Záznam ze dne ${denOd.dateString}", + style: Vzhled.dialogNadpis, + ), + scrollable: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20), + ), + content: SizedBox( + height: 60.h, + width: 90.w, + child: SingleChildScrollView( + child: Column( + children: [ + DeviceContainer( + children: [ + SizedBox( + width: (Device.screenType == ScreenType.mobile) + ? 80.w + : 40.w, + child: Column( + children: [ + Row( + children: [ + const Text( + "Datum: ", + style: Vzhled.nadpis, + ), + Text( + (Device.screenType == ScreenType.mobile) + ? "od ${denOd.dateTimeString}\ndo ${denDo.dateTimeString} (${data["codingTime"]})" + : "od ${denOd.dateTimeString} do ${denDo.dateTimeString} (${data["codingTime"]})", + ) + ], + ), + const SizedBox( + height: 15, + ), + Row( + children: [ + const Text( + "Jazyk: ", + style: Vzhled.nadpis, + ), + Text( + data["language"]["jazyk"], + style: TextStyle( + color: Color(data["language"]["barva"])), + ) + ], + ), + const SizedBox( + height: 15, + ), + ], + ), + ), + SizedBox( + width: (Device.screenType == ScreenType.mobile) + ? 80.w + : 40.w, + child: Column( + children: [ + Row( + children: [ + const Text( + "Programátor: ", + style: Vzhled.nadpis, + ), + Text( + data["programmerName"], + ) + ], + ), + const SizedBox( + height: 15, + ), + Row( + children: [ + const Text( + "Hodnocení: ", + style: Vzhled.nadpis, + ), + Row( + children: List.generate( + 5, + (index) { + return Icon(Icons.star, + color: (index + 1) <= data["review"] + ? Colors.yellow + : Colors.grey); + }, + ), + ) + ], + ), + ], + ), + ), + ], + ), + const SizedBox( + height: 15, + ), + if (categories.isNotEmpty) + Column( + children: [ + const Align( + alignment: Alignment.centerLeft, + child: Text( + "Kategorie", + style: Vzhled.nadpis, + ), + ), + const SizedBox(height: 15), + Row( + children: List.generate( + categories.length, + (index) { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Text(categories[index].name), + ); + }, + ), + ), + const SizedBox(height: 20), + ], + ), + SizedBox( + height: Adaptive.h(35), + width: Adaptive.w(100), + child: Column( + children: [ + Expanded( + child: Container( + decoration: BoxDecoration( + border: Border.all(color: Colors.white), + borderRadius: const BorderRadius.all( + Radius.circular(10), + ), + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: FleatherEditor( + readOnly: true, controller: controller), + ), + ), + ), + ], + ), + ) + ], + ), + ), + ), + ); + }, + ); +} diff --git a/lib/utils/vzhled.dart b/lib/utils/vzhled.dart new file mode 100644 index 0000000..f0471a9 --- /dev/null +++ b/lib/utils/vzhled.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; + +class Vzhled { + static const purple = Color.fromARGB(255, 105, 108, 180); + static const textColor = Color(0xfff1f1f3); + static const orange = Color(0xffff8200); //f58f29); + static const backgroundColor = Color(0xff17161b); + static const dialogColor = Color(0xff28272c); + + /// Normální tučný velký nadpis + static const TextStyle nadpis = + TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: textColor); + + /// Styl nadpisu v dialogu + static const TextStyle dialogNadpis = + TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: textColor); + + /// Text o velikosti nadpisu, ale normální tloušťka + static const TextStyle velkyText = TextStyle(fontSize: 22, color: textColor); + + static const double text = 14; + + /// Větší text, ale ne tučný + static const TextStyle mensiAleVelkyText = + TextStyle(fontSize: 16, color: textColor); + + static final ButtonStyle purpleCudlik = OutlinedButton.styleFrom( + backgroundColor: purple, + foregroundColor: textColor, + padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ); + + static final ButtonStyle orangeCudlik = OutlinedButton.styleFrom( + backgroundColor: orange, + foregroundColor: Colors.black, + padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), + textStyle: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold), + ); + + static const TextStyle textBtn = TextStyle( + color: Vzhled.purple, + fontWeight: FontWeight.bold, + decoration: TextDecoration.underline, + ); + + static InputDecoration inputDecoration(String text) { + return InputDecoration( + labelStyle: const TextStyle(color: Vzhled.textColor), + label: Text( + text, + ), + enabledBorder: OutlineInputBorder( + borderSide: const BorderSide( + color: Color.fromARGB(255, 173, 173, 173), width: 2.0), + borderRadius: BorderRadius.circular(6.0), + ), + focusedBorder: OutlineInputBorder( + borderSide: const BorderSide(color: Vzhled.purple, width: 2.0), + borderRadius: BorderRadius.circular(10.0), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..740401c --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,463 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: "6215ac7d00ed98300b72f45ed2b38c2ca841f9f4e6965fab33cbd591e45e4473" + url: "https://pub.dev" + source: hosted + version: "1.0.13" + async: + dependency: transitive + description: + name: async + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" + source: hosted + version: "2.10.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" + source: hosted + version: "1.2.1" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "9e775f9df26a165444bd5240f70bfee6f11b35c5e913e93ed4b06bf50b231325" + url: "https://pub.dev" + source: hosted + version: "4.3.2" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: ab35c068896ff769ce7e8de8198228d512e7f056fc8f26b2ff53ea3f97c8545f + url: "https://pub.dev" + source: hosted + version: "5.10.2" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: b7b52c2ad50d1105f2e0585a34288da415cf9d1037470985c7c57cce7b06d95f + url: "https://pub.dev" + source: hosted + version: "3.2.2" + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + csslib: + dependency: transitive + description: + name: csslib + sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 + url: "https://pub.dev" + source: hosted + version: "0.17.2" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" + source: hosted + version: "1.0.5" + diff_match_patch: + dependency: transitive + description: + name: diff_match_patch + sha256: "2efc9e6e8f449d0abe15be240e2c2a3bcd977c8d126cfd70598aee60af35c0a4" + url: "https://pub.dev" + source: hosted + version: "0.4.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: "843e307e9b7faa026dd9970e584b5d53265fb5a0c4323883fecdce89ec05d56a" + url: "https://pub.dev" + source: hosted + version: "4.2.6" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "8702baa08ad5aa6daa023082d612ca168bf3f7de81e3d56e1df18321f76d675f" + url: "https://pub.dev" + source: hosted + version: "6.11.8" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: "0c01b9c772ee730df03ac92102e538873558f908d6e42602f6ff9c61dead8d58" + url: "https://pub.dev" + source: hosted + version: "5.2.5" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: be13e431c0c950f0fc66bdb67b41b8059121d7e7d8bbbc21fb59164892d561f8 + url: "https://pub.dev" + source: hosted + version: "2.5.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: "5615b30c36f55b2777d0533771deda7e5730e769e5d3cb7fda79e9bed86cfa55" + url: "https://pub.dev" + source: hosted + version: "4.5.3" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "4b3a41410f3313bb95fd560aa5eb761b6ad65c185de772c72231e8b4aeed6d18" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + fleather: + dependency: "direct main" + description: + name: fleather + sha256: "6c0673d3d2eaa512958097a38992d6cb5e3d5d0332e321c10ea1074b95a30bc3" + url: "https://pub.dev" + source: hosted + version: "1.5.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + html: + dependency: transitive + description: + name: html + sha256: d9793e10dbe0e6c364f4c59bf3e01fb33a9b2a674bc7a1081693dba0614b6269 + url: "https://pub.dev" + source: hosted + version: "0.15.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: transitive + description: + name: intl + sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + url: "https://pub.dev" + source: hosted + version: "0.17.0" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" + source: hosted + version: "0.12.13" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + multi_select_flutter: + dependency: "direct main" + description: + name: multi_select_flutter + sha256: "503857b415d390d29159df8a9d92d83c6aac17aaf1c307fb7bcfc77d097d20ed" + url: "https://pub.dev" + source: hosted + version: "4.1.3" + parchment: + dependency: transitive + description: + name: parchment + sha256: daf6e25bee9add7fee8addb2e5a78bfeb2714fe8f5079af1df49211416e03584 + url: "https://pub.dev" + source: hosted + version: "1.5.0" + path: + dependency: transitive + description: + name: path + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" + source: hosted + version: "1.8.2" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" + source: hosted + version: "2.1.3" + quill_delta: + dependency: transitive + description: + name: quill_delta + sha256: a91c0e5354b32b5344d2514580dc6f453abe2723da92439019aa728da64d8350 + url: "https://pub.dev" + source: hosted + version: "3.0.0-nullsafety.2" + quiver: + dependency: transitive + description: + name: quiver + sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47 + url: "https://pub.dev" + source: hosted + version: "3.2.1" + responsive_sizer: + dependency: "direct main" + description: + name: responsive_sizer + sha256: "1632edd443b9ace1b88b3eb3c0520cb610cc8dcf3710197f7e3a0c0f38aae508" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" + source: hosted + version: "0.4.16" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: e8f2efc804810c0f2f5b485f49e7942179f56eabcfe81dce3387fec4bb55876b + url: "https://pub.dev" + source: hosted + version: "6.1.9" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1" + url: "https://pub.dev" + source: hosted + version: "6.0.23" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "0a5af0aefdd8cf820dd739886efb1637f1f24489900204f50984634c07a54815" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + url: "https://pub.dev" + source: hosted + version: "2.0.14" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" +sdks: + dart: ">=2.18.5 <3.0.0" + flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..6210485 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,101 @@ +name: denikprogramatora +description: Deník programátora pro soutěž Tour de App + +# 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 is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=2.18.5 <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 + flutter_localizations: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + firebase_core: ^2.4.0 + responsive_sizer: ^3.1.1 + firebase_auth: ^4.2.1 + cloud_firestore: ^4.2.0 + uuid: ^3.0.7 + multi_select_flutter: ^4.1.3 + fleather: ^1.4.0 + url_launcher: ^6.1.8 + +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: ^2.0.0 + +# 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 packages. +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/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..475e54e --- /dev/null +++ b/web/index.html @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + Deník Programátora + + + + + + + + +
+

+ +
+ + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..e4b342c --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "denikprogramatora", + "short_name": "denikprogramatora", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "Deník programátora pro soutěž Tour de App", + "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" + } + ] +}