commit ef57a730c40d5d1372fbcc2ba060e4aa697ad732 Author: Darin Kelkhoff Date: Thu Sep 26 15:51:01 2024 -0500 Initial checkin diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..92e232d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,4 @@ +# Contributing to QQQ Android + +See the [Contributing Guidelines for QQQ](https://github.com/Kingsrook/qqq/blob/dev/CONTRIBUTING.md) + diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..ca9b055 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,619 @@ + 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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..71364b2 --- /dev/null +++ b/README.md @@ -0,0 +1,23 @@ +# QQQ Android + +This project is a set of library modules and sample code for building QQQ +frontend applications for Android. + +## License +QQQ - Low-code Application Framework for Engineers. \ +Copyright (C) 2020-2024. Kingsrook, LLC \ +651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States \ +contact@kingsrook.com | https://github.com/Kingsrook/ + +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 . diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..a6d48a1 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,7 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.android.library) apply false + id("org.jetbrains.kotlin.plugin.serialization") version "1.9.0" apply false +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..20e2a01 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,23 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. For more details, visit +# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.nonTransitiveRClass=true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..faa0a7e --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,55 @@ +[versions] +agp = "8.6.0" +datastorePreferences = "1.1.1" +kotlin = "1.9.0" +coreKtx = "1.13.1" +junit = "4.13.2" +junitVersion = "1.2.1" +espressoCore = "3.6.1" +kotlinxSerializationJson = "1.7.1" +lifecycleRuntimeKtx = "2.8.5" +activityCompose = "1.9.2" +composeBom = "2024.04.01" +appcompat = "1.7.0" +lifecycleViewmodelCompose = "2.6.1" +material = "1.12.0" +runtimeAndroid = "1.7.2" +uiAndroid = "1.6.6" +material3Android = "1.3.0" +junitJupiter = "5.8.1" +auth0 = "3.0.0-beta.0" +uiToolingPreviewAndroid = "1.6.6" + +[libraries] +androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } +androidx-datastore = { module = "androidx.datastore:datastore", version.ref = "datastorePreferences" } +androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" } +androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" } +junit = { group = "junit", name = "junit", version.ref = "junit" } +androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } +androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } +androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } +androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } +androidx-ui = { group = "androidx.compose.ui", name = "ui" } +androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } +androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } +androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } +androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } +androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } +androidx-material3 = { group = "androidx.compose.material3", name = "material3" } +androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } +kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +androidx-runtime-android = { group = "androidx.compose.runtime", name = "runtime-android", version.ref = "runtimeAndroid" } +androidx-ui-android = { group = "androidx.compose.ui", name = "ui-android", version.ref = "uiAndroid" } +androidx-material3-android = { group = "androidx.compose.material3", name = "material3-android", version.ref = "material3Android" } +junit-jupiter = { group = "org.junit.jupiter", name = "junit-jupiter", version.ref = "junitJupiter" } +auth0 = { group = "com.auth0.android", name = "auth0", version.ref = "auth0" } +androidx-ui-tooling-preview-android = { group = "androidx.compose.ui", name = "ui-tooling-preview-android", version.ref = "uiToolingPreviewAndroid" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "agp" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } +android-library = { id = "com.android.library", version.ref = "agp" } + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..c437bfd --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Sep 20 14:25:53 CDT 2024 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# 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 + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/qqq/android/core/.gitignore b/qqq/android/core/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/qqq/android/core/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/qqq/android/core/build.gradle.kts b/qqq/android/core/build.gradle.kts new file mode 100644 index 0000000..92bf002 --- /dev/null +++ b/qqq/android/core/build.gradle.kts @@ -0,0 +1,54 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) + kotlin("plugin.serialization") + id("kotlinx-serialization") +} + +android { + namespace = "com.kingsrook.qqq.frontend.android.core" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + + testImplementation(libs.junit) + testImplementation(libs.junit.jupiter) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + // Kotlin serialization + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1") + + // Retrofit + implementation("com.squareup.retrofit2:retrofit:2.9.0") + + // Retrofit with Kotlin serialization Converter + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0") + implementation("com.squareup.okhttp3:okhttp:4.11.0") +} \ No newline at end of file diff --git a/qqq/android/core/consumer-rules.pro b/qqq/android/core/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/qqq/android/core/proguard-rules.pro b/qqq/android/core/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/qqq/android/core/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/qqq/android/core/src/androidTest/java/com/kingsrook/qqq/frontend/android/core/ExampleInstrumentedTest.kt b/qqq/android/core/src/androidTest/java/com/kingsrook/qqq/frontend/android/core/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..d0844ea --- /dev/null +++ b/qqq/android/core/src/androidTest/java/com/kingsrook/qqq/frontend/android/core/ExampleInstrumentedTest.kt @@ -0,0 +1,47 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest +{ + @Test + fun useAppContext() + { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.kingsrook.qqq.frontend.android.core.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/qqq/android/core/src/main/AndroidManifest.xml b/qqq/android/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/qqq/android/core/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/MockQQQRepository.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/MockQQQRepository.kt new file mode 100644 index 0000000..3062ea7 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/MockQQQRepository.kt @@ -0,0 +1,108 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.controllers + +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.core.model.metadata.QAppMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.QBrandingMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.QInstance +import com.kingsrook.qqq.frontend.android.core.model.metadata.QProcessMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.FullyAnonymousAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionRequest +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionResponse +import java.util.UUID + +/*************************************************************************** + ** + ***************************************************************************/ +class MockQQQRepository : QQQRepository +{ + private var sessionUUID: String? = null; + private var environment: Environment = Environment("Mock Env", "https://mock/"); + + /*************************************************************************** + ** + ***************************************************************************/ + override fun setEnvironment(environment: Environment) + { + this.environment = environment; + } + + /*************************************************************************** + ** + ***************************************************************************/ + override fun setSessionUUID(sessionUUID: String) + { + this.sessionUUID = sessionUUID; + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun getAuthenticationMetaData(): BaseAuthenticationMetaData + { + return (FullyAnonymousAuthenticationMetaData("FULLY_ANONYMOUS")) + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun manageSession(body: ManageSessionRequest): ManageSessionResponse + { + return ManageSessionResponse(UUID.randomUUID().toString()) + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun getMetaData(): QInstance + { + if((sessionUUID ?: "").lowercase().contains("fail")) + { + throw RuntimeException("sessionUUID indicates that we should fail") + } + + var appName = "Mock Application" + if(environment.label == "Test Env 2") + { + appName += " 2" + } + + return QInstance( + branding = QBrandingMetaData(appName = appName), + appTree = listOf( + QAppMetaData(name = "app1", label = "App 1"), + QAppMetaData(name = "app2", label = "App 2") + ), + processes = emptyMap(), + ) + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun getProcessMetaData(processName: String): QProcessMetaData + { + return QProcessMetaData(name = processName, label = processName) + } +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/NetworkQQQRepository.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/NetworkQQQRepository.kt new file mode 100644 index 0000000..afa9685 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/NetworkQQQRepository.kt @@ -0,0 +1,149 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.controllers + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.core.model.metadata.QInstance +import com.kingsrook.qqq.frontend.android.core.model.metadata.QProcessMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionRequest +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionResponse +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Response +import retrofit2.Retrofit +import java.io.IOException + + +/*************************************************************************** + ** + ***************************************************************************/ +class NetworkQQQRepository( + private var baseUrl: String +) : QQQRepository +{ + private val retroJson = Json { ignoreUnknownKeys = true } + + private val qqqApiServiceMap: MutableMap = mutableMapOf() + + /*************************************************************************** + ** + ***************************************************************************/ + override fun setEnvironment(environment: Environment) + { + baseUrl = environment.baseUrl + + if(qqqApiServiceMap[baseUrl] == null) + { + val thisRetrofit = buildRetrofit(baseUrl); + val thisQQQApiService = thisRetrofit.create(QQQApiService::class.java) + qqqApiServiceMap[baseUrl] = thisQQQApiService; + } + + // retrofit = buildRetrofit(); + // retrofit.create(QQQApiService::class.java) + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun buildRetrofit(baseUrl: String): Retrofit + { + return Retrofit.Builder() + .addConverterFactory(retroJson.asConverterFactory("application/json".toMediaType())) + .client( + OkHttpClient.Builder() + .addInterceptor(SessionCookieInterceptor) + .build() + ) + .baseUrl(baseUrl) + .build() + } + + /*************************************************************************** + ** + ***************************************************************************/ + override fun setSessionUUID(sessionUUID: String) + { + SessionCookieInterceptor.sessionUUID = sessionUUID; + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun getAuthenticationMetaData(): BaseAuthenticationMetaData + { + // return qqqApiService.getAuthenticationMetaData() + return qqqApiServiceMap[baseUrl]!!.getAuthenticationMetaData() + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun manageSession(body: ManageSessionRequest): ManageSessionResponse + { + return qqqApiServiceMap[baseUrl]!!.manageSession(body) + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun getMetaData(): QInstance + { + return qqqApiServiceMap[baseUrl]!!.getMetaData() + } + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun getProcessMetaData(processName: String): QProcessMetaData + { + return qqqApiServiceMap[baseUrl]!!.getProcessMetaData(processName).process + } + + /*************************************************************************** + ** + ***************************************************************************/ + companion object SessionCookieInterceptor : Interceptor + { + private var sessionUUID: String? = null; + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response + { + val requestBuilder = chain.request().newBuilder() + + sessionUUID?.let() + { + requestBuilder.addHeader("Cookie", "sessionUUID=${it}") + } + + val response: Response = chain.proceed(requestBuilder.build()) + + return response + } + } + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/QQQApiService.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/QQQApiService.kt new file mode 100644 index 0000000..b1869b1 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/QQQApiService.kt @@ -0,0 +1,76 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.controllers + +import com.kingsrook.qqq.frontend.android.core.model.metadata.QInstance +import com.kingsrook.qqq.frontend.android.core.model.metadata.QProcessMetaDataWrapped +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionRequest +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionResponse +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Path + + +/*************************************************************************** + ** Class (interface?) that instructs retrofit how to make HTTP requests + ** to a QQQ middleware server. + ***************************************************************************/ +interface QQQApiService +{ + ////////////////////////////// + // authentication endpoints // + ////////////////////////////// + @GET("metaData/authentication") + suspend fun getAuthenticationMetaData(): BaseAuthenticationMetaData + + @POST("manageSession") + suspend fun manageSession(@Body body: ManageSessionRequest): ManageSessionResponse + + ////////////////// + // qqq meta dta // + ////////////////// + @GET("metaData") + suspend fun getMetaData(): QInstance + + @GET("/metaData/process/{processName}") + suspend fun getProcessMetaData(@Path(value = "processName") processName: String): QProcessMetaDataWrapped + + /////////////////////////////////////////////////////////////////////////////// + // todo - maybe this is supposed to use some kind of interceptor in okhttp?? // + // but, if this works, just go with it?? // + /////////////////////////////////////////////////////////////////////////////// + /* + object Creddy + { + // Create credentials + val username = "darin+devcoldtrack@coldtrack.com" + val password = "bgz3pnx@KDP1zmx_jdw" + + // create correct Base64 encoded Basic Auth credentials + val credentials = Credentials.basic(username, password) + } + */ + +} + diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/QQQRepository.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/QQQRepository.kt new file mode 100644 index 0000000..e4042b6 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/controllers/QQQRepository.kt @@ -0,0 +1,48 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.controllers + +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.core.model.metadata.QInstance +import com.kingsrook.qqq.frontend.android.core.model.metadata.QProcessMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionRequest +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionResponse + +/*************************************************************************** + ** interface that defines the interaction points we have with a QQQ backend. + ***************************************************************************/ +interface QQQRepository +{ + fun setEnvironment(environment: Environment) + + fun setSessionUUID(sessionUUID: String) + + suspend fun getAuthenticationMetaData(): BaseAuthenticationMetaData + + suspend fun manageSession(body: ManageSessionRequest): ManageSessionResponse + + suspend fun getMetaData(): QInstance + + suspend fun getProcessMetaData(processName: String): QProcessMetaData +} + diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/Environment.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/Environment.kt new file mode 100644 index 0000000..900e7f8 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/Environment.kt @@ -0,0 +1,35 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class Environment( + val label: String, + val baseUrl: String, +) +{ +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/QPossibleValue.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/QPossibleValue.kt new file mode 100644 index 0000000..402a53e --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/QPossibleValue.kt @@ -0,0 +1,36 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model + +import java.io.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@kotlinx.serialization.Serializable +data class QPossibleValue( + val id: Serializable, + val label: String +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/AdornmentType.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/AdornmentType.kt new file mode 100644 index 0000000..096b65b --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/AdornmentType.kt @@ -0,0 +1,36 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +/*************************************************************************** + ** + ***************************************************************************/ +enum class AdornmentType +{ + LINK, + CHIP, + SIZE, + ERROR, + RENDER_HTML, + REVEAL, + CODE_EDITOR, +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/FieldAdornment.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/FieldAdornment.kt new file mode 100644 index 0000000..4114d28 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/FieldAdornment.kt @@ -0,0 +1,36 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class FieldAdornment( + val type: AdornmentType, + val values: Map? +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppMetaData.kt new file mode 100644 index 0000000..5a76501 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppMetaData.kt @@ -0,0 +1,42 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QAppMetaData( + val name: String, + val label: String, + val children: List? = emptyList(), + val childMap: Map? = emptyMap(), + val iconName: String? = null, + val widgets: List? = emptyList(), + val sections: List? = emptyList(), + // todo - not Any... val supplementalAppMetaData: Map = emptyMap() +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppNodeType.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppNodeType.kt new file mode 100644 index 0000000..238801b --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppNodeType.kt @@ -0,0 +1,33 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +/*************************************************************************** + ** + ***************************************************************************/ +enum class QAppNodeType +{ + TABLE, + PROCESS, + REPORT, + APP, +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppSection.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppSection.kt new file mode 100644 index 0000000..a7f35c0 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppSection.kt @@ -0,0 +1,40 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QAppSection( + val name: String, + val label: String, + val icon: QIcon? = null, + val tables: List? = emptyList(), + val processes: List? = emptyList(), + val reports: List? = emptyList(), +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppTreeNode.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppTreeNode.kt new file mode 100644 index 0000000..cd6e646 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QAppTreeNode.kt @@ -0,0 +1,39 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** +** +***************************************************************************/ +@Serializable +data class QAppTreeNode( + val name: String, + val label: String, + val type: QAppNodeType, + val children: List? = emptyList(), + val iconName: String? = null, +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QBrandingMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QBrandingMetaData.kt new file mode 100644 index 0000000..951a2a9 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QBrandingMetaData.kt @@ -0,0 +1,42 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QBrandingMetaData( + val companyName: String? = null, + val companyUrl: String? = null, + val appName: String? = null, + val logo: String? = null, + val icon: String? = null, + val accentColor: String? = null, + val environmentBannerText: String? = null, + val environmentBannerColor: String? = null, +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QComponentType.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QComponentType.kt new file mode 100644 index 0000000..e484415 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QComponentType.kt @@ -0,0 +1,41 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +/*************************************************************************** + ** + ***************************************************************************/ +enum class QComponentType +{ + HELP_TEXT, + BULK_EDIT_FORM, + BULK_LOAD_MAPPING, + VALIDATION_REVIEW_SCREEN, + EDIT_FORM, + VIEW_FORM, + DOWNLOAD_FORM, + RECORD_LIST, + PROCESS_SUMMARY_RESULTS, + GOOGLE_DRIVE_SELECT_FOLDER, + WIDGET, + HTML, +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFieldMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFieldMetaData.kt new file mode 100644 index 0000000..6dc60dd --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFieldMetaData.kt @@ -0,0 +1,48 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import com.kingsrook.qqq.frontend.android.core.model.QPossibleValue +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QFieldMetaData( + val name: String, + val label: String, + val defaultValue: String? = null, + val type: QFieldType, + val isRequired: Boolean = false, + val isEditable: Boolean = false, + val isHeavy: Boolean = false, + val possibleValueSourceName: String? = null, + val inlinePossibleValueSource: List? = null, + val displayFormat: String? = null, + val adornments: List? = null, + val helpContents: List? = null, + // todo - not Any... val behaviors: List?, +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFieldType.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFieldType.kt new file mode 100644 index 0000000..d4417f0 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFieldType.kt @@ -0,0 +1,41 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +/*************************************************************************** + ** + ***************************************************************************/ +enum class QFieldType +{ + BOOLEAN, + STRING, + INTEGER, + LONG, + DECIMAL, + DATE, + TIME, + DATE_TIME, + TEXT, + HTML, + PASSWORD, + BLOB, +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFrontendComponent.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFrontendComponent.kt new file mode 100644 index 0000000..35d6636 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFrontendComponent.kt @@ -0,0 +1,36 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QFrontendComponent( + val type: QComponentType, + val values: Map? = emptyMap() // todo double-check type +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFrontendStepMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFrontendStepMetaData.kt new file mode 100644 index 0000000..bd147c3 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QFrontendStepMetaData.kt @@ -0,0 +1,41 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QFrontendStepMetaData( + val name: String, + val label: String, + val components: List? = null, + val formFields: List? = null, + val viewFields: List? = null, + val recordListFields: List? = null, + val helpContents: List? = null, +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QHelpContent.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QHelpContent.kt new file mode 100644 index 0000000..ef4b493 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QHelpContent.kt @@ -0,0 +1,37 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QHelpContent( + val content: String, + val format: String, + val roles: Set = emptySet() +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QIcon.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QIcon.kt new file mode 100644 index 0000000..d5126e6 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QIcon.kt @@ -0,0 +1,37 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** +** +***************************************************************************/ +@Serializable +data class QIcon( + val name: String? = null, + val path: String? = null, + val color: String? = null, +) +{ + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QInstance.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QInstance.kt new file mode 100644 index 0000000..c891cd1 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QInstance.kt @@ -0,0 +1,42 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** +** +***************************************************************************/ +@Serializable +data class QInstance( + // todo val tables: Map?, // todo + val processes: Map?, + // todo val reports: Map?, // todo + // todo val widgets: Map?, // todo + val apps: Map? = emptyMap(), + val appTree: List = emptyList(), + val branding: QBrandingMetaData? = QBrandingMetaData(), + val environmentValues: Map = emptyMap(), + val helpContent: Map>? = emptyMap(), +) +{ +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QProcessMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QProcessMetaData.kt new file mode 100644 index 0000000..65f90e1 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QProcessMetaData.kt @@ -0,0 +1,39 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QProcessMetaData( + val name: String, + val label: String, + val tableName: String? = null, + val isHidden: Boolean = false, + val frontendSteps: List? = emptyList(), + val hasPermission: Boolean = false, +) +{ +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QProcessMetaDataWrapped.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QProcessMetaDataWrapped.kt new file mode 100644 index 0000000..9934ce4 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/QProcessMetaDataWrapped.kt @@ -0,0 +1,34 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class QProcessMetaDataWrapped( + val process: QProcessMetaData +) +{ +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/Auth0AuthenticationMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/Auth0AuthenticationMetaData.kt new file mode 100644 index 0000000..ee02587 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/Auth0AuthenticationMetaData.kt @@ -0,0 +1,44 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class Auth0AuthenticationMetaData( + override val name: String, + val baseUrl: String, + val clientId: String, + val audience: String, +) : BaseAuthenticationMetaData +{ + override val type: String + get() = TYPE; + + companion object + { + const val TYPE = "AUTH_0" + } +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/AuthenticationSerializer.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/AuthenticationSerializer.kt new file mode 100644 index 0000000..0281a28 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/AuthenticationSerializer.kt @@ -0,0 +1,45 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.SerializationException +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonContentPolymorphicSerializer +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.jsonObject +import kotlinx.serialization.json.jsonPrimitive + +/*************************************************************************** + ** + ***************************************************************************/ +object AuthenticationSerializer : JsonContentPolymorphicSerializer(BaseAuthenticationMetaData::class) +{ + /*************************************************************************** + ** + ***************************************************************************/ + override fun selectDeserializer(element: JsonElement) = when (val type = element.jsonObject["type"]?.jsonPrimitive?.content) + { + Auth0AuthenticationMetaData.TYPE -> Auth0AuthenticationMetaData.serializer() + FullyAnonymousAuthenticationMetaData.TYPE -> FullyAnonymousAuthenticationMetaData.serializer() + else -> throw SerializationException("Unknown type: $type") + } +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/BaseAuthenticationMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/BaseAuthenticationMetaData.kt new file mode 100644 index 0000000..19b8978 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/BaseAuthenticationMetaData.kt @@ -0,0 +1,35 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable(with = AuthenticationSerializer::class) +sealed interface BaseAuthenticationMetaData +{ + val type: String; + val name: String; + +} diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/FullyAnonymousAuthenticationMetaData.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/FullyAnonymousAuthenticationMetaData.kt new file mode 100644 index 0000000..18b6781 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/FullyAnonymousAuthenticationMetaData.kt @@ -0,0 +1,41 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class FullyAnonymousAuthenticationMetaData( + override val name: String +) : BaseAuthenticationMetaData +{ + override val type: String + get() = TYPE; + + companion object + { + const val TYPE = "FULLY_ANONYMOUS" + } +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/ManageSessionRequest.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/ManageSessionRequest.kt new file mode 100644 index 0000000..cd2af65 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/ManageSessionRequest.kt @@ -0,0 +1,35 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.Serializable + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class ManageSessionRequest( + val accessToken: String, + val uuid: String? +) +{ +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/ManageSessionResponse.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/ManageSessionResponse.kt new file mode 100644 index 0000000..1aa5da8 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/ManageSessionResponse.kt @@ -0,0 +1,36 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject + +/*************************************************************************** + ** + ***************************************************************************/ +@Serializable +data class ManageSessionResponse( + val uuid: String, + val values: JsonObject = JsonObject(emptyMap()) +) +{ +} \ No newline at end of file diff --git a/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/utils/StringExtensions.kt b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/utils/StringExtensions.kt new file mode 100644 index 0000000..882fdf4 --- /dev/null +++ b/qqq/android/core/src/main/java/com/kingsrook/qqq/frontend/android/core/utils/StringExtensions.kt @@ -0,0 +1,34 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.utils + +import kotlin.math.min + +/*************************************************************************** + ** string extension function to safely get firstN chars (avoid index out of + ** bounds) + ***************************************************************************/ +fun String.firstN(n: Int): String +{ + val actualN = min(n, this.length) + return this.substring(0, actualN) +} \ No newline at end of file diff --git a/qqq/android/core/src/test/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/AuthenticationSerializerTest.kt b/qqq/android/core/src/test/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/AuthenticationSerializerTest.kt new file mode 100644 index 0000000..2cb65ef --- /dev/null +++ b/qqq/android/core/src/test/java/com/kingsrook/qqq/frontend/android/core/model/metadata/authentication/AuthenticationSerializerTest.kt @@ -0,0 +1,56 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.model.metadata.authentication + +import kotlinx.serialization.json.Json +import org.junit.Test + +import org.junit.Assert.* + +/*************************************************************************** + ** + ***************************************************************************/ +class AuthenticationSerializerTest +{ + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testDecodeAuth0() + { + val json = """ + { + "name":"auth0", + "type":"AUTH_0", + "customizer":{"name":"com.coldtrack.live.utils.ColdTrackLiveAuth0Customizer","codeType":"JAVA"}, + "baseUrl":"https://auth.coldtrack-dev.com/", + "clientId":"Q7hjPeDrfq11EgZyFxpoXQRTPhTn3KzN", + "audience":"https://www.nutrifresh-one-dev.com" + } + """; + + val withUnknownKeys = Json { ignoreUnknownKeys = true } + val parsedItem = withUnknownKeys.decodeFromString(json) + assertTrue(parsedItem is Auth0AuthenticationMetaData) + assertEquals("Q7hjPeDrfq11EgZyFxpoXQRTPhTn3KzN", (parsedItem as Auth0AuthenticationMetaData).clientId); + } +} \ No newline at end of file diff --git a/qqq/android/core/src/test/java/com/kingsrook/qqq/frontend/android/core/utils/StringExtensionsTest.kt b/qqq/android/core/src/test/java/com/kingsrook/qqq/frontend/android/core/utils/StringExtensionsTest.kt new file mode 100644 index 0000000..c9d117c --- /dev/null +++ b/qqq/android/core/src/test/java/com/kingsrook/qqq/frontend/android/core/utils/StringExtensionsTest.kt @@ -0,0 +1,50 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.core.utils + +import org.junit.Assert.assertEquals +import org.junit.Test + +/*************************************************************************** + ** + ***************************************************************************/ +class StringExtensionsTest +{ + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testFirstN() + { + assertEquals("", "".firstN(0)); + assertEquals("", "".firstN(1)); + assertEquals("", "".firstN(100)); + + assertEquals("", "A".firstN(0)); + assertEquals("A", "A".firstN(1)); + assertEquals("A", "A".firstN(100)); + + assertEquals("", "ABC".firstN(0)); + assertEquals("A", "ABC".firstN(1)); + assertEquals("ABC", "ABC".firstN(100)); + } +} \ No newline at end of file diff --git a/qqq/android/mobileapp/.gitignore b/qqq/android/mobileapp/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/qqq/android/mobileapp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/qqq/android/mobileapp/.gradle/8.6/checksums/checksums.lock b/qqq/android/mobileapp/.gradle/8.6/checksums/checksums.lock new file mode 100644 index 0000000..09bd482 Binary files /dev/null and b/qqq/android/mobileapp/.gradle/8.6/checksums/checksums.lock differ diff --git a/qqq/android/mobileapp/.gradle/8.6/fileChanges/last-build.bin b/qqq/android/mobileapp/.gradle/8.6/fileChanges/last-build.bin new file mode 100644 index 0000000..f76dd23 Binary files /dev/null and b/qqq/android/mobileapp/.gradle/8.6/fileChanges/last-build.bin differ diff --git a/qqq/android/mobileapp/.gradle/8.6/fileHashes/fileHashes.lock b/qqq/android/mobileapp/.gradle/8.6/fileHashes/fileHashes.lock new file mode 100644 index 0000000..43edf5c Binary files /dev/null and b/qqq/android/mobileapp/.gradle/8.6/fileHashes/fileHashes.lock differ diff --git a/qqq/android/mobileapp/.gradle/8.6/gc.properties b/qqq/android/mobileapp/.gradle/8.6/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/qqq/android/mobileapp/.gradle/vcs-1/gc.properties b/qqq/android/mobileapp/.gradle/vcs-1/gc.properties new file mode 100644 index 0000000..e69de29 diff --git a/qqq/android/mobileapp/build.gradle.kts b/qqq/android/mobileapp/build.gradle.kts new file mode 100644 index 0000000..1d921cd --- /dev/null +++ b/qqq/android/mobileapp/build.gradle.kts @@ -0,0 +1,69 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +/* +buildFeatures { + compose = true +} +composeOptions { + kotlinCompilerExtensionVersion = rootProject.extra["compose_version"] as String +} + */ + +android { + namespace = "com.kingsrook.qqq.frontend.android.mobileapp" + compileSdk = 34 + + defaultConfig { + minSdk = 24 + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles("consumer-rules.pro") + + manifestPlaceholders["auth0Domain"] = "placeholder.domain" + manifestPlaceholders["auth0Scheme"] = "placeholderscheme" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + kotlinOptions { + jvmTarget = "1.8" + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.appcompat) + implementation(libs.material) + implementation(libs.androidx.runtime.android) + implementation(libs.androidx.ui.android) + implementation(libs.androidx.material3.android) + implementation(libs.androidx.ui.tooling.preview.android) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + + implementation(project(":qqq:android:core")) + implementation(libs.auth0) + + implementation(libs.kotlinx.serialization.json) + implementation(libs.androidx.datastore.preferences) + implementation(libs.androidx.datastore) +} \ No newline at end of file diff --git a/qqq/android/mobileapp/consumer-rules.pro b/qqq/android/mobileapp/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/qqq/android/mobileapp/proguard-rules.pro b/qqq/android/mobileapp/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/qqq/android/mobileapp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/qqq/android/mobileapp/src/androidTest/java/com/kingsrook/qqq/frontend/android/mobileapp/ExampleInstrumentedTest.kt b/qqq/android/mobileapp/src/androidTest/java/com/kingsrook/qqq/frontend/android/mobileapp/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..6f16da8 --- /dev/null +++ b/qqq/android/mobileapp/src/androidTest/java/com/kingsrook/qqq/frontend/android/mobileapp/ExampleInstrumentedTest.kt @@ -0,0 +1,47 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest +{ + @Test + fun useAppContext() + { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.kingsrook.qqq.frontend.android.mobileapp.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/AndroidManifest.xml b/qqq/android/mobileapp/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/qqq/android/mobileapp/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/QMobileApplication.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/QMobileApplication.kt new file mode 100644 index 0000000..dc1a778 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/QMobileApplication.kt @@ -0,0 +1,43 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp + +import android.app.Application +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer +import com.kingsrook.qqq.frontend.android.mobileapp.container.QDefaultAppContainer + +/*************************************************************************** + ** + ***************************************************************************/ +class QMobileApplication : Application() +{ + lateinit var container: QAppContainer + + /*************************************************************************** + ** + ***************************************************************************/ + override fun onCreate() + { + super.onCreate() + container = QDefaultAppContainer() + } +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/container/QAppContainer.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/container/QAppContainer.kt new file mode 100644 index 0000000..dce3698 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/container/QAppContainer.kt @@ -0,0 +1,38 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.container + +import com.kingsrook.qqq.frontend.android.core.controllers.QQQRepository +import com.kingsrook.qqq.frontend.android.core.model.Environment + +/*************************************************************************** + ** + ***************************************************************************/ +interface QAppContainer +{ + val qqqRepository: QQQRepository + + companion object + { + lateinit var availableEnvironments: List + } +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/container/QDefaultAppContainer.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/container/QDefaultAppContainer.kt new file mode 100644 index 0000000..6c6d2fa --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/container/QDefaultAppContainer.kt @@ -0,0 +1,38 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.container + +import com.kingsrook.qqq.frontend.android.core.controllers.NetworkQQQRepository +import com.kingsrook.qqq.frontend.android.core.controllers.QQQRepository +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer.Companion.availableEnvironments + +/*************************************************************************** + ** + ***************************************************************************/ +class QDefaultAppContainer : QAppContainer +{ + override val qqqRepository: QQQRepository by lazy() + { + NetworkQQQRepository(availableEnvironments[0].baseUrl) + } + +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/data/MockPreferencesDataStore.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/data/MockPreferencesDataStore.kt new file mode 100644 index 0000000..a3d919b --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/data/MockPreferencesDataStore.kt @@ -0,0 +1,52 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.data + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.preferencesOf +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/*************************************************************************** + ** + ***************************************************************************/ +class MockPreferencesDataStore : DataStore +{ + var preferences: Preferences = preferencesOf().toMutablePreferences() + + /*************************************************************************** + ** + ***************************************************************************/ + override val data: Flow + get() = flowOf(preferences) + + /*************************************************************************** + ** + ***************************************************************************/ + override suspend fun updateData(transform: suspend (t: Preferences) -> Preferences): Preferences + { + preferences = transform.invoke(preferences) + return preferences + } +} + diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/data/SettingsRepository.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/data/SettingsRepository.kt new file mode 100644 index 0000000..a4e51a8 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/data/SettingsRepository.kt @@ -0,0 +1,107 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.data + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.kingsrook.qqq.frontend.android.core.model.Environment +import kotlinx.coroutines.flow.first +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + + +val SESSION_UUID_KEY = stringPreferencesKey("session_uuid") +val SESSION_USER_FULL_NAME_KEY = stringPreferencesKey("session_user_full_name") + +val ENVIRONMENT_JSON_KEY = stringPreferencesKey("environment_json") + +/*************************************************************************** + ** + ***************************************************************************/ +class SettingsRepository( + private val dataStore: DataStore, +) +{ + + /*************************************************************************** + ** + ***************************************************************************/ + suspend fun getSessionUUID(): String + { + return (dataStore.data.first()[SESSION_UUID_KEY] ?: "") + } + + /*************************************************************************** + ** + ***************************************************************************/ + suspend fun setSessionUUID(sessionUUID: String) + { + dataStore.edit { preferences -> + preferences[SESSION_UUID_KEY] = sessionUUID + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + suspend fun getSessionUserFullName(): String + { + return (dataStore.data.first()[SESSION_USER_FULL_NAME_KEY] ?: "") + } + + /*************************************************************************** + ** + ***************************************************************************/ + suspend fun setSessionUserFullName(value: String) + { + dataStore.edit { preferences -> + preferences[SESSION_USER_FULL_NAME_KEY] = value + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + suspend fun getEnvironment(): Environment? + { + val environmentJsonString = (dataStore.data.first()[ENVIRONMENT_JSON_KEY] ?: "") + if(environmentJsonString == "") + { + return null + } + + return Json.decodeFromString(environmentJsonString) + } + + /*************************************************************************** + ** + ***************************************************************************/ + suspend fun setEnvironment(value: Environment) + { + dataStore.edit { preferences -> + preferences[ENVIRONMENT_JSON_KEY] = Json.encodeToString(value) + } + } + +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Login.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Login.kt new file mode 100644 index 0000000..c70951f --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Login.kt @@ -0,0 +1,172 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication + +import android.util.Log +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.auth0.android.Auth0 +import com.auth0.android.authentication.AuthenticationException +import com.auth0.android.callback.Callback +import com.auth0.android.provider.WebAuthProvider +import com.auth0.android.result.Credentials +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.Auth0AuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.utils.firstN +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullScreenLoading +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullSizedAllCenteredColumn +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.LoadState +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel +import kotlinx.coroutines.delay + +private const val TAG = "QAuth0Form" + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun QAuth0Login(qViewModel: QViewModel, modifier: Modifier = Modifier) +{ + val authenticationMetaData = (qViewModel.authenticationMetaDataLoadState as LoadState.Success).value as Auth0AuthenticationMetaData + val auth0Instance: Auth0 = QAuth0Service.makeAuth0Instance(qViewModel); + + val auth0Success = remember { mutableStateOf(false) } + val auth0Failure = remember { mutableStateOf(false) } + + val callback = object : Callback + { + /*************************************************************************** + ** + ***************************************************************************/ + override fun onFailure(error: AuthenticationException) + { + Log.w(TAG, "Auth0 Called back to failure", error) + error.printStackTrace() + qViewModel.logInFailed() + auth0Failure.value = true; + } + + /*************************************************************************** + ** + ***************************************************************************/ + override fun onSuccess(result: Credentials) + { + Log.d(TAG, "Auth0 Called back success, accessToken: ${result.accessToken.firstN(8)}") + auth0Success.value = true; + + qViewModel.doSetSessionUserFullName(result.user.nickname ?: "Unknown"); + qViewModel.manageSession(result.accessToken) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun tryAgain() + { + ///////////////////////////////////////////////////////////////////////// + // just by resetting these flags, we should re-run the WebAuthProvider // + ///////////////////////////////////////////////////////////////////////// + auth0Failure.value = false + auth0Success.value = false + } + + ////////////////////////////////////////////////////////////////////////////////////////////////////// + // do a little count-down before going to auth0 - probably just for dev, so we can see it happening // + ////////////////////////////////////////////////////////////////////////////////////////////////////// + val countdown = remember { mutableStateOf(0) } + + LaunchedEffect(key1 = countdown.value) + { + if(countdown.value > 0) + { + delay(100) + countdown.value = countdown.value - 1 + } + } + + ////////////////////////////////////////////////////////////////////////////// + // pick what we show based on state - later states at the top of the switch // + ////////////////////////////////////////////////////////////////////////////// + if(qViewModel.isAuthenticated) + { + Log.d(TAG, "User is authenticated - should be seeing apps now") + + FullScreenLoading("Login Successful. You should be taken to your apps now.") + } + else if(auth0Success.value) + { + Log.d(TAG, "Auth0Success is true - looking at manageSessionLoadState now...") + + when (val state = qViewModel.manageSessionLoadState) + { + is LoadState.Loading -> + { + FullScreenLoading("Creating Application Session...") + } + + is LoadState.Error -> Text("Error creating session: ${state.message}") + is LoadState.Success -> + { + FullScreenLoading("Login Successful. You should be taken to your apps now.") + } + } + } + else if(auth0Failure.value) + { + Log.d(TAG, "Auth0Failure is true - Offering try-again button") + + FullSizedAllCenteredColumn() + { + Text("Login failed... ", modifier = Modifier.padding(bottom = 16.dp)) + Button(onClick = { tryAgain() }) + { + Text("Try again") + } + } + } + else if(countdown.value > 0) + { + Log.d(TAG, "Sleeping before Auth0 login") + FullScreenLoading("Taking you to login in ${countdown.value} moment${if(countdown.value == 1) "" else "s"}...") + } + else + { + FullScreenLoading("Logging in...") + + Log.d(TAG, "Going to Auth0 login (WebAuthProvider)") + WebAuthProvider + .login(auth0Instance) + .withAudience(authenticationMetaData.audience) + .withScheme(QAuth0Service.scheme) + .start(LocalContext.current, callback) + } + +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Logout.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Logout.kt new file mode 100644 index 0000000..e7b13d7 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Logout.kt @@ -0,0 +1,106 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication + +import android.util.Log +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import com.auth0.android.authentication.AuthenticationException +import com.auth0.android.callback.Callback +import com.auth0.android.provider.WebAuthProvider +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullScreenLoading +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullSizedAllCenteredColumn +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel +import kotlinx.coroutines.delay + +private const val TAG = "QAuth0Logout" + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun QAuth0Logout(qViewModel: QViewModel, modifier: Modifier = Modifier) +{ + val auth0Instance = QAuth0Service.makeAuth0Instance(qViewModel); + + val auth0Success = remember { mutableStateOf(false) } + + val callback = object : Callback + { + /*************************************************************************** + ** + ***************************************************************************/ + override fun onFailure(error: AuthenticationException) + { + Log.w(TAG, "Auth0 Logout Called back to failure", error) + } + + /*************************************************************************** + ** + ***************************************************************************/ + override fun onSuccess(result: Void?) + { + Log.d(TAG, "Auth0 Logout Called back success") + auth0Success.value = true; + qViewModel.logOut() + } + } + + ////////////////////////////////////////////////////////////////////////////// + // pick what we show based on state - later states at the top of the switch // + ////////////////////////////////////////////////////////////////////////////// + if(!qViewModel.isAuthenticated) + { + Log.d(TAG, "User is not authenticated, so, idk, shouldn't be in here.") + + FullSizedAllCenteredColumn() + { + Text("Logout Successful.", modifier = Modifier.padding(bottom = 16.dp)) + Button(onClick = { qViewModel.requestLogin() }) + { + Text("Log back in") + } + } + } + else if(auth0Success.value) + { + Log.d(TAG, "Auth0Success is true - where do we go now?") + FullScreenLoading("Completing Logout...") + } + else + { + Log.d(TAG, "Going to Auth0 logout (WebAuthProvider)") + + FullScreenLoading("Logging out...") + WebAuthProvider.logout(auth0Instance) + .withScheme(QAuth0Service.scheme) + .start(LocalContext.current, callback) + } + +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Service.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Service.kt new file mode 100644 index 0000000..5449847 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuth0Service.kt @@ -0,0 +1,69 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication + +import android.util.Log +import com.auth0.android.Auth0 +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.Auth0AuthenticationMetaData +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.LoadState +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + + +/*************************************************************************** + ** + ***************************************************************************/ +object QAuth0Service +{ + lateinit var scheme: String; + + private const val TAG = "QAuth0Service"; + + /*************************************************************************** + ** + ***************************************************************************/ + fun makeAuth0Instance(qViewModel: QViewModel): Auth0 + { + if(!::scheme.isInitialized) + { + throw (IllegalStateException("QAuth0Service.scheme was not initialized!")) + } + + when (val loadState = qViewModel.authenticationMetaDataLoadState) + { + is LoadState.Success -> + { + val authenticationMetaData = loadState.value as Auth0AuthenticationMetaData; + val auth0Instance: Auth0 = Auth0.getInstance(authenticationMetaData.clientId, authenticationMetaData.baseUrl) + Log.d(TAG, "clientId: ${auth0Instance.clientId}, domain: ${auth0Instance.domain}") + return auth0Instance; + } + + else -> + { + throw (IllegalStateException("authenticationMetaData is not loaded.")) + } + } + + } + + +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuthenticationWrapper.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuthenticationWrapper.kt new file mode 100644 index 0000000..9a91c35 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QAuthenticationWrapper.kt @@ -0,0 +1,71 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe.QApplicationWrapper +import com.kingsrook.qqq.frontend.android.mobileapp.ui.utils.LoadStateView +import com.kingsrook.qqq.frontend.android.mobileapp.ui.utils.Reloader +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.LoadState +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + +/*************************************************************************** + ** Composable that wraps our entire application - providing a login form + ** when needed, or a logout screen, else showing the app. + ***************************************************************************/ +@Composable +fun QAuthenticationWrapper(qViewModel: QViewModel, modifier: Modifier = Modifier) +{ + val isAuthenticated = qViewModel.isAuthenticated; + val authenticationMetaDataLoadState = qViewModel.authenticationMetaDataLoadState; + val initialLoadFromDataStoreLoadState = qViewModel.initialLoadFromDataStoreLoadState; + val loadStates = listOf(initialLoadFromDataStoreLoadState, authenticationMetaDataLoadState); + + Column() + { + LoadStateView( + loadStates, + modifier, + reloader = Reloader("Try again", { qViewModel.reInitAfterFailure() }), + loadingText = "Authenticating app environment: ${qViewModel.environment.label}" + ) + { + val authenticationMetaData = (authenticationMetaDataLoadState as LoadState.Success).value + + if(qViewModel.isLogoutRequested) + { + QLogoutForm(qViewModel, authenticationMetaData) + } + else if(isAuthenticated) + { + QApplicationWrapper(qViewModel, Modifier) + } + else + { + QLoginForm(qViewModel, authenticationMetaData) + } + } + } +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QLoginForm.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QLoginForm.kt new file mode 100644 index 0000000..5317c03 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QLoginForm.kt @@ -0,0 +1,71 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.Auth0AuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.FullyAnonymousAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullSizedAllCenteredColumn +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel +import java.util.UUID + + +/*************************************************************************** + ** Display an appropriate login form, based on the authenticationMetaData + ** type. + ***************************************************************************/ +@Composable +fun QLoginForm(qViewModel: QViewModel, authenticationMetaData: BaseAuthenticationMetaData, modifier: Modifier = Modifier) +{ + when (authenticationMetaData) + { + is Auth0AuthenticationMetaData -> + { + QAuth0Login(qViewModel, modifier) + } + + is FullyAnonymousAuthenticationMetaData -> + { + FullSizedAllCenteredColumn() + { + Text("Click to log in Anonymously:", modifier = Modifier.padding(bottom = 16.dp)) + + Button(onClick = { + qViewModel.doSetSessionUserFullName("Anonymous"); + qViewModel.manageSession(UUID.randomUUID().toString()) + }) { Text("Log In") } + } + } + + else -> + { + Text("Error: Unrecognized AuthenticationMetaData type: ${authenticationMetaData.javaClass.simpleName}", color = Color.Red) + } + } +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QLogoutForm.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QLogoutForm.kt new file mode 100644 index 0000000..180a432 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/authentication/QLogoutForm.kt @@ -0,0 +1,79 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.Auth0AuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.FullyAnonymousAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullSizedAllCenteredColumn +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + + +/*************************************************************************** + ** Display an appropriate logout screen/form, based on the authenticationMetaData + ** type. + ***************************************************************************/ +@Composable +fun QLogoutForm(qViewModel: QViewModel, authenticationMetaData: BaseAuthenticationMetaData, modifier: Modifier = Modifier) +{ + when (authenticationMetaData) + { + is Auth0AuthenticationMetaData -> + { + QAuth0Logout(qViewModel, modifier) + } + + is FullyAnonymousAuthenticationMetaData -> + { + LaunchedEffect(true) + { + /////////////////////////////////////////////////////////////////// + // only run the logout code the first time this code is rendered // + /////////////////////////////////////////////////////////////////// + qViewModel.logOut(); + } + + FullSizedAllCenteredColumn() + { + Text("Logout Successful.", modifier = Modifier.padding(bottom = 16.dp)) + + Button(onClick = { qViewModel.requestLogin() }) + { + Text("Log back in") + } + } + } + + else -> + { + Text("Error: Unrecognized AuthenticationMetaData type: ${authenticationMetaData.javaClass.simpleName}", color = Color.Red) + } + } +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/common/FullScreenLoading.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/common/FullScreenLoading.kt new file mode 100644 index 0000000..90b496b --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/common/FullScreenLoading.kt @@ -0,0 +1,50 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.common + +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun FullScreenLoading(loadingText: String, modifier: Modifier = Modifier) +{ + FullSizedAllCenteredColumn(modifier) + { + Text(loadingText, color = Color.Gray, modifier = Modifier.padding(bottom = 16.dp)) + + CircularProgressIndicator( + modifier = Modifier.width(64.dp), + color = MaterialTheme.colorScheme.secondary, + trackColor = MaterialTheme.colorScheme.surfaceVariant, + ) + } +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/common/FullSizedAllCenteredColumn.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/common/FullSizedAllCenteredColumn.kt new file mode 100644 index 0000000..5179b17 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/common/FullSizedAllCenteredColumn.kt @@ -0,0 +1,43 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.common + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun FullSizedAllCenteredColumn(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) +{ + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + modifier = modifier.fillMaxSize(), + content = content) +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/EnvironmentDialog.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/EnvironmentDialog.kt new file mode 100644 index 0000000..40c2a1f --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/EnvironmentDialog.kt @@ -0,0 +1,155 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe + +import android.util.Log +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.KeyboardArrowDown +import androidx.compose.material.icons.filled.KeyboardArrowUp +import androidx.compose.material.icons.filled.Place +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.unit.dp +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + +const val TAG = "EnvironmentDialog" + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun EnvironmentDialog(qViewModel: QViewModel, isEnvironmentDialogOpen: MutableState) +{ + val dropdownExpanded = remember { mutableStateOf(false) } + val textFieldSize = remember { mutableStateOf(IntSize.Zero) } + + val options = QAppContainer.availableEnvironments; + val selectedValue = remember { mutableStateOf(qViewModel.environment) } + val isSwitching = remember { mutableStateOf(false) } + + val icon = if(dropdownExpanded.value) + Icons.Filled.KeyboardArrowUp + else + Icons.Filled.KeyboardArrowDown + + AlertDialog( + icon = { Icon(imageVector = Icons.Default.Place, contentDescription = "App Environment") }, + title = { Text("App Environment") }, + text = { + + Column() + { + Box( + Modifier + .fillMaxWidth() + .clickable { dropdownExpanded.value = !dropdownExpanded.value } + ) { + Row( + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier + .fillMaxWidth() + .border(width = 1.dp, color = Color.Black, shape = RoundedCornerShape(4.dp)) + .padding(16.dp) + .onGloballyPositioned { coordinates -> textFieldSize.value = coordinates.size }, + ) + { + Text(text = selectedValue.value.label) + Icon(icon, if(dropdownExpanded.value) "close" else "open") + } + + DropdownMenu( + expanded = dropdownExpanded.value, + onDismissRequest = { dropdownExpanded.value = false }, + Modifier.width((textFieldSize.value.width.toFloat() / LocalDensity.current.density).dp) + ) + { + options.forEach() + { environment -> + DropdownMenuItem( + onClick = + { + selectedValue.value = environment + dropdownExpanded.value = false + }, + text = { Text(text = environment.label) } + ) + } + } + } + } + + }, + onDismissRequest = { + if(isSwitching.value) + { + Log.d(TAG, "Request to dismiss, but we are currently switching, so noop") + return@AlertDialog; + } + + isEnvironmentDialogOpen.value = false + }, + dismissButton = { TextButton(onClick = { isEnvironmentDialogOpen.value = false }) { Text("Dismiss") } }, + confirmButton = { + TextButton( + onClick = { + if(selectedValue.value != qViewModel.environment) + { + isSwitching.value = true + qViewModel.switchEnvironment(selectedValue.value) + { + isEnvironmentDialogOpen.value = false + }; + } + else + { + isEnvironmentDialogOpen.value = false + } + }, + enabled = (selectedValue.value != qViewModel.environment && !isSwitching.value) + ) { Text(if(isSwitching.value) "Switching..." else "Switch") } + } + ) +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QApplicationWrapper.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QApplicationWrapper.kt new file mode 100644 index 0000000..80de553 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QApplicationWrapper.kt @@ -0,0 +1,86 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe + +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kingsrook.qqq.frontend.android.core.model.metadata.QInstance +import com.kingsrook.qqq.frontend.android.mobileapp.ui.utils.LoadStateView +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.LoadState +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + +/*************************************************************************** + ** Composable that wraps our entire application - providing a login form + ** when needed, else showing the app. + ***************************************************************************/ +@Composable +fun QApplicationWrapper(qViewModel: QViewModel, modifier: Modifier = Modifier) +{ + val qInstanceLoadState = qViewModel.qInstanceLoadState; + val loadStates = listOf(qInstanceLoadState); + + LoadStateView(loadStates, + modifier, + loadingText = "Loading apps for environment: ${qViewModel.environment.label}") + { + val qInstance = (qInstanceLoadState as LoadState.Success).value + Column(modifier = Modifier) + { + Text( + "${qInstance.branding?.appName}", + fontSize = 24.sp, + fontWeight = FontWeight.SemiBold, + modifier = Modifier.padding(bottom = 8.dp).testTag("qInstance.branding.appName") + ) + + // Column(modifier = modifier, content = content) + + LazyColumn() + { + items(qInstance.appTree) { app -> + Text( + app.label, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .border(1.dp, Color.Black, RoundedCornerShape(8.dp)) + .padding(24.dp) + ) + } + } + } + } +} + diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QNavigationDrawer.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QNavigationDrawer.kt new file mode 100644 index 0000000..e1c9050 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QNavigationDrawer.kt @@ -0,0 +1,107 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.ModalDrawerSheet +import androidx.compose.material3.ModalNavigationDrawer +import androidx.compose.material3.NavigationDrawerItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.TopLevelAppState + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun QNavigationDrawer(topLevelAppState: TopLevelAppState, content: @Composable ColumnScope.() -> Unit) +{ + ModalNavigationDrawer( + drawerState = topLevelAppState.navDrawerState, + drawerContent = + { + ModalDrawerSheet(modifier = Modifier.padding(top = 24.dp)) + { + Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) + { + Text("QNav Drawer title", fontWeight = FontWeight.Bold, modifier = Modifier.padding(16.dp)) + + IconButton(onClick = topLevelAppState::closeNavDrawer) + { + Icon(imageVector = Icons.Filled.Close, contentDescription = "Close Menu") + } + } + HorizontalDivider() + + if(topLevelAppState.isLoggedIn()) + { + NavigationDrawerItem( + label = + { + Text(text = "Log Out") + }, + icon = { Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "Log Out") }, + selected = false, + onClick = + { + topLevelAppState.logOut() + topLevelAppState.closeNavDrawer() + } + ) + } + else + { + Row( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth().padding(16.dp) + ) + { + Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "Log Out", tint = Color.Gray) + Text("Log Out", color = Color.Gray) + } + } + + // ...other drawer items + } + } + ) { + Column(modifier = Modifier, content = content) + } + +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QTopAppBar.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QTopAppBar.kt new file mode 100644 index 0000000..73fec9b --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/QTopAppBar.kt @@ -0,0 +1,96 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Place +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.style.TextOverflow +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + +/*************************************************************************** + ** + ***************************************************************************/ +@OptIn(ExperimentalMaterial3Api::class) // needed for TopAppBar +@Composable +fun QTopAppBar(qViewModel: QViewModel, navMenuCallback: (() -> Unit)? = null) +{ + val title = "QQWhat?"; + + val isUserDialogOpen = remember { mutableStateOf(false) } + val isEnvironmentDialogOpen = remember { mutableStateOf(false) } + + TopAppBar( + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + titleContentColor = MaterialTheme.colorScheme.primary, + ), + title = { Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis) }, + navigationIcon = + { + navMenuCallback?.let() + { + IconButton(onClick = { it() }) + { + Icon(imageVector = Icons.Filled.Menu, contentDescription = "Open Menu") + } + } + }, + actions = + { + IconButton(onClick = { isUserDialogOpen.value = true }, Modifier.testTag("topBar.userIcon")) + { + Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "User Information") + } + + IconButton(onClick = { isEnvironmentDialogOpen.value = true }, Modifier.testTag("topBar.environmentIcon")) + { + Icon(imageVector = Icons.Default.Place, contentDescription = "App Environment") + } + } + ) + + if(isUserDialogOpen.value) + { + UserDialog(qViewModel, isUserDialogOpen) + } + + if(isEnvironmentDialogOpen.value) + { + EnvironmentDialog(qViewModel, isEnvironmentDialogOpen) + } + +} + diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/UserDialog.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/UserDialog.kt new file mode 100644 index 0000000..39da5f3 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/horseshoe/UserDialog.kt @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.AccountCircle +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun UserDialog(qViewModel: QViewModel, isUserDialogOpen: MutableState) +{ + AlertDialog( + icon = { Icon(imageVector = Icons.Default.AccountCircle, contentDescription = "User Information") }, + title = { Text("User Information") }, + text = { + Text("Current User: ${qViewModel.sessionUserFullName}") + }, + onDismissRequest = { isUserDialogOpen.value = false }, + dismissButton = { TextButton(onClick = { isUserDialogOpen.value = false }) { Text("Dismiss") } }, + confirmButton = { + TextButton(onClick = { + qViewModel.requestLogout(); + isUserDialogOpen.value = false + }) { Text("Log Out") } + } + ) +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/utils/LoadStateView.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/utils/LoadStateView.kt new file mode 100644 index 0000000..7920b42 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/utils/LoadStateView.kt @@ -0,0 +1,97 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.utils + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.kingsrook.qqq.frontend.android.mobileapp.ui.common.FullScreenLoading +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.LoadState + +/*************************************************************************** + ** Composable that + ***************************************************************************/ +@Composable +fun LoadStateView( + loadStates: List>, + modifier: Modifier = Modifier, + reloader: Reloader? = null, + loadingText: String = "Loading...", + content: @Composable ColumnScope.() -> Unit +) +{ + + if(LoadState.areAnyErrors(loadStates)) + { + /////////////////////////////////////////////////////// + // show errors, with reload button, if one was given // + /////////////////////////////////////////////////////// + LazyColumn(Modifier.padding(8.dp)) + { + items(LoadState.getErrorMessages(loadStates)) + { + Text("Error: ${it}", color = Color.Red) + } + + if(reloader != null) + { + item() + { + Row(horizontalArrangement = Arrangement.Center, modifier = Modifier.fillMaxWidth()) + { + Button(onClick = reloader.callback) + { + Text(reloader.buttonLabel) + } + } + } + } + } + + } + else if(LoadState.areAnyLoading(loadStates)) + { + ////////////////// + // show loading // + ////////////////// + FullScreenLoading(loadingText) + } + else + { + /////////////////////// + // show the content! // + /////////////////////// + Column(modifier = modifier, content = content) + } +} + diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/utils/Reloader.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/utils/Reloader.kt new file mode 100644 index 0000000..072c489 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/ui/utils/Reloader.kt @@ -0,0 +1,30 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.ui.utils + +/*************************************************************************** + ** + ***************************************************************************/ +data class Reloader( + val buttonLabel: String, + val callback: () -> Unit +) \ No newline at end of file diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/LoadState.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/LoadState.kt new file mode 100644 index 0000000..3de8027 --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/LoadState.kt @@ -0,0 +1,88 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.viewmodel + +/*************************************************************************** + ** + ***************************************************************************/ +sealed interface LoadState +{ + data class Success(val value: T) : LoadState + data class Error(val message: String) : LoadState + data class Loading(val unit: Unit) : LoadState + { + val start = System.currentTimeMillis() + } + + companion object + { + /*************************************************************************** + ** + ***************************************************************************/ + fun areAnyErrors(loadStates: List>): Boolean + { + for(loadState in loadStates) + { + if(loadState is Error) + { + return (true); + } + } + + return (false); + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun getErrorMessages(loadStates: List>): List + { + val rs: MutableList = mutableListOf(); + + for(loadState in loadStates) + { + if(loadState is Error) + { + rs.add(loadState.message) + } + } + + return (rs) + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun areAnyLoading(loadStates: List>): Boolean + { + for(loadState in loadStates) + { + if(loadState is Loading) + { + return (true); + } + } + + return (false); + } + } +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/QViewModel.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/QViewModel.kt new file mode 100644 index 0000000..264bddd --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/QViewModel.kt @@ -0,0 +1,603 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.viewmodel + +import android.util.Log +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.initializer +import androidx.lifecycle.viewmodel.viewModelFactory +import com.kingsrook.qqq.frontend.android.core.controllers.QQQRepository +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.core.model.metadata.QInstance +import com.kingsrook.qqq.frontend.android.core.model.metadata.QProcessMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.BaseAuthenticationMetaData +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionRequest +import com.kingsrook.qqq.frontend.android.core.model.metadata.authentication.ManageSessionResponse +import com.kingsrook.qqq.frontend.android.core.utils.firstN +import com.kingsrook.qqq.frontend.android.mobileapp.QMobileApplication +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer +import com.kingsrook.qqq.frontend.android.mobileapp.data.SettingsRepository +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch + + +private const val TAG = "QViewModel" + +/*************************************************************************** + ** + ***************************************************************************/ +open class QViewModel( + private val qqqRepository: QQQRepository, + private val dataStore: DataStore +) : ViewModel() +{ + private var settingsRepository: SettingsRepository = SettingsRepository(dataStore) + + var initialLoadFromDataStoreLoadState: LoadState by mutableStateOf(LoadState.Loading(Unit)) + private set + + var isAuthenticated: Boolean by mutableStateOf(false) + private set + + var isLogoutRequested: Boolean by mutableStateOf(false) + private set + + var sessionUUID: String by mutableStateOf("") + private set + + private var storedSessionUUIDLoadState: LoadState by mutableStateOf(LoadState.Loading(Unit)) + private set + + var sessionUserFullName: String by mutableStateOf("") + + private var storedSessionUserFullName: LoadState by mutableStateOf(LoadState.Loading(Unit)) + + private var storedEnvironmentLoadState: LoadState by mutableStateOf(LoadState.Loading(Unit)) + + private var defaultEnvironment = QAppContainer.availableEnvironments[0]; + + var environment: Environment by mutableStateOf(defaultEnvironment) + private set + + var authenticationMetaDataLoadState: LoadState by mutableStateOf(LoadState.Loading(Unit)) + private set + + var manageSessionLoadState: LoadState by mutableStateOf(LoadState.Loading(Unit)) + private set + + var qInstanceLoadState: LoadState by mutableStateOf(LoadState.Loading(Unit)) + private set + + var qProcessMetaDataLoadState: Map> by mutableStateOf(emptyMap()) + private set + + init + { + viewModelScope.launch() + { + doInitialLoadFromDataStoreV2().join() + loadAuthenticationMetaData().join() + tryLoginFromDataStore().join() + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun reInitAfterFailure() + { + clearAll() + loadAuthenticationMetaData() + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun clearAll() + { + //////////////////////////////////////////// + // reset all the things to initial values // + //////////////////////////////////////////// + // storedSessionDance = LoadState.Loading(Unit) // well, not this one - it's only for our very first load. + isAuthenticated = false + isLogoutRequested = false + sessionUUID = "" + storedSessionUUIDLoadState = LoadState.Loading(Unit) + sessionUserFullName = "" + storedSessionUserFullName = LoadState.Loading(Unit) + storedEnvironmentLoadState = LoadState.Loading(Unit) + authenticationMetaDataLoadState = LoadState.Loading(Unit) + manageSessionLoadState = LoadState.Loading(Unit) + qInstanceLoadState = LoadState.Loading(Unit) + qProcessMetaDataLoadState = emptyMap() + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun loadAuthenticationMetaData(): Job + { + return viewModelScope.launch() + { + authenticationMetaDataLoadState = try + { + LoadState.Success(qqqRepository.getAuthenticationMetaData()) + } + catch(e: Exception) + { + Log.w(TAG, "Error loading Authentication MetaData", e) + LoadState.Error(e.message ?: "Error loading Authentication MetaData") + } + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + open fun manageSession(accessToken: String) + { + val outerThis = this + viewModelScope.launch() + { + try + { + Log.d(TAG, "managing session with an access token from login flow: ${accessToken.firstN(8)}") + + val manageSessionLoadState = LoadState.Success(qqqRepository.manageSession(ManageSessionRequest(accessToken = accessToken, uuid = null))) + sessionUUID = manageSessionLoadState.value.uuid + qqqRepository.setSessionUUID(sessionUUID) + loadQInstance() + logInSuccessful() + + outerThis.manageSessionLoadState = manageSessionLoadState + } + catch(e: Exception) + { + Log.w(TAG, "Error managing session", e) + outerThis.manageSessionLoadState = LoadState.Error(e.message ?: "Error managing session") + } + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun logInFailed() + { + isAuthenticated = false; + + /////////////////////////////////////////////////// + // make sure we don't have a sessionUUID stored. // + /////////////////////////////////////////////////// + storeSessionUUID("") + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun logInSuccessful() + { + isAuthenticated = true; + storeSessionUUID(sessionUUID) + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun requestLogout() + { + isLogoutRequested = true + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun requestLogin() + { + isLogoutRequested = false + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun doInitialLoadFromDataStore() + { + viewModelScope.launch() + { + //////////////////////////////////////// + // load the session UUID from storage // + //////////////////////////////////////// + Log.d(TAG, "starting initialLoadFromDataStore; waiting for loadStoredSessionUUID()") + + viewModelScope.launch() + { + loadStoredSessionUUID() + loadStoredSessionUserFullName() + }.join() + + when (storedSessionUUIDLoadState) + { + is LoadState.Success -> + { + Log.d(TAG, "loadStoredSessionUUID was success") + sessionUUID = (storedSessionUUIDLoadState as LoadState.Success).value + + if(sessionUUID == "") + { + Log.d(TAG, "loadStoredSessionUUID was empty - no more dance") + } + else + { + Log.d(TAG, "loadStoredSessionUUID was set - trying to load instance now") + qqqRepository.setSessionUUID(sessionUUID); + loadQInstance().join() + + when (qInstanceLoadState) + { + is LoadState.Success -> + { + loadStoredSessionUserFullName().join() + + sessionUserFullName = when (val it = storedSessionUserFullName) + { + is LoadState.Success -> it.value + else -> "Unknown" + } + + Log.d(TAG, "qInstanceLoadState was success (${qInstanceLoadState}). marking login success!") + logInSuccessful() + } + + else -> + { + qInstanceLoadState = LoadState.Loading(Unit) + Log.d(TAG, "qInstanceLoadState was not success (${qInstanceLoadState}). marking login failed.") + logInFailed() + } + } + } + } + + else -> + { + /////////// + // noop. // + /////////// + Log.d(TAG, "loadStoredSessionUUID was not success (${storedSessionUUIDLoadState}). ending dance") + } + } + + initialLoadFromDataStoreLoadState = LoadState.Success(Unit) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun doInitialLoadFromDataStoreV2(): Job + { + return viewModelScope.launch() + { + //////////////////////////////////////// + // load the session UUID from storage // + //////////////////////////////////////// + Log.d(TAG, "starting initialLoadFromDataStore;") + + val job1 = loadStoredSessionUUID(); + val job2 = loadStoredSessionUserFullName(); + val job3 = loadStoredEnvironmentOrDefault(); + + job1.join() + job2.join() + job3.join() + + Log.d(TAG, "past initialLoadFromDataStore;") + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun tryLoginFromDataStore(): Job + { + return viewModelScope.launch() + { + when (storedSessionUUIDLoadState) + { + is LoadState.Success -> + { + Log.d(TAG, "loadStoredSessionUUID was success") + sessionUUID = (storedSessionUUIDLoadState as LoadState.Success).value + + if(sessionUUID == "") + { + Log.d(TAG, "loadStoredSessionUUID was empty - no more dance") + logInFailed() + } + else + { + Log.d(TAG, "loadStoredSessionUUID was set - trying to load instance now") + qqqRepository.setSessionUUID(sessionUUID); + loadQInstance().join() + + when (qInstanceLoadState) + { + is LoadState.Success -> + { + loadStoredSessionUserFullName().join() + + sessionUserFullName = when (val it = storedSessionUserFullName) + { + is LoadState.Success -> it.value + else -> "Unknown" + } + + Log.d(TAG, "qInstanceLoadState was success (${qInstanceLoadState}). marking login success!") + logInSuccessful() + } + + else -> + { + qInstanceLoadState = LoadState.Loading(Unit) + Log.d(TAG, "qInstanceLoadState was not success (${qInstanceLoadState}). marking login failed.") + logInFailed() + } + } + } + } + + else -> + { + /////////// + // noop. // + /////////// + Log.d(TAG, "loadStoredSessionUUID was not success (${storedSessionUUIDLoadState}). ending dance") + } + } + + initialLoadFromDataStoreLoadState = LoadState.Success(Unit) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun loadStoredSessionUUID(): Job + { + return viewModelScope.launch() + { + storedSessionUUIDLoadState = try + { + LoadState.Success(settingsRepository.getSessionUUID()) + } + catch(e: Exception) + { + Log.w(TAG, "Error loading stored session UUID", e) + LoadState.Error(e.message ?: "Error loading stored session UUID") + } + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun loadStoredEnvironmentOrDefault(): Job + { + return viewModelScope.launch() + { + try + { + var env: Environment? = settingsRepository.getEnvironment() + + if(env == null) + { + Log.d(TAG, "got null environment from settings repo. Using default.") + env = defaultEnvironment + } + else + { + Log.d(TAG, "got environment ${env} from settings repo.") + } + + activateEnvironment(env).join() + } + catch(e: Exception) + { + Log.w(TAG, "Error loading stored environment", e) + storedEnvironmentLoadState = LoadState.Error(e.message ?: "Error loading stored environment") + } + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun activateEnvironment(environment: Environment): Job + { + return viewModelScope.launch() + { + this@QViewModel.environment = environment + qqqRepository.setEnvironment(environment) + settingsRepository.setEnvironment(environment) + storedEnvironmentLoadState = LoadState.Success(environment) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun switchEnvironment(environment: Environment, postSwitchCallback: () -> Unit) + { + viewModelScope.launch() + { + activateEnvironment(environment).join() + logOut().join() + loadAuthenticationMetaData().join() + postSwitchCallback.invoke() + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun loadStoredSessionUserFullName(): Job + { + return viewModelScope.launch() + { + storedSessionUserFullName = try + { + LoadState.Success(settingsRepository.getSessionUserFullName()) + } + catch(e: Exception) + { + Log.w(TAG, "Error loading stored session user full name", e) + LoadState.Error(e.message ?: "Error loading stored session user full name") + } + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun storeSessionUUID(value: String) + { + viewModelScope.launch() + { + Log.d(TAG, "Storing session UUID: [${value}]") + settingsRepository.setSessionUUID(value) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun doSetSessionUserFullName(value: String) + { + sessionUserFullName = value; + viewModelScope.launch() + { + settingsRepository.setSessionUserFullName(value) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun logOut(): Job + { + return viewModelScope.launch() + { + val capturedIsLogoutRequested = isLogoutRequested + val capturedAuthLoadState = authenticationMetaDataLoadState + + viewModelScope.launch() + { + isAuthenticated = false + sessionUUID = "" + qqqRepository.setSessionUUID(sessionUUID) + settingsRepository.setSessionUUID(sessionUUID) + }.join() + + clearAll(); + + //////////////////////////////////////// + // restore these 2 after the clear... // + //////////////////////////////////////// + isLogoutRequested = capturedIsLogoutRequested + authenticationMetaDataLoadState = capturedAuthLoadState + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun loadQInstance(): Job + { + return viewModelScope.launch() + { + qInstanceLoadState = try + { + val value = qqqRepository.getMetaData() + LoadState.Success(value) + } + catch(e: Exception) + { + Log.w(TAG, "Error loading QInstance", e) + LoadState.Error(e.message ?: "Error loading QInstance") + } + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun loadQProcessMetaData(processName: String) + { + if(qProcessMetaDataLoadState.containsKey(processName)) + { + // todo - try again if failed, or if old?? + return; + } + + qProcessMetaDataLoadState = qProcessMetaDataLoadState.plus(Pair(processName, LoadState.Loading(Unit))) + + viewModelScope.launch() + { + val processLoadState = try + { + LoadState.Success(qqqRepository.getProcessMetaData(processName)) + } + catch(e: Exception) + { + Log.w(TAG, "Error loading Process [${processName}]", e) + LoadState.Error(e.message ?: "Error loading Process") + } + + qProcessMetaDataLoadState = qProcessMetaDataLoadState.plus(Pair(processName, processLoadState)) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + companion object + { + lateinit var dataStore: DataStore + + val factory: ViewModelProvider.Factory = viewModelFactory() + { + initializer() + { + val application = (this[APPLICATION_KEY] as QMobileApplication) + QViewModel(qqqRepository = application.container.qqqRepository, dataStore = dataStore) + } + } + } +} diff --git a/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/TopLevelAppState.kt b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/TopLevelAppState.kt new file mode 100644 index 0000000..8e220bb --- /dev/null +++ b/qqq/android/mobileapp/src/main/java/com/kingsrook/qqq/frontend/android/mobileapp/viewmodel/TopLevelAppState.kt @@ -0,0 +1,81 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp.viewmodel + +import androidx.compose.material3.DrawerState +import androidx.compose.material3.DrawerValue +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/*************************************************************************** + ** + ***************************************************************************/ +class TopLevelAppState( + private val coroutineScope: CoroutineScope, + private val qViewModel: QViewModel +) +{ + var navDrawerState: DrawerState by mutableStateOf(DrawerState(DrawerValue.Closed)) + private set + + /*************************************************************************** + ** + ***************************************************************************/ + fun openNavDrawer() + { + coroutineScope.launch() + { + navDrawerState.open() + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun closeNavDrawer() + { + coroutineScope.launch() + { + navDrawerState.close() + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun logOut() + { + this.qViewModel.requestLogout() + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun isLoggedIn(): Boolean + { + return this.qViewModel.isAuthenticated + } + +} \ No newline at end of file diff --git a/qqq/android/mobileapp/src/test/java/com/kingsrook/qqq/frontend/android/mobileapp/ExampleUnitTest.kt b/qqq/android/mobileapp/src/test/java/com/kingsrook/qqq/frontend/android/mobileapp/ExampleUnitTest.kt new file mode 100644 index 0000000..2073758 --- /dev/null +++ b/qqq/android/mobileapp/src/test/java/com/kingsrook/qqq/frontend/android/mobileapp/ExampleUnitTest.kt @@ -0,0 +1,40 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.frontend.android.mobileapp + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest +{ + @Test + fun addition_isCorrect() + { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/.gitignore b/qqqsampleandroidmobileapp/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/qqqsampleandroidmobileapp/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/build.gradle.kts b/qqqsampleandroidmobileapp/build.gradle.kts new file mode 100644 index 0000000..70e069b --- /dev/null +++ b/qqqsampleandroidmobileapp/build.gradle.kts @@ -0,0 +1,96 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.kingsrook.qqq.sampleandroidmobileapp" + compileSdk = 34 + + defaultConfig { + applicationId = "com.kingsrook.qqq.sampleandroidmobileapp" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + + // todo do not commit must not hard-code + manifestPlaceholders["auth0Domain"] = "auth.coldtrack-dev.com" + manifestPlaceholders["auth0Scheme"] = "qqqsampleapp" + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.lifecycle.viewmodel.compose) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(project(":qqq:android:mobileapp")) + implementation(project(":qqq:android:core")) + implementation(libs.auth0) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) + implementation(libs.androidx.datastore.preferences) + implementation(libs.androidx.datastore) +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/proguard-rules.pro b/qqqsampleandroidmobileapp/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/qqqsampleandroidmobileapp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/BaseTest.kt b/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/BaseTest.kt new file mode 100644 index 0000000..b19169c --- /dev/null +++ b/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/BaseTest.kt @@ -0,0 +1,46 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp + +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer +import org.junit.Before + +/*************************************************************************** + ** + ***************************************************************************/ +open class BaseTest +{ + + /*************************************************************************** + ** + ***************************************************************************/ + @Before + fun beforeEach() + { + QAppContainer.availableEnvironments = listOf( + Environment("Test Env 1", "http://192.168.4.67:8000/"), + Environment("Test Env 2", "http://192.168.4.67:8000/"), + ) + } + +} diff --git a/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/SessionTests.kt b/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/SessionTests.kt new file mode 100644 index 0000000..61b8bd8 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/SessionTests.kt @@ -0,0 +1,287 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp + +import androidx.compose.ui.test.assertHasClickAction +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertTextContains +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.isDialog +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.datastore.preferences.core.MutablePreferences +import androidx.datastore.preferences.core.preferencesOf +import com.kingsrook.qqq.frontend.android.core.controllers.MockQQQRepository +import com.kingsrook.qqq.frontend.android.mobileapp.data.ENVIRONMENT_JSON_KEY +import com.kingsrook.qqq.frontend.android.mobileapp.data.MockPreferencesDataStore +import com.kingsrook.qqq.frontend.android.mobileapp.data.SESSION_USER_FULL_NAME_KEY +import com.kingsrook.qqq.frontend.android.mobileapp.data.SESSION_UUID_KEY +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel +import com.kingsrook.qqq.sampleandroidmobileapp.utils.TestUtils +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotEquals +import org.junit.Rule +import org.junit.Test + +/*************************************************************************** + ** + ***************************************************************************/ +class SessionTests : BaseTest() +{ + @get:Rule + val composeTestRule = createComposeRule() + + private val knownUUID = "some-known-uuid" + private val fullName = "John Doe Test" + private val envLabel = "Some Stored Env" + + /*************************************************************************** + ** + ***************************************************************************/ + private fun atEndLocalDebugSleep() + { + TestUtils.localDebugSleep(composeTestRule, 0) + } + + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testLogin() + { + TestUtils.startStandardTest(composeTestRule) + TestUtils.login(composeTestRule) + + assertHomePageIsDisplayed() + + atEndLocalDebugSleep() + } + + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testValuesFromDataStore() + { + val dataStore = makeMockPreferencesDataStoreWithValues() + val qViewModel = QViewModel(MockQQQRepository(), dataStore) + + TestUtils.startStandardTest(composeTestRule, qViewModel) + + ////////////////////////////////////////////////////////////// + // note - no login needed - should go straight to home page // + // with known values in viewModel // + ////////////////////////////////////////////////////////////// + assertHomePageIsDisplayed() + assertEquals(fullName, qViewModel.sessionUserFullName) + assertEquals(knownUUID, qViewModel.sessionUUID) + + openUserDialog() + composeTestRule + .onNodeWithText("Current User: ${fullName}") + .assertIsDisplayed() + composeTestRule + .onNodeWithText("Dismiss") + .performClick() + + openEnvironmentDialog() + composeTestRule + .onNodeWithText(envLabel) + .assertIsDisplayed() + composeTestRule + .onNodeWithText("Dismiss") + .performClick() + + atEndLocalDebugSleep() + } + + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testExpiredSessionFromDataStore() + { + val failureUUID = "please-fail" + val dataStore = makeMockPreferencesDataStoreWithValues() + (dataStore.preferences as MutablePreferences)[SESSION_UUID_KEY] = failureUUID + val qViewModel = QViewModel(MockQQQRepository(), dataStore) + + TestUtils.startStandardTest(composeTestRule, qViewModel) + + /////////////////////////////////////////////////////////////////////////// + // instead of logging in automatically, we should land on a login screen // + /////////////////////////////////////////////////////////////////////////// + composeTestRule + .onNodeWithText("Click to log in Anonymously:") + .assertIsDisplayed() + + ///////////////////////////////////// + // and we should be able to log in // + ///////////////////////////////////// + TestUtils.login(composeTestRule) + assertHomePageIsDisplayed() + + assertNotEquals(failureUUID, qViewModel.sessionUUID) + + assertNotEquals(failureUUID, dataStore.preferences[SESSION_UUID_KEY]) + // in case the above ever fails, it probably means race condition, so this instead + // composeTestRule.waitUntil(condition = + // { + // failureUUID != dataStore.preferences[SESSION_UUID_KEY] + // }) + + atEndLocalDebugSleep() + } + + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testUserInfoDialogAndLogout() + { + TestUtils.startStandardTest(composeTestRule) + TestUtils.login(composeTestRule) + + openUserDialog() + + composeTestRule + .onNodeWithText("Current User: Anonymous") + .assertIsDisplayed() + + composeTestRule + .onNode(hasAnyAncestor(isDialog()) and hasText("Log Out")) + .performClick() + + composeTestRule + .onNodeWithText("Logout Successful.") + .assertIsDisplayed() + + composeTestRule + .onNodeWithText("Log back in") + .assertIsDisplayed() + .performClick() + + TestUtils.login(composeTestRule) + assertHomePageIsDisplayed() + + atEndLocalDebugSleep() + } + + /*************************************************************************** + ** + ***************************************************************************/ + @Test + fun testEnvironmentDialogAndSwitch() + { + TestUtils.startStandardTest(composeTestRule) + TestUtils.login(composeTestRule) + + openEnvironmentDialog() + + composeTestRule + .onNodeWithText("Test Env 1") + .assertIsDisplayed() + .performClick() + + composeTestRule + .onNodeWithText("Test Env 2") + .assertIsDisplayed() + .performClick() + + composeTestRule + .onNodeWithText("Switch") + .assertIsDisplayed() + .performClick() + + TestUtils.login(composeTestRule) + assertHomePageIsDisplayed("Mock Application 2") + + atEndLocalDebugSleep() + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun makeMockPreferencesDataStoreWithValues(): MockPreferencesDataStore + { + val dataStore = MockPreferencesDataStore() + dataStore.preferences = preferencesOf( + SESSION_UUID_KEY to knownUUID, + SESSION_USER_FULL_NAME_KEY to fullName, + ENVIRONMENT_JSON_KEY to """ + {"label": "${envLabel}", "baseUrl": "http://was-stored"} + """.trimIndent() + ).toMutablePreferences() + return dataStore + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun openUserDialog() + { + composeTestRule + .onNodeWithTag("topBar.userIcon", true) + .assertIsDisplayed() + .assertHasClickAction() + .performClick() + + composeTestRule + .onNodeWithText("User Information") + .assertIsDisplayed() + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun openEnvironmentDialog() + { + composeTestRule + .onNodeWithTag("topBar.environmentIcon", true) + .assertIsDisplayed() + .assertHasClickAction() + .performClick() + + composeTestRule + .onNodeWithText("App Environment") + .assertIsDisplayed() + } + + /*************************************************************************** + ** + ***************************************************************************/ + private fun assertHomePageIsDisplayed(appName: String = "Mock Application") + { + composeTestRule + .onNodeWithTag("qInstance.branding.appName") + .assertTextContains(appName) + .assertIsDisplayed() + + composeTestRule + .onNodeWithText("App 1") + .assertIsDisplayed() + } + +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/utils/TestUtils.kt b/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/utils/TestUtils.kt new file mode 100644 index 0000000..0ebf77a --- /dev/null +++ b/qqqsampleandroidmobileapp/src/androidTest/java/com/kingsrook/qqq/sampleandroidmobileapp/utils/TestUtils.kt @@ -0,0 +1,145 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.utils + +import android.widget.Toast +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.onRoot +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.printToLog +import com.kingsrook.qqq.frontend.android.core.controllers.MockQQQRepository +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel +import com.kingsrook.qqq.frontend.android.mobileapp.data.MockPreferencesDataStore +import com.kingsrook.qqq.sampleandroidmobileapp.MainComposable + +/*************************************************************************** + ** + ***************************************************************************/ +object TestUtils +{ + lateinit var qViewModel: QViewModel + lateinit var toast: Toast + + /*************************************************************************** + ** + ***************************************************************************/ + fun startStandardTest(composeTestRule: ComposeContentTestRule, qViewModelParam: QViewModel? = null) + { + if(qViewModelParam == null) + { + this.qViewModel = createQViewModel(); + } + else + { + this.qViewModel = qViewModelParam; + } + + composeTestRule.setContent() + { + toast = Toast.makeText(LocalContext.current, "This is a test", 3) + MainComposable(this.qViewModel) + } + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun login(composeTestRule: ComposeContentTestRule) + { + composeTestRule.onNodeWithText("Log In").performClick() + composeTestRule.waitUntil("Should become authed", timeoutMillis = 5000, condition = + { + qViewModel.isAuthenticated + }) + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun createQViewModel(): QViewModel + { + val dataStore = MockPreferencesDataStore() + val qViewModel = QViewModel(MockQQQRepository(), dataStore) + return (qViewModel) + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun sleep(composeTestRule: ComposeContentTestRule, seconds: Int) + { + composeTestRule.waitUntil("Intentional Sleep", timeoutMillis = (seconds + 1) * 1000L, condition = + { + try + { + Thread.sleep(seconds * 1000L) + return@waitUntil true + } + catch(e: Exception) + { + // noop, just return + return@waitUntil true + } + }) + } + + /*************************************************************************** + ** Sleep, but, only when running locally, e.g., to see the screen and debug. + ** will this cause tests to behave differently in CI? We shall see! + ***************************************************************************/ + fun localDebugSleep(composeTestRule: ComposeContentTestRule, seconds: Int = 300) + { + if(seconds == 0) + { + return; + } + + if(System.getenv("CIRCLECI") != null) + { + println("In CIRCLECI - so - not doing a localDebugSleep"); + return; + } + + showToast("Going into a localDebugSleep sleep \uD83D\uDECC now for ${seconds} second${if(seconds == 1) "" else "s"} ⏰") + sleep(composeTestRule, seconds); + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun logTree(composeTestRule: ComposeContentTestRule, useUnmergedTree: Boolean) + { + composeTestRule.onRoot(useUnmergedTree = useUnmergedTree) + .printToLog("currentLabelExists") + } + + /*************************************************************************** + ** + ***************************************************************************/ + fun showToast(text: String) + { + toast.setText(text) + toast.show() + } +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/AndroidManifest.xml b/qqqsampleandroidmobileapp/src/main/AndroidManifest.xml new file mode 100644 index 0000000..d4c81a2 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/AndroidManifest.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/MainActivity.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/MainActivity.kt new file mode 100644 index 0000000..3dcdcab --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/MainActivity.kt @@ -0,0 +1,123 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp + +import android.content.Context +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.preferencesDataStore +import androidx.lifecycle.viewmodel.compose.viewModel +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer +import com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication.QAuth0Service +import com.kingsrook.qqq.frontend.android.mobileapp.ui.authentication.QAuthenticationWrapper +import com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe.QNavigationDrawer +import com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe.QTopAppBar +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.TopLevelAppState +import com.kingsrook.qqq.sampleandroidmobileapp.ui.theme.AndroidDevProjectTheme + + +val Context.dataStore: DataStore by preferencesDataStore(name = "settings") + +/*************************************************************************** + ** + ***************************************************************************/ +class MainActivity : ComponentActivity() +{ + /*************************************************************************** + ** + ***************************************************************************/ + override fun onCreate(savedInstanceState: Bundle?) + { + super.onCreate(savedInstanceState); + + QAuth0Service.scheme = "qqqsampleapp"; + QAppContainer.availableEnvironments = listOf( + Environment("Production", "https://live.coldtrack.com/"), + Environment("Staging", "https://live.coldtrack-staging.com/"), + Environment("Development", "https://live.coldtrack-dev.com/"), + Environment("Local Dev", "http://192.168.4.70:8000/"), + Environment("Bad URL", "http://no.such.domain.dot.dot/"), + ) + + enableEdgeToEdge() + setContent() + { + // todo - this should come from the Container / DI (i think?) + QViewModel.dataStore = LocalContext.current.dataStore; + val qViewModel: QViewModel = viewModel(factory = QViewModel.factory) + + MainComposable(qViewModel) + } + } +} + +/*************************************************************************** + ** + ***************************************************************************/ +@Composable +fun MainComposable(qViewModel: QViewModel) +{ + AndroidDevProjectTheme() + { + val topLevelAppState = TopLevelAppState(coroutineScope = rememberCoroutineScope(), qViewModel = qViewModel) + + QNavigationDrawer(topLevelAppState) + { + Scaffold( + Modifier.fillMaxSize(), + topBar = { QTopAppBar(qViewModel, navMenuCallback = topLevelAppState::openNavDrawer) } + ) { innerPadding -> + Column( + Modifier + .padding(innerPadding) + .fillMaxSize() + ) + { + QAuthenticationWrapper( + qViewModel, + Modifier + .padding(8.dp) + .fillMaxSize() + ) + } + } + } + + } +} + + diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/EnvironmentDialogPreview.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/EnvironmentDialogPreview.kt new file mode 100644 index 0000000..ab51cf0 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/EnvironmentDialogPreview.kt @@ -0,0 +1,42 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.previews + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.Preview +import com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe.EnvironmentDialog +import com.kingsrook.qqq.sampleandroidmobileapp.previews.utils.PreviewUtils + +/*************************************************************************** + ** + ***************************************************************************/ +@Preview +@Composable +fun EnvironmentDialogPreview() +{ + val qViewModel = PreviewUtils.createQViewModel() + val isOpen = remember { mutableStateOf(true) } + + EnvironmentDialog(qViewModel, isOpen) +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/UserDialogPreview.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/UserDialogPreview.kt new file mode 100644 index 0000000..baac8c9 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/UserDialogPreview.kt @@ -0,0 +1,42 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.previews + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.tooling.preview.Preview +import com.kingsrook.qqq.frontend.android.mobileapp.ui.horseshoe.UserDialog +import com.kingsrook.qqq.sampleandroidmobileapp.previews.utils.PreviewUtils + +/*************************************************************************** + ** + ***************************************************************************/ +@Preview +@Composable +fun UserDialogPreview() +{ + val qViewModel = PreviewUtils.createQViewModel().also { it.sessionUserFullName = "Testasuperlongnameandwheredoesitwraandwheredoesitwrappandwheredoesitwrap?"} + val isOpen = remember { mutableStateOf(true) } + + UserDialog(qViewModel, isOpen) +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/utils/PreviewUtils.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/utils/PreviewUtils.kt new file mode 100644 index 0000000..b41cda4 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/previews/utils/PreviewUtils.kt @@ -0,0 +1,51 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.previews.utils + +import com.kingsrook.qqq.frontend.android.core.controllers.MockQQQRepository +import com.kingsrook.qqq.frontend.android.core.model.Environment +import com.kingsrook.qqq.frontend.android.mobileapp.container.QAppContainer +import com.kingsrook.qqq.frontend.android.mobileapp.data.MockPreferencesDataStore +import com.kingsrook.qqq.frontend.android.mobileapp.viewmodel.QViewModel + +/*************************************************************************** + ** + ***************************************************************************/ +object PreviewUtils +{ + /*************************************************************************** + ** + ***************************************************************************/ + fun createQViewModel(): QViewModel + { + QAppContainer.availableEnvironments = listOf( + Environment("Production", "https://live.coldtrack.com/"), + Environment("Staging", "https://live.coldtrack-staging.com/"), + Environment("Production", "https://live.coldtrack-dev.com/"), + Environment("Local Test", "http://192.168.4.67:8000/"), + ) + + val dataStore = MockPreferencesDataStore() + val qViewModel = QViewModel(MockQQQRepository(), dataStore) + return (qViewModel) + } +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Color.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Color.kt new file mode 100644 index 0000000..a2f5cd6 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Color.kt @@ -0,0 +1,32 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Theme.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Theme.kt new file mode 100644 index 0000000..13ab005 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Theme.kt @@ -0,0 +1,82 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun AndroidDevProjectTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) +{ + val colorScheme = when + { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> + { + val context = LocalContext.current + if(darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Type.kt b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Type.kt new file mode 100644 index 0000000..8ceff88 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/java/com/kingsrook/qqq/sampleandroidmobileapp/ui/theme/Type.kt @@ -0,0 +1,55 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2024-2024. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * 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 . + */ + +package com.kingsrook.qqq.sampleandroidmobileapp.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/res/drawable/ic_launcher_background.xml b/qqqsampleandroidmobileapp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..054c5f7 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,191 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/qqqsampleandroidmobileapp/src/main/res/drawable/ic_launcher_foreground.xml b/qqqsampleandroidmobileapp/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..816b509 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/qqqsampleandroidmobileapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..683ed4d --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/qqqsampleandroidmobileapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..683ed4d --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-hdpi/ic_launcher.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-mdpi/ic_launcher.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-xhdpi/ic_launcher.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/qqqsampleandroidmobileapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/qqqsampleandroidmobileapp/src/main/res/values/colors.xml b/qqqsampleandroidmobileapp/src/main/res/values/colors.xml new file mode 100644 index 0000000..ebdfb06 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/values/colors.xml @@ -0,0 +1,31 @@ + + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/res/values/strings.xml b/qqqsampleandroidmobileapp/src/main/res/values/strings.xml new file mode 100644 index 0000000..1010c50 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/values/strings.xml @@ -0,0 +1,24 @@ + + + + QQQ Sample Android Mobile App + \ No newline at end of file diff --git a/qqqsampleandroidmobileapp/src/main/res/values/themes.xml b/qqqsampleandroidmobileapp/src/main/res/values/themes.xml new file mode 100644 index 0000000..d75c478 --- /dev/null +++ b/qqqsampleandroidmobileapp/src/main/res/values/themes.xml @@ -0,0 +1,26 @@ + + + + + +