diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..3f72f815de3e3092b0b522753ea6ae5d09f8e224 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Default: treat files as text and normalize to LF in repo +* text=auto eol=lf + +# Keep Windows scripts as CRLF if you have them +*.bat text eol=crlf +*.cmd text eol=crlf + +# Binary files (don't touch line endings) +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.jar binary +*.keystore binary +*.apk binary +*.aab binary diff --git a/LICENSE b/LICENSE index 583f384ecf059a6093e388f85fddb4beb1377950..45fea324443b2d309232739bfc6e1eeaf9458db6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,674 +1,674 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - Apps Quickly and easily install Android apps onto your device! - Copyright (C) 2021 E FOUNDATION - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Apps Copyright (C) 2021 E FOUNDATION - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + Apps Quickly and easily install Android apps onto your device! + Copyright (C) 2021 E FOUNDATION + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Apps Copyright (C) 2021 E FOUNDATION + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/app/lint.xml b/app/lint.xml index d91d126a9148ea739ea2070aa007835dc6d06e5d..21c78df5c3c249d653fb45ad83084a0419f368bd 100644 --- a/app/lint.xml +++ b/app/lint.xml @@ -1,5 +1,5 @@ - - - - - + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b8d83e7f692ff98b5b072005bb663c47678e3e24..cb9d3d92a951959d070a817074fa7e9e020a32c3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,215 +1,215 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt index b13c12c69b975f9baabeb89ca43f7f4c2f720181..b98bfc5d4d13373a15b9792e6c243917cd6f9d5e 100644 --- a/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt +++ b/app/src/main/java/foundation/e/apps/AppLoungeApplication.kt @@ -1,159 +1,159 @@ -/* - * Copyright (C) 2021-2024 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps - -import android.app.Application -import android.os.Build -import android.util.Log -import androidx.annotation.RequiresApi -import androidx.hilt.work.HiltWorkerFactory -import androidx.work.Configuration -import androidx.work.ExistingPeriodicWorkPolicy -import dagger.hilt.android.HiltAndroidApp -import foundation.e.apps.data.Constants.TAG_APP_INSTALL_STATE -import foundation.e.apps.data.Constants.TAG_AUTHDATA_DUMP -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.install.AppInstallDAO -import foundation.e.apps.data.preference.AppLoungeDataStore -import foundation.e.apps.data.preference.AppLoungePreference -import foundation.e.apps.di.qualifiers.IoCoroutineScope -import foundation.e.apps.install.pkg.AppLoungePackageManager -import foundation.e.apps.install.pkg.PkgManagerBR -import foundation.e.apps.install.updates.UpdatesWorkManager -import foundation.e.apps.install.workmanager.InstallWorkManager -import foundation.e.apps.ui.setup.tos.TOS_VERSION -import foundation.e.apps.utils.CustomUncaughtExceptionHandler -import foundation.e.lib.telemetry.Telemetry -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.MainScope -import kotlinx.coroutines.flow.first -import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import timber.log.Timber -import timber.log.Timber.Forest.plant -import java.util.concurrent.Executors -import javax.inject.Inject - -@HiltAndroidApp -@DelicateCoroutinesApi -class AppLoungeApplication : Application(), Configuration.Provider { - - @Inject - lateinit var appLoungePackageManager: AppLoungePackageManager - - @Inject - lateinit var workerFactory: HiltWorkerFactory - - @Inject - lateinit var appLoungeDataStore: AppLoungeDataStore - - @Inject - lateinit var appLoungePreference: AppLoungePreference - - @Inject - lateinit var appInstallDao: AppInstallDAO - - @Inject - lateinit var uncaughtExceptionHandler: CustomUncaughtExceptionHandler - - @Inject - @IoCoroutineScope - lateinit var coroutineScope: CoroutineScope - - @RequiresApi(Build.VERSION_CODES.TIRAMISU) - override fun onCreate() { - super.onCreate() - - Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler) - - InstallWorkManager.context = this - // Register broadcast receiver for package manager - val pkgManagerBR = object : PkgManagerBR() {} - registerReceiver(pkgManagerBR, appLoungePackageManager.getFilter(), RECEIVER_EXPORTED) - - val currentVersion = runBlocking { appLoungeDataStore.tosVersion.first() } - if (!currentVersion.contentEquals(TOS_VERSION)) { - MainScope().launch { - appLoungeDataStore.saveTOCStatus(false, "") - } - } - - if (BuildConfig.DEBUG) { - plant(Timber.DebugTree()) - } else { - // Allow enabling telemetry only for release builds. - Telemetry.init(BuildConfig.SENTRY_DSN, this) - plant(object : Timber.Tree() { - override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { - if (priority <= Log.WARN && !listOf( - TAG_AUTHDATA_DUMP, - TAG_APP_INSTALL_STATE - ).contains(tag) - ) { - return - } - Log.println(priority, tag, message) - } - }) - } - - appLoungePreference.migrateAnonymousUserUpdateInterval() - UpdatesWorkManager.enqueueWork( - this, - appLoungePreference.getUpdateInterval(), - ExistingPeriodicWorkPolicy.KEEP - ) - - removeStalledInstallationFromDb() - } - - private fun removeStalledInstallationFromDb() = coroutineScope.launch { - val existingInstallations = appInstallDao.getItemInInstallation().toMutableList() - if (existingInstallations.isEmpty()) { - return@launch - } - val validPackageSession = packageManager.packageInstaller.allSessions - .filter { !it.isActive } - .mapNotNull { info -> info.appPackageName } - - if (validPackageSession.isNotEmpty()) { - existingInstallations.removeIf { validPackageSession.contains(it.packageName) } - } - - if (existingInstallations.isEmpty()) { - Timber.d("All packages have corresponding sessions;") - return@launch - } - - Timber.e("removing ${existingInstallations.size} app from db stuck at installing") - for (appInstall in existingInstallations) { - Timber.d("removing (${appInstall.packageName}) : (${appInstall.id}) from db") - - appInstall.status = Status.INSTALLATION_ISSUE - appInstallDao.deleteDownload(appInstall) - } - } - - override val workManagerConfiguration: Configuration - get() = Configuration.Builder() - .setWorkerFactory(workerFactory) - .setExecutor(Executors.newSingleThreadExecutor()) - .build() -} +/* + * Copyright (C) 2021-2024 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps + +import android.app.Application +import android.os.Build +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.hilt.work.HiltWorkerFactory +import androidx.work.Configuration +import androidx.work.ExistingPeriodicWorkPolicy +import dagger.hilt.android.HiltAndroidApp +import foundation.e.apps.data.Constants.TAG_APP_INSTALL_STATE +import foundation.e.apps.data.Constants.TAG_AUTHDATA_DUMP +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.install.AppInstallDAO +import foundation.e.apps.data.preference.AppLoungeDataStore +import foundation.e.apps.data.preference.AppLoungePreference +import foundation.e.apps.di.qualifiers.IoCoroutineScope +import foundation.e.apps.install.pkg.AppLoungePackageManager +import foundation.e.apps.install.pkg.PkgManagerBR +import foundation.e.apps.install.updates.UpdatesWorkManager +import foundation.e.apps.install.workmanager.InstallWorkManager +import foundation.e.apps.ui.setup.tos.TOS_VERSION +import foundation.e.apps.utils.CustomUncaughtExceptionHandler +import foundation.e.lib.telemetry.Telemetry +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.DelicateCoroutinesApi +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import timber.log.Timber +import timber.log.Timber.Forest.plant +import java.util.concurrent.Executors +import javax.inject.Inject + +@HiltAndroidApp +@DelicateCoroutinesApi +class AppLoungeApplication : Application(), Configuration.Provider { + + @Inject + lateinit var appLoungePackageManager: AppLoungePackageManager + + @Inject + lateinit var workerFactory: HiltWorkerFactory + + @Inject + lateinit var appLoungeDataStore: AppLoungeDataStore + + @Inject + lateinit var appLoungePreference: AppLoungePreference + + @Inject + lateinit var appInstallDao: AppInstallDAO + + @Inject + lateinit var uncaughtExceptionHandler: CustomUncaughtExceptionHandler + + @Inject + @IoCoroutineScope + lateinit var coroutineScope: CoroutineScope + + @RequiresApi(Build.VERSION_CODES.TIRAMISU) + override fun onCreate() { + super.onCreate() + + Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler) + + InstallWorkManager.context = this + // Register broadcast receiver for package manager + val pkgManagerBR = object : PkgManagerBR() {} + registerReceiver(pkgManagerBR, appLoungePackageManager.getFilter(), RECEIVER_EXPORTED) + + val currentVersion = runBlocking { appLoungeDataStore.tosVersion.first() } + if (!currentVersion.contentEquals(TOS_VERSION)) { + MainScope().launch { + appLoungeDataStore.saveTOCStatus(false, "") + } + } + + if (BuildConfig.DEBUG) { + plant(Timber.DebugTree()) + } else { + // Allow enabling telemetry only for release builds. + Telemetry.init(BuildConfig.SENTRY_DSN, this) + plant(object : Timber.Tree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (priority <= Log.WARN && !listOf( + TAG_AUTHDATA_DUMP, + TAG_APP_INSTALL_STATE + ).contains(tag) + ) { + return + } + Log.println(priority, tag, message) + } + }) + } + + appLoungePreference.migrateAnonymousUserUpdateInterval() + UpdatesWorkManager.enqueueWork( + this, + appLoungePreference.getUpdateInterval(), + ExistingPeriodicWorkPolicy.KEEP + ) + + removeStalledInstallationFromDb() + } + + private fun removeStalledInstallationFromDb() = coroutineScope.launch { + val existingInstallations = appInstallDao.getItemInInstallation().toMutableList() + if (existingInstallations.isEmpty()) { + return@launch + } + val validPackageSession = packageManager.packageInstaller.allSessions + .filter { !it.isActive } + .mapNotNull { info -> info.appPackageName } + + if (validPackageSession.isNotEmpty()) { + existingInstallations.removeIf { validPackageSession.contains(it.packageName) } + } + + if (existingInstallations.isEmpty()) { + Timber.d("All packages have corresponding sessions;") + return@launch + } + + Timber.e("removing ${existingInstallations.size} app from db stuck at installing") + for (appInstall in existingInstallations) { + Timber.d("removing (${appInstall.packageName}) : (${appInstall.id}) from db") + + appInstall.status = Status.INSTALLATION_ISSUE + appInstallDao.deleteDownload(appInstall) + } + } + + override val workManagerConfiguration: Configuration + get() = Configuration.Builder() + .setWorkerFactory(workerFactory) + .setExecutor(Executors.newSingleThreadExecutor()) + .build() +} diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationInstaller.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationInstaller.kt index 29b10ee664220495fe44b02ec7145674532f148c..007b7b9c365dea5533c688e12d07d9248604ff73 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationInstaller.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationInstaller.kt @@ -1,28 +1,28 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.application - -import foundation.e.apps.data.application.data.Application - -interface ApplicationInstaller { - - fun installApplication(app: Application) - - fun cancelDownload(app: Application) -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.application + +import foundation.e.apps.data.application.data.Application + +interface ApplicationInstaller { + + fun installApplication(app: Application) + + fun cancelDownload(app: Application) +} diff --git a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt index 2f6fa76aa61c5526d1600ce378b633c28ada81dc..96ebe5f78e6335491e69637f9ed87646e928b4e9 100644 --- a/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/application/ApplicationRepository.kt @@ -1,210 +1,210 @@ -/* - * Copyright (C) 2021-2025 e Foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.data.application - -import androidx.lifecycle.LiveData -import androidx.lifecycle.liveData -import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.Stores -import foundation.e.apps.data.application.apps.AppsApi -import foundation.e.apps.data.application.category.CategoriesResponse -import foundation.e.apps.data.application.category.CategoryApi -import foundation.e.apps.data.application.data.Application -import foundation.e.apps.data.application.data.Home -import foundation.e.apps.data.application.downloadInfo.DownloadInfoApi -import foundation.e.apps.data.application.utils.CategoryType -import foundation.e.apps.data.enums.FilterLevel -import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.handleNetworkResult -import foundation.e.apps.data.install.models.AppInstall -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class ApplicationRepository @Inject constructor( - private val categoryApi: CategoryApi, - private val appsApi: AppsApi, - private val downloadInfoApi: DownloadInfoApi, - private val stores: Stores -) { - companion object { - const val APP_TYPE_ANY = "any" - const val APP_TYPE_OPEN = "open" - const val APP_TYPE_PWA = "pwa" - } - - private enum class AppSourceWeight { - GPLAY, - OPEN_SOURCE, - PWA - } - - fun getHomeScreenData(): LiveData>> { - return liveData { - suspend fun emitResult( - result: ResultSupreme>, - resultsBySource: Map>> - ) { - val merged = mergeResults(resultsBySource.values) - emit( - ResultSupreme.create( - result.getResultStatus(), - merged, - result.message, - result.exception, - ) - ) - } - - coroutineScope { - val resultsBySource = mutableMapOf>>() - val sources = stores.getStores().keys - val deferredResults = sources.associateWith { source -> - async { loadHomeData(source) } - } - - sources.forEach { source -> - val result = deferredResults.getValue(source).await() - resultsBySource[source] = result - emitResult(result, resultsBySource) - } - } - } - } - - private suspend fun loadHomeData(source: Source): ResultSupreme> { - val list = mutableListOf() - val result = handleNetworkResult { - val homeDataBuilder = stores.getStore(source) - checkNotNull(homeDataBuilder) { "Could not find store for $source" } - homeDataBuilder.getHomeScreenData(list) - } - - setHomeErrorMessage(result.getResultStatus(), source) - list.sortBy { - when (it.source) { - APP_TYPE_OPEN -> AppSourceWeight.OPEN_SOURCE.ordinal - APP_TYPE_PWA -> AppSourceWeight.PWA.ordinal - else -> AppSourceWeight.GPLAY.ordinal - } - } - - return ResultSupreme.create(result.getResultStatus(), list) - } - - private fun setHomeErrorMessage(apiStatus: ResultStatus, source: Source) { - if (apiStatus != ResultStatus.OK) { - apiStatus.message = when (source) { - Source.PLAY_STORE -> ("GPlay home loading error\n" + apiStatus.message).trim() - Source.SYSTEM_APP -> ("Gitlab home not allowed\n" + apiStatus.message).trim() - Source.OPEN_SOURCE -> ("Open Source home loading error\n" + apiStatus.message).trim() - Source.PWA -> ("PWA home loading error\n" + apiStatus.message).trim() - } - } - } - - private fun mergeResults(results: Collection>>): List { - val merged = results.flatMap { it.data.orEmpty() }.toMutableList() - merged.sortBy { - when (it.source) { - APP_TYPE_OPEN -> AppSourceWeight.OPEN_SOURCE.ordinal - APP_TYPE_PWA -> AppSourceWeight.PWA.ordinal - else -> AppSourceWeight.GPLAY.ordinal - } - } - return merged - } - - fun getSelectedAppTypes(): List { - val selectedAppTypes = mutableListOf() - if (stores.isStoreEnabled(Source.PLAY_STORE)) selectedAppTypes.add(APP_TYPE_ANY) - if (stores.isStoreEnabled(Source.OPEN_SOURCE)) selectedAppTypes.add(APP_TYPE_OPEN) - if (stores.isStoreEnabled(Source.PWA)) selectedAppTypes.add(APP_TYPE_PWA) - - return selectedAppTypes - } - - suspend fun getApplicationDetails( - packageNameList: List, - source: Source - ): Pair, ResultStatus> { - return appsApi.getApplicationDetails(packageNameList, source) - } - - suspend fun getAppFilterLevel(application: Application): FilterLevel { - return appsApi.getAppFilterLevel(application) - } - - suspend fun getApplicationDetails( - id: String, - packageName: String, - source: Source - ): Pair { - return appsApi.getApplicationDetails(id, packageName, source) - } - - suspend fun updateFusedDownloadWithDownloadingInfo( - source: Source, - appInstall: AppInstall - ) { - downloadInfoApi.updateFusedDownloadWithDownloadingInfo( - source, - appInstall - ) - } - - suspend fun getOSSDownloadInfo(id: String, version: String? = null) = - downloadInfoApi.getOSSDownloadInfo(id, version) - - suspend fun getCategoriesList( - type: CategoryType, - ): List { - return categoryApi.getCategoriesList(type) - } - - suspend fun getAppsListBasedOnCategory( - category: String, - pageUrl: String?, - source: Source - ): ResultSupreme, String>> { - return when (source) { - Source.OPEN_SOURCE -> categoryApi.getCleanApkAppsByCategory(category, Source.OPEN_SOURCE) - Source.PWA -> categoryApi.getCleanApkAppsByCategory(category, Source.PWA) - else -> categoryApi.getGplayAppsByCategory(category, pageUrl) - } - } - - fun getFusedAppInstallationStatus(application: Application): Status { - return appsApi.getFusedAppInstallationStatus(application) - } - - fun isAnyFusedAppUpdated( - newApplications: List, - oldApplications: List - ) = appsApi.isAnyFusedAppUpdated(newApplications, oldApplications) - - fun isAnyAppInstallStatusChanged(currentList: List) = - appsApi.isAnyAppInstallStatusChanged(currentList) - - fun isOpenSourceSelected() = appsApi.isOpenSourceSelected() -} +/* + * Copyright (C) 2021-2025 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.data.application + +import androidx.lifecycle.LiveData +import androidx.lifecycle.liveData +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.Stores +import foundation.e.apps.data.application.apps.AppsApi +import foundation.e.apps.data.application.category.CategoriesResponse +import foundation.e.apps.data.application.category.CategoryApi +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.application.data.Home +import foundation.e.apps.data.application.downloadInfo.DownloadInfoApi +import foundation.e.apps.data.application.utils.CategoryType +import foundation.e.apps.data.enums.FilterLevel +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.handleNetworkResult +import foundation.e.apps.data.install.models.AppInstall +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class ApplicationRepository @Inject constructor( + private val categoryApi: CategoryApi, + private val appsApi: AppsApi, + private val downloadInfoApi: DownloadInfoApi, + private val stores: Stores +) { + companion object { + const val APP_TYPE_ANY = "any" + const val APP_TYPE_OPEN = "open" + const val APP_TYPE_PWA = "pwa" + } + + private enum class AppSourceWeight { + GPLAY, + OPEN_SOURCE, + PWA + } + + fun getHomeScreenData(): LiveData>> { + return liveData { + suspend fun emitResult( + result: ResultSupreme>, + resultsBySource: Map>> + ) { + val merged = mergeResults(resultsBySource.values) + emit( + ResultSupreme.create( + result.getResultStatus(), + merged, + result.message, + result.exception, + ) + ) + } + + coroutineScope { + val resultsBySource = mutableMapOf>>() + val sources = stores.getStores().keys + val deferredResults = sources.associateWith { source -> + async { loadHomeData(source) } + } + + sources.forEach { source -> + val result = deferredResults.getValue(source).await() + resultsBySource[source] = result + emitResult(result, resultsBySource) + } + } + } + } + + private suspend fun loadHomeData(source: Source): ResultSupreme> { + val list = mutableListOf() + val result = handleNetworkResult { + val homeDataBuilder = stores.getStore(source) + checkNotNull(homeDataBuilder) { "Could not find store for $source" } + homeDataBuilder.getHomeScreenData(list) + } + + setHomeErrorMessage(result.getResultStatus(), source) + list.sortBy { + when (it.source) { + APP_TYPE_OPEN -> AppSourceWeight.OPEN_SOURCE.ordinal + APP_TYPE_PWA -> AppSourceWeight.PWA.ordinal + else -> AppSourceWeight.GPLAY.ordinal + } + } + + return ResultSupreme.create(result.getResultStatus(), list) + } + + private fun setHomeErrorMessage(apiStatus: ResultStatus, source: Source) { + if (apiStatus != ResultStatus.OK) { + apiStatus.message = when (source) { + Source.PLAY_STORE -> ("GPlay home loading error\n" + apiStatus.message).trim() + Source.SYSTEM_APP -> ("Gitlab home not allowed\n" + apiStatus.message).trim() + Source.OPEN_SOURCE -> ("Open Source home loading error\n" + apiStatus.message).trim() + Source.PWA -> ("PWA home loading error\n" + apiStatus.message).trim() + } + } + } + + private fun mergeResults(results: Collection>>): List { + val merged = results.flatMap { it.data.orEmpty() }.toMutableList() + merged.sortBy { + when (it.source) { + APP_TYPE_OPEN -> AppSourceWeight.OPEN_SOURCE.ordinal + APP_TYPE_PWA -> AppSourceWeight.PWA.ordinal + else -> AppSourceWeight.GPLAY.ordinal + } + } + return merged + } + + fun getSelectedAppTypes(): List { + val selectedAppTypes = mutableListOf() + if (stores.isStoreEnabled(Source.PLAY_STORE)) selectedAppTypes.add(APP_TYPE_ANY) + if (stores.isStoreEnabled(Source.OPEN_SOURCE)) selectedAppTypes.add(APP_TYPE_OPEN) + if (stores.isStoreEnabled(Source.PWA)) selectedAppTypes.add(APP_TYPE_PWA) + + return selectedAppTypes + } + + suspend fun getApplicationDetails( + packageNameList: List, + source: Source + ): Pair, ResultStatus> { + return appsApi.getApplicationDetails(packageNameList, source) + } + + suspend fun getAppFilterLevel(application: Application): FilterLevel { + return appsApi.getAppFilterLevel(application) + } + + suspend fun getApplicationDetails( + id: String, + packageName: String, + source: Source + ): Pair { + return appsApi.getApplicationDetails(id, packageName, source) + } + + suspend fun updateFusedDownloadWithDownloadingInfo( + source: Source, + appInstall: AppInstall + ) { + downloadInfoApi.updateFusedDownloadWithDownloadingInfo( + source, + appInstall + ) + } + + suspend fun getOSSDownloadInfo(id: String, version: String? = null) = + downloadInfoApi.getOSSDownloadInfo(id, version) + + suspend fun getCategoriesList( + type: CategoryType, + ): List { + return categoryApi.getCategoriesList(type) + } + + suspend fun getAppsListBasedOnCategory( + category: String, + pageUrl: String?, + source: Source + ): ResultSupreme, String>> { + return when (source) { + Source.OPEN_SOURCE -> categoryApi.getCleanApkAppsByCategory(category, Source.OPEN_SOURCE) + Source.PWA -> categoryApi.getCleanApkAppsByCategory(category, Source.PWA) + else -> categoryApi.getGplayAppsByCategory(category, pageUrl) + } + } + + fun getFusedAppInstallationStatus(application: Application): Status { + return appsApi.getFusedAppInstallationStatus(application) + } + + fun isAnyFusedAppUpdated( + newApplications: List, + oldApplications: List + ) = appsApi.isAnyFusedAppUpdated(newApplications, oldApplications) + + fun isAnyAppInstallStatusChanged(currentList: List) = + appsApi.isAnyAppInstallStatusChanged(currentList) + + fun isOpenSourceSelected() = appsApi.isOpenSourceSelected() +} diff --git a/app/src/main/java/foundation/e/apps/data/application/data/Application.kt b/app/src/main/java/foundation/e/apps/data/application/data/Application.kt index 924a5666f9791ef0b90b00e395eb4bbaf47e331d..df1330645d254e7aaf59e5da5e3eb0ec08210cec 100644 --- a/app/src/main/java/foundation/e/apps/data/application/data/Application.kt +++ b/app/src/main/java/foundation/e/apps/data/application/data/Application.kt @@ -1,149 +1,149 @@ -/* - * Copyright (C) 2021-2024 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.data.application.data - -import android.net.Uri -import androidx.core.net.toUri -import com.aurora.gplayapi.Constants.Restriction -import com.aurora.gplayapi.data.models.ContentRating -import com.google.gson.annotations.SerializedName -import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.FilterLevel -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.Type -import foundation.e.apps.data.enums.Type.NATIVE -import foundation.e.apps.data.enums.Type.PWA - -data class Application( - val _id: String = String(), - val author: String = String(), - val category: String = String(), - val description: String = String(), - var perms: List = emptyList(), - var reportId: Long = -1L, - val icon_image_path: String = String(), - val icon_url: String = String(), - val last_modified: String = String(), - var latest_version_code: Long = -1, - val latest_version_number: String = String(), - val latest_downloaded_version: String = String(), - val licence: String = String(), - val name: String = String(), - val other_images_path: List = emptyList(), - val package_name: String = String(), - val ratings: Ratings = Ratings(), - val offer_type: Int = -1, - var status: Status = Status.UNAVAILABLE, - val shareUrl: String = String(), - val originalSize: Long = 0, - var appSize: String = String(), - var source: Source = Source.PLAY_STORE, - val price: String = String(), - val isFree: Boolean = true, - val is_pwa: Boolean = false, - var pwaPlayerDbId: Long = -1, - val url: String = String(), - var type: Type = NATIVE, - var privacyScore: Int = -1, - var isPurchased: Boolean = false, - var updatedOn: String = String(), - - /* - * Number of permissions and trackers from Exodus Api used for privacy score calculation. - */ - var numberOfPermission: Int = 0, - var numberOfTracker: Int = 0, - - /* - * Store restriction from App. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] - */ - var restriction: Restriction = Restriction.NOT_RESTRICTED, - - /* - * Show a blank app at the end during loading. - * Used when loading apps of a category. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] - */ - var isPlaceHolder: Boolean = false, - - /* - * Store the filter/restriction level. - * If it is not NONE, then the app cannot be downloaded. - * If it is FilterLevel.UI, then we should show "N/A" on install button. - * If it is FilterLevel.DATA, then this app should not be displayed. - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 - */ - var filterLevel: FilterLevel = FilterLevel.UNKNOWN, - var isGplayReplaced: Boolean = false, - @SerializedName(value = "on_fdroid") - val isFDroidApp: Boolean = false, - var contentRating: ContentRating = ContentRating(), - @SerializedName(value = "antifeatures") - val antiFeatures: List> = emptyList(), - var isSystemApp: Boolean = false, -) { - val iconUrl: String? - get() { - if (icon_url.isNotBlank()) { - return icon_url - } - if (icon_image_path.isBlank()) { - return null - } - return when (source) { - Source.OPEN_SOURCE, Source.PWA -> { - if (icon_image_path.startsWith("http")) { - icon_image_path - } else { - CleanApkRetrofit.ASSET_URL + icon_image_path - } - } - Source.SYSTEM_APP, Source.PLAY_STORE -> icon_image_path - } - } - - fun updateType() { - this.type = if (this.is_pwa) PWA else NATIVE - } - - fun hasExodusPrivacyRating(): Boolean { - return this.reportId.toInt() != -1 - } -} - -val Application.shareUri: Uri - get() = when (type) { - PWA -> url.toUri() - NATIVE -> when { - isFDroidApp -> buildFDroidUri(package_name) - else -> shareUrl.toUri() - } - } - -private fun buildFDroidUri(packageName: String): Uri { - return Uri.Builder() - .scheme("https") - .authority("f-droid.org") - .appendPath("packages") - .appendPath(packageName) - .build() -} +/* + * Copyright (C) 2021-2024 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.data.application.data + +import android.net.Uri +import androidx.core.net.toUri +import com.aurora.gplayapi.Constants.Restriction +import com.aurora.gplayapi.data.models.ContentRating +import com.google.gson.annotations.SerializedName +import foundation.e.apps.data.cleanapk.CleanApkRetrofit +import foundation.e.apps.data.enums.FilterLevel +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.enums.Type +import foundation.e.apps.data.enums.Type.NATIVE +import foundation.e.apps.data.enums.Type.PWA + +data class Application( + val _id: String = String(), + val author: String = String(), + val category: String = String(), + val description: String = String(), + var perms: List = emptyList(), + var reportId: Long = -1L, + val icon_image_path: String = String(), + val icon_url: String = String(), + val last_modified: String = String(), + var latest_version_code: Long = -1, + val latest_version_number: String = String(), + val latest_downloaded_version: String = String(), + val licence: String = String(), + val name: String = String(), + val other_images_path: List = emptyList(), + val package_name: String = String(), + val ratings: Ratings = Ratings(), + val offer_type: Int = -1, + var status: Status = Status.UNAVAILABLE, + val shareUrl: String = String(), + val originalSize: Long = 0, + var appSize: String = String(), + var source: Source = Source.PLAY_STORE, + val price: String = String(), + val isFree: Boolean = true, + val is_pwa: Boolean = false, + var pwaPlayerDbId: Long = -1, + val url: String = String(), + var type: Type = NATIVE, + var privacyScore: Int = -1, + var isPurchased: Boolean = false, + var updatedOn: String = String(), + + /* + * Number of permissions and trackers from Exodus Api used for privacy score calculation. + */ + var numberOfPermission: Int = 0, + var numberOfTracker: Int = 0, + + /* + * Store restriction from App. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] + */ + var restriction: Restriction = Restriction.NOT_RESTRICTED, + + /* + * Show a blank app at the end during loading. + * Used when loading apps of a category. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5131 [2] + */ + var isPlaceHolder: Boolean = false, + + /* + * Store the filter/restriction level. + * If it is not NONE, then the app cannot be downloaded. + * If it is FilterLevel.UI, then we should show "N/A" on install button. + * If it is FilterLevel.DATA, then this app should not be displayed. + * + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 + */ + var filterLevel: FilterLevel = FilterLevel.UNKNOWN, + var isGplayReplaced: Boolean = false, + @SerializedName(value = "on_fdroid") + val isFDroidApp: Boolean = false, + var contentRating: ContentRating = ContentRating(), + @SerializedName(value = "antifeatures") + val antiFeatures: List> = emptyList(), + var isSystemApp: Boolean = false, +) { + val iconUrl: String? + get() { + if (icon_url.isNotBlank()) { + return icon_url + } + if (icon_image_path.isBlank()) { + return null + } + return when (source) { + Source.OPEN_SOURCE, Source.PWA -> { + if (icon_image_path.startsWith("http")) { + icon_image_path + } else { + CleanApkRetrofit.ASSET_URL + icon_image_path + } + } + Source.SYSTEM_APP, Source.PLAY_STORE -> icon_image_path + } + } + + fun updateType() { + this.type = if (this.is_pwa) PWA else NATIVE + } + + fun hasExodusPrivacyRating(): Boolean { + return this.reportId.toInt() != -1 + } +} + +val Application.shareUri: Uri + get() = when (type) { + PWA -> url.toUri() + NATIVE -> when { + isFDroidApp -> buildFDroidUri(package_name) + else -> shareUrl.toUri() + } + } + +private fun buildFDroidUri(packageName: String): Uri { + return Uri.Builder() + .scheme("https") + .authority("f-droid.org") + .appendPath("packages") + .appendPath(packageName) + .build() +} diff --git a/app/src/main/java/foundation/e/apps/data/application/data/Category.kt b/app/src/main/java/foundation/e/apps/data/application/data/Category.kt index 85d4d5ab23aebc5d4cad751a2bfd42ebe87a3d27..0b195c5e211a5f65d5f1ba15f04405ce54aee5c6 100644 --- a/app/src/main/java/foundation/e/apps/data/application/data/Category.kt +++ b/app/src/main/java/foundation/e/apps/data/application/data/Category.kt @@ -1,35 +1,35 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.application.data - -import foundation.e.apps.data.enums.AppTag -import java.util.UUID - -data class Category( - val id: String = UUID.randomUUID().toString(), - val title: String = String(), - val browseUrl: String = String(), - val imageUrl: String = String(), - var drawable: Int = -1, - /* - * Change tag to standard AppTag class. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5364 - */ - var tag: AppTag = AppTag.GPlay() -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.application.data + +import foundation.e.apps.data.enums.AppTag +import java.util.UUID + +data class Category( + val id: String = UUID.randomUUID().toString(), + val title: String = String(), + val browseUrl: String = String(), + val imageUrl: String = String(), + var drawable: Int = -1, + /* + * Change tag to standard AppTag class. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5364 + */ + var tag: AppTag = AppTag.GPlay() +) diff --git a/app/src/main/java/foundation/e/apps/data/application/data/Home.kt b/app/src/main/java/foundation/e/apps/data/application/data/Home.kt index 520f6a3f1199e4f5916c5cb87ff0dc9442a69346..ae1092994708028ce75e8acc92ed32a6430449b1 100644 --- a/app/src/main/java/foundation/e/apps/data/application/data/Home.kt +++ b/app/src/main/java/foundation/e/apps/data/application/data/Home.kt @@ -1,28 +1,28 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.application.data - -import java.util.UUID - -data class Home( - val title: String = String(), - val list: List = emptyList(), - var source: String = String(), - var id: String = UUID.randomUUID().toString() -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.application.data + +import java.util.UUID + +data class Home( + val title: String = String(), + val list: List = emptyList(), + var source: String = String(), + var id: String = UUID.randomUUID().toString() +) diff --git a/app/src/main/java/foundation/e/apps/data/application/data/Ratings.kt b/app/src/main/java/foundation/e/apps/data/application/data/Ratings.kt index a8db122e75232ca3a55005b534925a7f19675f2f..5205b82415c5c51b2129e038441a60f9afb569e2 100644 --- a/app/src/main/java/foundation/e/apps/data/application/data/Ratings.kt +++ b/app/src/main/java/foundation/e/apps/data/application/data/Ratings.kt @@ -1,24 +1,24 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.application.data - -data class Ratings( - val privacyScore: Double = -1.0, - val usageQualityScore: Double = -1.0 -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.application.data + +data class Ratings( + val privacyScore: Double = -1.0, + val usageQualityScore: Double = -1.0 +) diff --git a/app/src/main/java/foundation/e/apps/data/application/search/SearchRepositoryImpl.kt b/app/src/main/java/foundation/e/apps/data/application/search/SearchRepositoryImpl.kt index 97c68bfa7277f72af9a1d92a86c80a06690afe0b..ade1e278d93966ed3c19ca611e8a76f67d48513e 100644 --- a/app/src/main/java/foundation/e/apps/data/application/search/SearchRepositoryImpl.kt +++ b/app/src/main/java/foundation/e/apps/data/application/search/SearchRepositoryImpl.kt @@ -1,264 +1,264 @@ -/* - * Copyright (C) 2024-2025 e Foundation - * Copyright (C) 2024 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.data.application.search - -import android.content.Context -import dagger.hilt.android.qualifiers.ApplicationContext -import foundation.e.apps.data.ResultSupreme -import foundation.e.apps.data.Stores -import foundation.e.apps.data.application.ApplicationDataManager -import foundation.e.apps.data.application.data.Application -import foundation.e.apps.data.enums.ResultStatus -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.handleNetworkResult -import foundation.e.apps.data.login.exceptions.GPlayIOException -import foundation.e.apps.data.playstore.PlayStoreRepository -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SearchRepositoryImpl @Inject constructor( - private val stores: Stores, - private val applicationDataManager: ApplicationDataManager -) : SearchRepository { - - @Inject - @ApplicationContext - lateinit var context: Context - - override suspend fun getOpenSourceSearchResults(query: String): SearchResult { - val searchResultsByPackageName = if (isPackageName(query)) { - searchAppsByPackageName(query).data?.first.orEmpty() - } else { - emptyList() - } - - val searchResultsByKeyword = buildList { - if (stores.isStoreEnabled(Source.OPEN_SOURCE)) { - addAll(fetchOpenSourceSearchResult(query, searchResultsByPackageName)) - } - if (stores.isStoreEnabled(Source.PWA)) { - addAll(fetchPwaSearchResult(query, searchResultsByPackageName)) - } - } - - return buildFinalSearchResult(searchResultsByKeyword, searchResultsByPackageName) - } - - /** - * Builds the final search result, combining keyword and package-specific results. - */ - private fun buildFinalSearchResult( - keywordSpecificResults: List, - packageSpecificResults: List, - ): SearchResult { - return ResultSupreme.Success( - Pair( - filterWithKeywordSearch(keywordSpecificResults, packageSpecificResults), - stores.isStoreEnabled(Source.PLAY_STORE) // Add loading indication for Play Store if enabled - ) - ) - } - - private fun isPackageName(keyword: String): Boolean { - val packageNameRegex = ".*\\..*".toRegex() // com.example - return packageNameRegex.matches(keyword) - } - - private suspend fun fetchPwaSearchResult( - query: String, - packageSpecificResults: List, - ): List { - val result = handleNetworkResult { - stores.getStore(Source.PWA)?.getSearchResults(query).orEmpty() - } - - return if (result.isSuccess()) { - result.data.orEmpty().map { updatePwa(it) } - .apply { filterWithKeywordSearch(this, packageSpecificResults) } - } else { - emptyList() - } - } - - private fun updatePwa(app: Application): Application { - applicationDataManager.updateStatus(app) - return app.apply { - this.source = Source.PWA - this.updateType() - } - } - - private suspend fun fetchOpenSourceSearchResult( - query: String, - packageSpecificResults: List, - ): List { - val result = handleNetworkResult { - stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(query).orEmpty().map { - applicationDataManager.updateStatus(it) - it.updateType() - it - } - } - - return if (result.isSuccess()) { - result.data.orEmpty().apply { - filterWithKeywordSearch(this, packageSpecificResults) - } - } else { - emptyList() - } - } - - private suspend fun searchAppsByPackageName(query: String): SearchResult { - val apps = mutableListOf() - - var playStoreApp: Application? = null - var cleanApkApp: Application? = null - - val result = handleNetworkResult { - if (stores.isStoreEnabled(Source.PLAY_STORE)) { - playStoreApp = getPlayStoreApp(query) - } - - if (stores.isStoreEnabled(Source.OPEN_SOURCE)) { - cleanApkApp = getCleanApkApp(query) - } - } - - val resultStatus = result.getResultStatus() - if (resultStatus != ResultStatus.OK) { - return ResultSupreme.create(resultStatus, Pair(emptyList(), false)) - } - - // Choose only open source app if it exists both in F-Droid and Play Store. - // Example: com.fsck.k9 - when { - cleanApkApp != null -> apps.add(cleanApkApp) - playStoreApp != null -> apps.add(playStoreApp) - } - - if (stores.isStoreEnabled(Source.PLAY_STORE)) { - apps.add(Application(isPlaceHolder = true)) - } - - return ResultSupreme.create(resultStatus, Pair(apps.mapNotNull { it }, true)) - } - - /** - * Filters and combines search results to prevent duplicates while preserving package-specific matches. - * - * Package-specific results may contain multiple variants from different sources. - * These are displayed at the top as-is. - * - * Keyword search results are deduplicated by package name and exclude any apps - * already present in package-specific results. - */ - private fun filterWithKeywordSearch( - list: List, - packageSpecificResults: List, - ): List { - val packageNames = packageSpecificResults.map { it.package_name }.toSet() - - val filteredResults = list.distinctBy { it.package_name } - .filter { it.package_name !in packageNames } - - val apps = (packageSpecificResults + filteredResults).toMutableList() - apps.removeIf { it.isPlaceHolder } - - if (stores.isStoreEnabled(Source.PLAY_STORE)) { - apps.add(Application(isPlaceHolder = true)) - } - - return apps.toList() - } - - private suspend fun getCleanApkApp(query: String): Application? { - getCleanApkSearchResult(query).let { - if (it.isSuccess() && it.data!!.package_name.isNotBlank()) { - return it.data!! - } - } - - return null - } - - private suspend fun getPlayStoreApp(query: String): Application? { - val storeRepository = stores.getStore(Source.PLAY_STORE) as? PlayStoreRepository - return storeRepository?.getAppDetailsWeb(query) - } - - /* - * Method to search cleanapk based on package name. - * This is to be only used for showing an entry in search results list. - * DO NOT use this to show info on ApplicationFragment as it will not have all the required - * information to show for an app. - * - */ - private suspend fun getCleanApkSearchResult(packageName: String): ResultSupreme { - var application = Application() - val result = handleNetworkResult { - val results = - stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(packageName).orEmpty() - - if (results.isNotEmpty() && results.size == 1) { - application = results[0] - } - } - - return ResultSupreme.create(result.getResultStatus(), application) - } - - override suspend fun getSearchSuggestions(query: String): List { - var searchSuggestions = listOf() - handleNetworkResult { - searchSuggestions = stores.getStores() - .map { it.value } - .flatMap { storeRepository -> - storeRepository.getSearchSuggestions(query) - } - } - - return searchSuggestions - } - - override suspend fun getPlayStoreSearchResults(query: String): SearchResult { - val source = Source.PLAY_STORE - - val result = handleNetworkResult { - if (!stores.isStoreEnabled(source)) { - return@handleNetworkResult Pair( - listOf(), - false - ) - } - - val searchResults = stores.getStore(source)?.getSearchResults(query) - ?: error("Couldn't get Store for Source: $source") - - val apps = searchResults.toMutableList() - if (searchResults.isNotEmpty()) { - apps.add(Application(isPlaceHolder = true)) - } - - return@handleNetworkResult Pair(apps.toList(), true) +/* + * Copyright (C) 2024-2025 e Foundation + * Copyright (C) 2024 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.data.application.search + +import android.content.Context +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.data.ResultSupreme +import foundation.e.apps.data.Stores +import foundation.e.apps.data.application.ApplicationDataManager +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.enums.ResultStatus +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.handleNetworkResult +import foundation.e.apps.data.login.exceptions.GPlayIOException +import foundation.e.apps.data.playstore.PlayStoreRepository +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class SearchRepositoryImpl @Inject constructor( + private val stores: Stores, + private val applicationDataManager: ApplicationDataManager +) : SearchRepository { + + @Inject + @ApplicationContext + lateinit var context: Context + + override suspend fun getOpenSourceSearchResults(query: String): SearchResult { + val searchResultsByPackageName = if (isPackageName(query)) { + searchAppsByPackageName(query).data?.first.orEmpty() + } else { + emptyList() + } + + val searchResultsByKeyword = buildList { + if (stores.isStoreEnabled(Source.OPEN_SOURCE)) { + addAll(fetchOpenSourceSearchResult(query, searchResultsByPackageName)) + } + if (stores.isStoreEnabled(Source.PWA)) { + addAll(fetchPwaSearchResult(query, searchResultsByPackageName)) + } + } + + return buildFinalSearchResult(searchResultsByKeyword, searchResultsByPackageName) + } + + /** + * Builds the final search result, combining keyword and package-specific results. + */ + private fun buildFinalSearchResult( + keywordSpecificResults: List, + packageSpecificResults: List, + ): SearchResult { + return ResultSupreme.Success( + Pair( + filterWithKeywordSearch(keywordSpecificResults, packageSpecificResults), + stores.isStoreEnabled(Source.PLAY_STORE) // Add loading indication for Play Store if enabled + ) + ) + } + + private fun isPackageName(keyword: String): Boolean { + val packageNameRegex = ".*\\..*".toRegex() // com.example + return packageNameRegex.matches(keyword) + } + + private suspend fun fetchPwaSearchResult( + query: String, + packageSpecificResults: List, + ): List { + val result = handleNetworkResult { + stores.getStore(Source.PWA)?.getSearchResults(query).orEmpty() + } + + return if (result.isSuccess()) { + result.data.orEmpty().map { updatePwa(it) } + .apply { filterWithKeywordSearch(this, packageSpecificResults) } + } else { + emptyList() + } + } + + private fun updatePwa(app: Application): Application { + applicationDataManager.updateStatus(app) + return app.apply { + this.source = Source.PWA + this.updateType() + } + } + + private suspend fun fetchOpenSourceSearchResult( + query: String, + packageSpecificResults: List, + ): List { + val result = handleNetworkResult { + stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(query).orEmpty().map { + applicationDataManager.updateStatus(it) + it.updateType() + it + } + } + + return if (result.isSuccess()) { + result.data.orEmpty().apply { + filterWithKeywordSearch(this, packageSpecificResults) + } + } else { + emptyList() + } + } + + private suspend fun searchAppsByPackageName(query: String): SearchResult { + val apps = mutableListOf() + + var playStoreApp: Application? = null + var cleanApkApp: Application? = null + + val result = handleNetworkResult { + if (stores.isStoreEnabled(Source.PLAY_STORE)) { + playStoreApp = getPlayStoreApp(query) + } + + if (stores.isStoreEnabled(Source.OPEN_SOURCE)) { + cleanApkApp = getCleanApkApp(query) + } + } + + val resultStatus = result.getResultStatus() + if (resultStatus != ResultStatus.OK) { + return ResultSupreme.create(resultStatus, Pair(emptyList(), false)) + } + + // Choose only open source app if it exists both in F-Droid and Play Store. + // Example: com.fsck.k9 + when { + cleanApkApp != null -> apps.add(cleanApkApp) + playStoreApp != null -> apps.add(playStoreApp) + } + + if (stores.isStoreEnabled(Source.PLAY_STORE)) { + apps.add(Application(isPlaceHolder = true)) + } + + return ResultSupreme.create(resultStatus, Pair(apps.mapNotNull { it }, true)) + } + + /** + * Filters and combines search results to prevent duplicates while preserving package-specific matches. + * + * Package-specific results may contain multiple variants from different sources. + * These are displayed at the top as-is. + * + * Keyword search results are deduplicated by package name and exclude any apps + * already present in package-specific results. + */ + private fun filterWithKeywordSearch( + list: List, + packageSpecificResults: List, + ): List { + val packageNames = packageSpecificResults.map { it.package_name }.toSet() + + val filteredResults = list.distinctBy { it.package_name } + .filter { it.package_name !in packageNames } + + val apps = (packageSpecificResults + filteredResults).toMutableList() + apps.removeIf { it.isPlaceHolder } + + if (stores.isStoreEnabled(Source.PLAY_STORE)) { + apps.add(Application(isPlaceHolder = true)) + } + + return apps.toList() + } + + private suspend fun getCleanApkApp(query: String): Application? { + getCleanApkSearchResult(query).let { + if (it.isSuccess() && it.data!!.package_name.isNotBlank()) { + return it.data!! + } + } + + return null + } + + private suspend fun getPlayStoreApp(query: String): Application? { + val storeRepository = stores.getStore(Source.PLAY_STORE) as? PlayStoreRepository + return storeRepository?.getAppDetailsWeb(query) + } + + /* + * Method to search cleanapk based on package name. + * This is to be only used for showing an entry in search results list. + * DO NOT use this to show info on ApplicationFragment as it will not have all the required + * information to show for an app. + * + */ + private suspend fun getCleanApkSearchResult(packageName: String): ResultSupreme { + var application = Application() + val result = handleNetworkResult { + val results = + stores.getStore(Source.OPEN_SOURCE)?.getSearchResults(packageName).orEmpty() + + if (results.isNotEmpty() && results.size == 1) { + application = results[0] + } + } + + return ResultSupreme.create(result.getResultStatus(), application) + } + + override suspend fun getSearchSuggestions(query: String): List { + var searchSuggestions = listOf() + handleNetworkResult { + searchSuggestions = stores.getStores() + .map { it.value } + .flatMap { storeRepository -> + storeRepository.getSearchSuggestions(query) + } + } + + return searchSuggestions + } + + override suspend fun getPlayStoreSearchResults(query: String): SearchResult { + val source = Source.PLAY_STORE + + val result = handleNetworkResult { + if (!stores.isStoreEnabled(source)) { + return@handleNetworkResult Pair( + listOf(), + false + ) + } + + val searchResults = stores.getStore(source)?.getSearchResults(query) + ?: error("Couldn't get Store for Source: $source") + + val apps = searchResults.toMutableList() + if (searchResults.isNotEmpty()) { + apps.add(Application(isPlaceHolder = true)) + } + + return@handleNetworkResult Pair(apps.toList(), true) } return if (result.isSuccess()) { diff --git a/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt b/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt index 7806e6e9c4bcfdddb9457a40547ebdff8620bcae..e8a4b5927c147805490ab4cbf5c15d5d7ec686f1 100644 --- a/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt +++ b/app/src/main/java/foundation/e/apps/data/application/utils/CategoryUtils.kt @@ -1,134 +1,134 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.application.utils - -import foundation.e.apps.R -import foundation.e.apps.data.application.data.Category -import foundation.e.apps.data.cleanapk.data.categories.Categories -import foundation.e.apps.data.enums.AppTag - -object CategoryUtils { - - private const val CATEGORY_OPEN_GAMES_ID = "game_open_games" - private const val CATEGORY_OPEN_GAMES_TITLE = "Open games" - private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&" - private const val CATEGORY_TITLE_CONJUNCTION = "and" - - private val categoryIconMap = mapOf( - "comics" to R.drawable.ic_cat_comics, - "connectivity" to R.drawable.ic_cat_connectivity, - "development" to R.drawable.ic_cat_development, - "education" to R.drawable.ic_cat_education, - "graphics" to R.drawable.ic_cat_graphics, - "internet" to R.drawable.ic_cat_internet, - "music_and_audio" to R.drawable.ic_cat_music_and_audio, - "entertainment" to R.drawable.ic_cat_entertainment, - "tools" to R.drawable.ic_cat_tools, - "security" to R.drawable.ic_cat_security, - "system" to R.drawable.ic_cat_system, - "system_apps" to R.drawable.ic_cat_system, - "communication" to R.drawable.ic_cat_communication, - "medical" to R.drawable.ic_cat_medical, - "lifestyle" to R.drawable.ic_cat_lifestyle, - "video_players" to R.drawable.ic_cat_video_players, - "video_players_and_editors" to R.drawable.ic_cat_video_players, - "events" to R.drawable.ic_cat_events, - "productivity" to R.drawable.ic_cat_productivity, - "house_and_home" to R.drawable.ic_cat_house_and_home, - "art_and_design" to R.drawable.ic_art_and_design, - "photography" to R.drawable.ic_cat_photography, - "auto_and_vehicles" to R.drawable.ic_auto_and_vehicles, - "books_and_reference" to R.drawable.ic_books_and_reference, - "social" to R.drawable.ic_cat_social, - "travel_and_local" to R.drawable.ic_cat_travel_and_local, - "beauty" to R.drawable.ic_beauty, - "personalization" to R.drawable.ic_cat_personalization, - "business" to R.drawable.ic_business, - "health_and_fitness" to R.drawable.ic_cat_health_and_fitness, - "dating" to R.drawable.ic_cat_dating, - "news_and_magazines" to R.drawable.ic_cat_news_and_magazine, - "finance" to R.drawable.ic_cat_finance, - "food_and_drink" to R.drawable.ic_cat_food_and_drink, - "shopping" to R.drawable.ic_cat_shopping, - "libraries_and_demo" to R.drawable.ic_cat_libraries_and_demo, - "sports" to R.drawable.ic_cat_sports, - "maps_and_navigation" to R.drawable.ic_cat_maps_and_navigation, - "parenting" to R.drawable.ic_cat_parenting, - "weather" to R.drawable.ic_cat_weather, - "topic/family" to R.drawable.ic_cat_family, - "game_card" to R.drawable.ic_cat_game_card, - "game_action" to R.drawable.ic_cat_game_action, - "game_board" to R.drawable.ic_cat_game_board, - "game_role_playing" to R.drawable.ic_cat_game_role_playing, - "game_arcade" to R.drawable.ic_cat_game_arcade, - "game_casino" to R.drawable.ic_cat_game_casino, - "game_adventure" to R.drawable.ic_cat_game_adventure, - "game_casual" to R.drawable.ic_cat_game_casual, - "game_puzzle" to R.drawable.ic_cat_game_puzzle, - "game_strategy" to R.drawable.ic_cat_game_strategy, - "game_educational" to R.drawable.ic_cat_game_educational, - "game_music" to R.drawable.ic_cat_game_music, - "game_racing" to R.drawable.ic_cat_game_racing, - "game_simulation" to R.drawable.ic_cat_game_simulation, - "game_sports" to R.drawable.ic_cat_game_sports, - "game_trivia" to R.drawable.ic_cat_game_trivia, - "game_word" to R.drawable.ic_cat_game_word, - "game_open_games" to R.drawable.ic_cat_open_games, - "pwa_education" to R.drawable.ic_cat_education, - "pwa_entertainment" to R.drawable.ic_cat_entertainment, - "food & drink" to R.drawable.ic_cat_food_nd_drink, - "pwa_lifestyle" to R.drawable.ic_cat_lifestyle, - "music" to R.drawable.ic_cat_game_music, - "news" to R.drawable.ic_cat_news, - "pwa_games" to R.drawable.ic_cat_game_action, - "reference" to R.drawable.ic_cat_reference, - "pwa_shopping" to R.drawable.ic_cat_shopping, - "pwa_social" to R.drawable.ic_cat_social, - "pwa_sports" to R.drawable.ic_cat_sports, - "travel" to R.drawable.ic_cat_travel, - "pwa_business" to R.drawable.ic_business, - "watch_face" to R.drawable.ic_watchface, - "android_wear" to R.drawable.ic_watch_apps, - ) - - fun provideAppsCategoryIconResource(categoryId: String) = - categoryIconMap[categoryId] ?: R.drawable.ic_cat_default - - fun getCategories( - categories: Categories, - categoryNames: List, - tag: AppTag - ) = categoryNames.map { category -> - Category( - id = category, - title = getCategoryTitle(category, categories), - drawable = provideAppsCategoryIconResource(category), - tag = tag - ) - } - - private fun getCategoryTitle(category: String, categories: Categories): String { - return if (category.contentEquals(CATEGORY_OPEN_GAMES_ID)) { - CATEGORY_OPEN_GAMES_TITLE - } else { - categories.translations.getOrDefault(category, "") - } +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.application.utils + +import foundation.e.apps.R +import foundation.e.apps.data.application.data.Category +import foundation.e.apps.data.cleanapk.data.categories.Categories +import foundation.e.apps.data.enums.AppTag + +object CategoryUtils { + + private const val CATEGORY_OPEN_GAMES_ID = "game_open_games" + private const val CATEGORY_OPEN_GAMES_TITLE = "Open games" + private const val CATEGORY_TITLE_REPLACEABLE_CONJUNCTION = "&" + private const val CATEGORY_TITLE_CONJUNCTION = "and" + + private val categoryIconMap = mapOf( + "comics" to R.drawable.ic_cat_comics, + "connectivity" to R.drawable.ic_cat_connectivity, + "development" to R.drawable.ic_cat_development, + "education" to R.drawable.ic_cat_education, + "graphics" to R.drawable.ic_cat_graphics, + "internet" to R.drawable.ic_cat_internet, + "music_and_audio" to R.drawable.ic_cat_music_and_audio, + "entertainment" to R.drawable.ic_cat_entertainment, + "tools" to R.drawable.ic_cat_tools, + "security" to R.drawable.ic_cat_security, + "system" to R.drawable.ic_cat_system, + "system_apps" to R.drawable.ic_cat_system, + "communication" to R.drawable.ic_cat_communication, + "medical" to R.drawable.ic_cat_medical, + "lifestyle" to R.drawable.ic_cat_lifestyle, + "video_players" to R.drawable.ic_cat_video_players, + "video_players_and_editors" to R.drawable.ic_cat_video_players, + "events" to R.drawable.ic_cat_events, + "productivity" to R.drawable.ic_cat_productivity, + "house_and_home" to R.drawable.ic_cat_house_and_home, + "art_and_design" to R.drawable.ic_art_and_design, + "photography" to R.drawable.ic_cat_photography, + "auto_and_vehicles" to R.drawable.ic_auto_and_vehicles, + "books_and_reference" to R.drawable.ic_books_and_reference, + "social" to R.drawable.ic_cat_social, + "travel_and_local" to R.drawable.ic_cat_travel_and_local, + "beauty" to R.drawable.ic_beauty, + "personalization" to R.drawable.ic_cat_personalization, + "business" to R.drawable.ic_business, + "health_and_fitness" to R.drawable.ic_cat_health_and_fitness, + "dating" to R.drawable.ic_cat_dating, + "news_and_magazines" to R.drawable.ic_cat_news_and_magazine, + "finance" to R.drawable.ic_cat_finance, + "food_and_drink" to R.drawable.ic_cat_food_and_drink, + "shopping" to R.drawable.ic_cat_shopping, + "libraries_and_demo" to R.drawable.ic_cat_libraries_and_demo, + "sports" to R.drawable.ic_cat_sports, + "maps_and_navigation" to R.drawable.ic_cat_maps_and_navigation, + "parenting" to R.drawable.ic_cat_parenting, + "weather" to R.drawable.ic_cat_weather, + "topic/family" to R.drawable.ic_cat_family, + "game_card" to R.drawable.ic_cat_game_card, + "game_action" to R.drawable.ic_cat_game_action, + "game_board" to R.drawable.ic_cat_game_board, + "game_role_playing" to R.drawable.ic_cat_game_role_playing, + "game_arcade" to R.drawable.ic_cat_game_arcade, + "game_casino" to R.drawable.ic_cat_game_casino, + "game_adventure" to R.drawable.ic_cat_game_adventure, + "game_casual" to R.drawable.ic_cat_game_casual, + "game_puzzle" to R.drawable.ic_cat_game_puzzle, + "game_strategy" to R.drawable.ic_cat_game_strategy, + "game_educational" to R.drawable.ic_cat_game_educational, + "game_music" to R.drawable.ic_cat_game_music, + "game_racing" to R.drawable.ic_cat_game_racing, + "game_simulation" to R.drawable.ic_cat_game_simulation, + "game_sports" to R.drawable.ic_cat_game_sports, + "game_trivia" to R.drawable.ic_cat_game_trivia, + "game_word" to R.drawable.ic_cat_game_word, + "game_open_games" to R.drawable.ic_cat_open_games, + "pwa_education" to R.drawable.ic_cat_education, + "pwa_entertainment" to R.drawable.ic_cat_entertainment, + "food & drink" to R.drawable.ic_cat_food_nd_drink, + "pwa_lifestyle" to R.drawable.ic_cat_lifestyle, + "music" to R.drawable.ic_cat_game_music, + "news" to R.drawable.ic_cat_news, + "pwa_games" to R.drawable.ic_cat_game_action, + "reference" to R.drawable.ic_cat_reference, + "pwa_shopping" to R.drawable.ic_cat_shopping, + "pwa_social" to R.drawable.ic_cat_social, + "pwa_sports" to R.drawable.ic_cat_sports, + "travel" to R.drawable.ic_cat_travel, + "pwa_business" to R.drawable.ic_business, + "watch_face" to R.drawable.ic_watchface, + "android_wear" to R.drawable.ic_watch_apps, + ) + + fun provideAppsCategoryIconResource(categoryId: String) = + categoryIconMap[categoryId] ?: R.drawable.ic_cat_default + + fun getCategories( + categories: Categories, + categoryNames: List, + tag: AppTag + ) = categoryNames.map { category -> + Category( + id = category, + title = getCategoryTitle(category, categories), + drawable = provideAppsCategoryIconResource(category), + tag = tag + ) + } + + private fun getCategoryTitle(category: String, categories: Categories): String { + return if (category.contentEquals(CATEGORY_OPEN_GAMES_ID)) { + CATEGORY_OPEN_GAMES_TITLE + } else { + categories.translations.getOrDefault(category, "") + } } fun getCategoryIconName(category: Category): String { @@ -141,15 +141,15 @@ object CategoryUtils { if (categoryTitle.contains(CATEGORY_TITLE_REPLACEABLE_CONJUNCTION)) { categoryTitle = categoryTitle.replace( CATEGORY_TITLE_REPLACEABLE_CONJUNCTION, - CATEGORY_TITLE_CONJUNCTION - ) - } - - categoryTitle = categoryTitle.replace(' ', '_') - return categoryTitle.lowercase() - } -} - -enum class CategoryType { - APPLICATION, GAMES -} + CATEGORY_TITLE_CONJUNCTION + ) + } + + categoryTitle = categoryTitle.replace(' ', '_') + return categoryTitle.lowercase() + } +} + +enum class CategoryType { + APPLICATION, GAMES +} diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkRetrofit.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkRetrofit.kt index 281a67d0238fe93b255a4e70e3dcc57f3b95a51a..c451aadbc6f095ec40dd50532ca8ebbef2367dbc 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkRetrofit.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/CleanApkRetrofit.kt @@ -1,107 +1,107 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk - -import foundation.e.apps.data.cleanapk.data.app.CleanApkApplication -import foundation.e.apps.data.cleanapk.data.categories.Categories -import foundation.e.apps.data.cleanapk.data.download.Download -import foundation.e.apps.data.cleanapk.data.home.HomeScreenResponse -import foundation.e.apps.data.cleanapk.data.search.Search -import retrofit2.Response -import retrofit2.http.GET -import retrofit2.http.Query - -interface CleanApkRetrofit { - - companion object { - // API endpoints - const val BASE_URL = "https://api.cleanapk.org/v2/" - const val ASSET_URL = "https://api.cleanapk.org/v2/media/" - - // Application sources - const val APP_SOURCE_FOSS = "open" - const val APP_SOURCE_ANY = "any" - - // Application types - const val APP_TYPE_NATIVE = "native" - const val APP_TYPE_PWA = "pwa" - const val APP_TYPE_ANY = "any" - } - - @GET("apps?action=list_home") - suspend fun getHomeScreenData( - @Query("type") type: String = APP_TYPE_ANY, - @Query("source") source: String = APP_SOURCE_ANY, - @Query("architecture") architecture: String? = null, - ): Response - - // TODO: Reminder that this function is for search App and PWA both - @GET("apps?action=app_detail") - suspend fun getAppOrPWADetailsByID( - @Query("id") id: String, - @Query("architectures") architectures: List? = null, - @Query("type") type: String? = null, - ): Response - - @Suppress("LongParameterList") - // Retrofit endpoint mirrors CleanAPK query parameters; keeping them explicit preserves the API contract - @GET("apps?action=search") - suspend fun searchApps( - @Query("keyword") keyword: String, - @Query("source") source: String = APP_SOURCE_FOSS, - @Query("type") type: String = APP_TYPE_ANY, - @Query("nres") pageSize: Int = 20, - @Query("page") page: Int = 1, - @Query("by") by: String? = null, - @Query("architectures") architectures: List? = null, - ): Response - - @Suppress("LongParameterList") - // Endpoint requires explicit query parts; grouping them would hide required inputs - @GET("apps?action=list_apps") - suspend fun listApps( - @Query("category") category: String, - @Query("source") source: String = APP_SOURCE_FOSS, - @Query("type") type: String = APP_TYPE_ANY, - @Query("nres") nres: Int = 20, - @Query("page") page: Int = 1, - @Query("architectures") architectures: List? = null, - ): Response - - @GET("apps?action=list_apps") - suspend fun checkAvailablePackages( - @Query("package_names[]") packages: List, - @Query("source") source: String = "open", - @Query("architectures") architectures: List? = null, - ): Response - - @GET("apps?action=download") - suspend fun getDownloadInfo( - @Query("app_id") id: String, - @Query("version") version: String? = null, - @Query("architecture") architecture: String? = null - ): Response - - @GET("apps?action=list_cat") - suspend fun getCategoriesList( - @Query("type") type: String = APP_TYPE_ANY, - @Query("source") source: String = APP_SOURCE_ANY, - @Query("architectures") architectures: List? = null, - ): Response -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk + +import foundation.e.apps.data.cleanapk.data.app.CleanApkApplication +import foundation.e.apps.data.cleanapk.data.categories.Categories +import foundation.e.apps.data.cleanapk.data.download.Download +import foundation.e.apps.data.cleanapk.data.home.HomeScreenResponse +import foundation.e.apps.data.cleanapk.data.search.Search +import retrofit2.Response +import retrofit2.http.GET +import retrofit2.http.Query + +interface CleanApkRetrofit { + + companion object { + // API endpoints + const val BASE_URL = "https://api.cleanapk.org/v2/" + const val ASSET_URL = "https://api.cleanapk.org/v2/media/" + + // Application sources + const val APP_SOURCE_FOSS = "open" + const val APP_SOURCE_ANY = "any" + + // Application types + const val APP_TYPE_NATIVE = "native" + const val APP_TYPE_PWA = "pwa" + const val APP_TYPE_ANY = "any" + } + + @GET("apps?action=list_home") + suspend fun getHomeScreenData( + @Query("type") type: String = APP_TYPE_ANY, + @Query("source") source: String = APP_SOURCE_ANY, + @Query("architecture") architecture: String? = null, + ): Response + + // TODO: Reminder that this function is for search App and PWA both + @GET("apps?action=app_detail") + suspend fun getAppOrPWADetailsByID( + @Query("id") id: String, + @Query("architectures") architectures: List? = null, + @Query("type") type: String? = null, + ): Response + + @Suppress("LongParameterList") + // Retrofit endpoint mirrors CleanAPK query parameters; keeping them explicit preserves the API contract + @GET("apps?action=search") + suspend fun searchApps( + @Query("keyword") keyword: String, + @Query("source") source: String = APP_SOURCE_FOSS, + @Query("type") type: String = APP_TYPE_ANY, + @Query("nres") pageSize: Int = 20, + @Query("page") page: Int = 1, + @Query("by") by: String? = null, + @Query("architectures") architectures: List? = null, + ): Response + + @Suppress("LongParameterList") + // Endpoint requires explicit query parts; grouping them would hide required inputs + @GET("apps?action=list_apps") + suspend fun listApps( + @Query("category") category: String, + @Query("source") source: String = APP_SOURCE_FOSS, + @Query("type") type: String = APP_TYPE_ANY, + @Query("nres") nres: Int = 20, + @Query("page") page: Int = 1, + @Query("architectures") architectures: List? = null, + ): Response + + @GET("apps?action=list_apps") + suspend fun checkAvailablePackages( + @Query("package_names[]") packages: List, + @Query("source") source: String = "open", + @Query("architectures") architectures: List? = null, + ): Response + + @GET("apps?action=download") + suspend fun getDownloadInfo( + @Query("app_id") id: String, + @Query("version") version: String? = null, + @Query("architecture") architecture: String? = null + ): Response + + @GET("apps?action=list_cat") + suspend fun getCategoriesList( + @Query("type") type: String = APP_TYPE_ANY, + @Query("source") source: String = APP_SOURCE_ANY, + @Query("architectures") architectures: List? = null, + ): Response +} diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/app/CleanApkApplication.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/app/CleanApkApplication.kt index 4ba0b0c7b5a79ad3296059d33a9b835bd1b2754e..7d4dce143363da852676ede060dc9d73180d51f5 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/app/CleanApkApplication.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/app/CleanApkApplication.kt @@ -1,26 +1,26 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk.data.app - -import foundation.e.apps.data.application.data.Application - -data class CleanApkApplication( - val app: Application = Application(), - val success: Boolean = false -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk.data.app + +import foundation.e.apps.data.application.data.Application + +data class CleanApkApplication( + val app: Application = Application(), + val success: Boolean = false +) diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/categories/Categories.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/categories/Categories.kt index 6fac8eefc7ec2699f282b861ecd5b05e5e1a7801..8512691df2368d75330e1cb0a5f5e00ac54784d5 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/categories/Categories.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/categories/Categories.kt @@ -1,26 +1,26 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk.data.categories - -data class Categories( - val apps: List = emptyList(), - val games: List = emptyList(), - val success: Boolean = false, - val translations: Map = emptyMap() -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk.data.categories + +data class Categories( + val apps: List = emptyList(), + val games: List = emptyList(), + val success: Boolean = false, + val translations: Map = emptyMap() +) diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/Download.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/Download.kt index 0776bf058e3748add07a75d3e0631223b11fe28a..48e81e5cfa5fb2ed12e0b76fe20a7ed7ee16772b 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/Download.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/Download.kt @@ -1,24 +1,24 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk.data.download - -data class Download( - val download_data: DownloadData = DownloadData(), - val success: Boolean = false -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk.data.download + +data class Download( + val download_data: DownloadData = DownloadData(), + val success: Boolean = false +) diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/DownloadData.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/DownloadData.kt index 2ee6254495b257a76385179a346a01f79ce648af..a23858994f8e2f221b1c56955fbaf495afd86093 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/DownloadData.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/download/DownloadData.kt @@ -1,24 +1,24 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk.data.download - -data class DownloadData( - val download_link: String = String(), - val signature: String = String() -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk.data.download + +data class DownloadData( + val download_link: String = String(), + val signature: String = String() +) diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/CleanApkHome.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/CleanApkHome.kt index de40e01f5af76c34487454c6cc73d636037818ae..790ff5bfea69c596a06bc5fbfb9ef2f0994fc3bf 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/CleanApkHome.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/CleanApkHome.kt @@ -1,29 +1,29 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk.data.home - -import foundation.e.apps.data.application.data.Application - -data class CleanApkHome( - val top_updated_apps: List = emptyList(), - val top_updated_games: List = emptyList(), - val popular_apps: List = emptyList(), - val popular_games: List = emptyList(), - val discover: List = emptyList(), -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk.data.home + +import foundation.e.apps.data.application.data.Application + +data class CleanApkHome( + val top_updated_apps: List = emptyList(), + val top_updated_games: List = emptyList(), + val popular_apps: List = emptyList(), + val popular_games: List = emptyList(), + val discover: List = emptyList(), +) diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/HomeScreenResponse.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/HomeScreenResponse.kt index 9c64a8a752248048f4340bdc6d7405f314facaea..52758c1ce7d8bea5009146b084b093bc751bacea 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/HomeScreenResponse.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/home/HomeScreenResponse.kt @@ -1,24 +1,24 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.cleanapk.data.home - -data class HomeScreenResponse( - val home: CleanApkHome = CleanApkHome(), - val success: Boolean = false -) +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.cleanapk.data.home + +data class HomeScreenResponse( + val home: CleanApkHome = CleanApkHome(), + val success: Boolean = false +) diff --git a/app/src/main/java/foundation/e/apps/data/cleanapk/data/search/Search.kt b/app/src/main/java/foundation/e/apps/data/cleanapk/data/search/Search.kt index be66b5eac968fc33ea82a7ee2cbcea2d99af9eb6..527066f9e220a40f9a06c6f2334ec48a027df4a8 100644 --- a/app/src/main/java/foundation/e/apps/data/cleanapk/data/search/Search.kt +++ b/app/src/main/java/foundation/e/apps/data/cleanapk/data/search/Search.kt @@ -1,29 +1,29 @@ -/* - * Copyright (C) 2021-2026 e Foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.data.cleanapk.data.search - -import com.google.gson.annotations.SerializedName -import foundation.e.apps.data.application.data.Application - -data class Search( - val apps: List = emptyList(), - val numberOfResults: Int = -1, - @SerializedName(value = "pages") val numberOfPages: Int = 0, - val success: Boolean = false -) +/* + * Copyright (C) 2021-2026 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.data.cleanapk.data.search + +import com.google.gson.annotations.SerializedName +import foundation.e.apps.data.application.data.Application + +data class Search( + val apps: List = emptyList(), + val numberOfResults: Int = -1, + @SerializedName(value = "pages") val numberOfPages: Int = 0, + val success: Boolean = false +) diff --git a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt b/app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt index 70b0f890e033816ffe6c1705941dc57873c0e386..3c860e005a3327a26a957e2d9c2d983c11cb7619 100644 --- a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt +++ b/app/src/main/java/foundation/e/apps/data/database/install/AppInstallConverter.kt @@ -1,43 +1,43 @@ -package foundation.e.apps.data.database.install - -import androidx.room.TypeConverter -import com.aurora.gplayapi.data.models.ContentRating -import com.aurora.gplayapi.data.models.PlayFile -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken - -class AppInstallConverter { - - private val gson = Gson() - - @TypeConverter - fun listToJsonString(value: List): String = gson.toJson(value) - - @TypeConverter - fun jsonStringToList(value: String) = - gson.fromJson(value, Array::class.java).toMutableList() - - @TypeConverter - fun listToJsonLong(value: MutableMap): String = gson.toJson(value) - - @TypeConverter - fun jsonLongToList(value: String): MutableMap = - gson.fromJson(value, object : TypeToken>() {}.type) - - @TypeConverter - fun filesToJsonString(value: List): String = gson.toJson(value) - - @TypeConverter - fun jsonStringToFiles(value: String) = - gson.fromJson(value, Array::class.java).toMutableList() - - @TypeConverter - fun fromContentRating(contentRating: ContentRating): String { - return gson.toJson(contentRating) - } - - @TypeConverter - fun toContentRating(name: String): ContentRating { - return gson.fromJson(name, ContentRating::class.java) - } -} +package foundation.e.apps.data.database.install + +import androidx.room.TypeConverter +import com.aurora.gplayapi.data.models.ContentRating +import com.aurora.gplayapi.data.models.PlayFile +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +class AppInstallConverter { + + private val gson = Gson() + + @TypeConverter + fun listToJsonString(value: List): String = gson.toJson(value) + + @TypeConverter + fun jsonStringToList(value: String) = + gson.fromJson(value, Array::class.java).toMutableList() + + @TypeConverter + fun listToJsonLong(value: MutableMap): String = gson.toJson(value) + + @TypeConverter + fun jsonLongToList(value: String): MutableMap = + gson.fromJson(value, object : TypeToken>() {}.type) + + @TypeConverter + fun filesToJsonString(value: List): String = gson.toJson(value) + + @TypeConverter + fun jsonStringToFiles(value: String) = + gson.fromJson(value, Array::class.java).toMutableList() + + @TypeConverter + fun fromContentRating(contentRating: ContentRating): String { + return gson.toJson(contentRating) + } + + @TypeConverter + fun toContentRating(name: String): ContentRating { + return gson.fromJson(name, ContentRating::class.java) + } +} diff --git a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt b/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt index 4928227462824e73374e7d12ca6342b6bf6a9e59..011d4af7cdf127c3800263c102a9fdaf98fd3f3d 100644 --- a/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt +++ b/app/src/main/java/foundation/e/apps/data/database/install/AppInstallDatabase.kt @@ -1,33 +1,33 @@ -package foundation.e.apps.data.database.install - -import android.content.Context -import androidx.room.Database -import androidx.room.Room -import androidx.room.RoomDatabase -import androidx.room.TypeConverters -import foundation.e.apps.data.database.AppDatabase -import foundation.e.apps.data.install.AppInstallDAO -import foundation.e.apps.data.install.models.AppInstall - -@Database(entities = [AppInstall::class], version = 6, exportSchema = false) -@TypeConverters(AppInstallConverter::class) -abstract class AppInstallDatabase : RoomDatabase() { - abstract fun fusedDownloadDao(): AppInstallDAO - - companion object { - private lateinit var INSTANCE: AppInstallDatabase - private const val DATABASE_NAME = "fused_database" - - fun getInstance(context: Context): AppInstallDatabase { - if (!Companion::INSTANCE.isInitialized) { - synchronized(AppDatabase::class) { - INSTANCE = - Room.databaseBuilder(context, AppInstallDatabase::class.java, DATABASE_NAME) - .fallbackToDestructiveMigration() - .build() - } - } - return INSTANCE - } - } -} +package foundation.e.apps.data.database.install + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters +import foundation.e.apps.data.database.AppDatabase +import foundation.e.apps.data.install.AppInstallDAO +import foundation.e.apps.data.install.models.AppInstall + +@Database(entities = [AppInstall::class], version = 6, exportSchema = false) +@TypeConverters(AppInstallConverter::class) +abstract class AppInstallDatabase : RoomDatabase() { + abstract fun fusedDownloadDao(): AppInstallDAO + + companion object { + private lateinit var INSTANCE: AppInstallDatabase + private const val DATABASE_NAME = "fused_database" + + fun getInstance(context: Context): AppInstallDatabase { + if (!Companion::INSTANCE.isInitialized) { + synchronized(AppDatabase::class) { + INSTANCE = + Room.databaseBuilder(context, AppInstallDatabase::class.java, DATABASE_NAME) + .fallbackToDestructiveMigration() + .build() + } + } + return INSTANCE + } + } +} diff --git a/app/src/main/java/foundation/e/apps/data/enums/Status.kt b/app/src/main/java/foundation/e/apps/data/enums/Status.kt index f16de5cc8cba7c61f5babc844b0d20bf74dd5e77..0e4406298464bce876e0693e69ebffa215ad1d5d 100644 --- a/app/src/main/java/foundation/e/apps/data/enums/Status.kt +++ b/app/src/main/java/foundation/e/apps/data/enums/Status.kt @@ -1,42 +1,42 @@ -/* - * Copyright ECORP SAS 2022 - * Apps Quickly and easily install Android apps onto your device! - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.enums - -enum class Status { - INSTALLED, - UPDATABLE, - INSTALLING, - DOWNLOADING, - DOWNLOADED, - UNAVAILABLE, - QUEUED, - BLOCKED, - INSTALLATION_ISSUE, - AWAITING, - PURCHASE_NEEDED; - - companion object { - val downloadStatuses = setOf( - QUEUED, - AWAITING, - DOWNLOADING, - DOWNLOADED - ) - } -} +/* + * Copyright ECORP SAS 2022 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.enums + +enum class Status { + INSTALLED, + UPDATABLE, + INSTALLING, + DOWNLOADING, + DOWNLOADED, + UNAVAILABLE, + QUEUED, + BLOCKED, + INSTALLATION_ISSUE, + AWAITING, + PURCHASE_NEEDED; + + companion object { + val downloadStatuses = setOf( + QUEUED, + AWAITING, + DOWNLOADING, + DOWNLOADED + ) + } +} diff --git a/app/src/main/java/foundation/e/apps/data/enums/Type.kt b/app/src/main/java/foundation/e/apps/data/enums/Type.kt index ccb670d502a40cfb582abcb4c589ae1dda2c1a58..3bab81752713199eb42b0cb7cb10ba6e57e4af82 100644 --- a/app/src/main/java/foundation/e/apps/data/enums/Type.kt +++ b/app/src/main/java/foundation/e/apps/data/enums/Type.kt @@ -1,6 +1,6 @@ -package foundation.e.apps.data.enums - -enum class Type { - NATIVE, - PWA -} +package foundation.e.apps.data.enums + +enum class Type { + NATIVE, + PWA +} diff --git a/app/src/main/java/foundation/e/apps/data/enums/User.kt b/app/src/main/java/foundation/e/apps/data/enums/User.kt index f66d98e2ba19f3081e48aec9eeec3952c6ce494c..b5dc3df6726a615f30d82ddc529bf1c3b1cba335 100644 --- a/app/src/main/java/foundation/e/apps/data/enums/User.kt +++ b/app/src/main/java/foundation/e/apps/data/enums/User.kt @@ -1,5 +1,5 @@ -package foundation.e.apps.data.enums - +package foundation.e.apps.data.enums + enum class User { NO_GOOGLE, ANONYMOUS, diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt index f008b96b3ef464de9f3487a77ebff99af7982031..a435d7428d0ae6c4e9c2d876e1f9465bf22091f9 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallDAO.kt @@ -1,38 +1,38 @@ -package foundation.e.apps.data.install - -import androidx.lifecycle.LiveData -import androidx.room.Dao -import androidx.room.Delete -import androidx.room.Insert -import androidx.room.OnConflictStrategy -import androidx.room.Query -import androidx.room.Update -import foundation.e.apps.data.install.models.AppInstall - -@Dao -interface AppInstallDAO { - - @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun addDownload(appInstall: AppInstall) - - @Query("SELECT * FROM fuseddownload") - fun getDownloadLiveList(): LiveData> - - @Query("SELECT * FROM fuseddownload") - suspend fun getDownloadList(): List - - @Query("SELECT * FROM fuseddownload where id = :id") - suspend fun getDownloadById(id: String): AppInstall? - - @Query("SELECT * FROM fuseddownload where id = :id") - fun getDownloadFlowById(id: String): LiveData - - @Query("SELECT * FROM fuseddownload where status = 'INSTALLING'") - suspend fun getItemInInstallation(): List - - @Update - suspend fun updateDownload(appInstall: AppInstall) - - @Delete - suspend fun deleteDownload(appInstall: AppInstall) -} +package foundation.e.apps.data.install + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.Update +import foundation.e.apps.data.install.models.AppInstall + +@Dao +interface AppInstallDAO { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun addDownload(appInstall: AppInstall) + + @Query("SELECT * FROM fuseddownload") + fun getDownloadLiveList(): LiveData> + + @Query("SELECT * FROM fuseddownload") + suspend fun getDownloadList(): List + + @Query("SELECT * FROM fuseddownload where id = :id") + suspend fun getDownloadById(id: String): AppInstall? + + @Query("SELECT * FROM fuseddownload where id = :id") + fun getDownloadFlowById(id: String): LiveData + + @Query("SELECT * FROM fuseddownload where status = 'INSTALLING'") + suspend fun getItemInInstallation(): List + + @Update + suspend fun updateDownload(appInstall: AppInstall) + + @Delete + suspend fun deleteDownload(appInstall: AppInstall) +} diff --git a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt b/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt index 30a1d0e6cfc724a9f9029de689a3d8e39f78faa6..ddebd3709625f97f3457cb88eb63ef0263a70563 100644 --- a/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt +++ b/app/src/main/java/foundation/e/apps/data/install/AppInstallRepository.kt @@ -1,58 +1,58 @@ -package foundation.e.apps.data.install - -import androidx.lifecycle.LiveData -import androidx.lifecycle.asFlow -import foundation.e.apps.OpenForTesting -import foundation.e.apps.data.install.models.AppInstall -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -@OpenForTesting -class AppInstallRepository @Inject constructor( - private val appInstallDAO: AppInstallDAO -) { - - private val mutex = Mutex() - - suspend fun addDownload(appInstall: AppInstall) { - mutex.withLock { - return appInstallDAO.addDownload(appInstall) - } - } - - suspend fun getDownloadList(): List { - mutex.withLock { - return appInstallDAO.getDownloadList() - } - } - - fun getDownloadLiveList(): LiveData> { - return appInstallDAO.getDownloadLiveList() - } - - suspend fun updateDownload(appInstall: AppInstall) { - mutex.withLock { - appInstallDAO.updateDownload(appInstall) - } - } - - suspend fun deleteDownload(appInstall: AppInstall) { - mutex.withLock { - return appInstallDAO.deleteDownload(appInstall) - } - } - - suspend fun getDownloadById(id: String): AppInstall? { - mutex.withLock { - return appInstallDAO.getDownloadById(id) - } - } - - fun getDownloadFlowById(id: String): Flow { - return appInstallDAO.getDownloadFlowById(id).asFlow() - } -} +package foundation.e.apps.data.install + +import androidx.lifecycle.LiveData +import androidx.lifecycle.asFlow +import foundation.e.apps.OpenForTesting +import foundation.e.apps.data.install.models.AppInstall +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +@OpenForTesting +class AppInstallRepository @Inject constructor( + private val appInstallDAO: AppInstallDAO +) { + + private val mutex = Mutex() + + suspend fun addDownload(appInstall: AppInstall) { + mutex.withLock { + return appInstallDAO.addDownload(appInstall) + } + } + + suspend fun getDownloadList(): List { + mutex.withLock { + return appInstallDAO.getDownloadList() + } + } + + fun getDownloadLiveList(): LiveData> { + return appInstallDAO.getDownloadLiveList() + } + + suspend fun updateDownload(appInstall: AppInstall) { + mutex.withLock { + appInstallDAO.updateDownload(appInstall) + } + } + + suspend fun deleteDownload(appInstall: AppInstall) { + mutex.withLock { + return appInstallDAO.deleteDownload(appInstall) + } + } + + suspend fun getDownloadById(id: String): AppInstall? { + mutex.withLock { + return appInstallDAO.getDownloadById(id) + } + } + + fun getDownloadFlowById(id: String): Flow { + return appInstallDAO.getDownloadFlowById(id).asFlow() + } +} diff --git a/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt b/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt index a8b48a9ec1503d4f9d9e4e173a4a9e8d9a3b3bd8..01c5828a7d2bc37e991b231019ab7abbfcb74a66 100644 --- a/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt +++ b/app/src/main/java/foundation/e/apps/data/install/models/AppInstall.kt @@ -1,53 +1,53 @@ -package foundation.e.apps.data.install.models - -import androidx.room.Entity -import androidx.room.Ignore -import androidx.room.PrimaryKey -import com.aurora.gplayapi.data.models.ContentRating -import com.aurora.gplayapi.data.models.PlayFile -import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.Source -import foundation.e.apps.data.enums.Status -import foundation.e.apps.data.enums.Type - -@Entity(tableName = "FusedDownload") -data class AppInstall( - @PrimaryKey val id: String = String(), - val source: Source = Source.PLAY_STORE, - var status: Status = Status.UNAVAILABLE, - val name: String = String(), - val packageName: String = String(), - var downloadURLList: MutableList = mutableListOf(), - var downloadIdMap: MutableMap = mutableMapOf(), - val orgStatus: Status = Status.UNAVAILABLE, - val type: Type = Type.NATIVE, - val iconImageUrl: String = String(), - val versionCode: Long = 1, - val offerType: Int = -1, - val isFree: Boolean = true, - var appSize: Long = 0, - var files: List = mutableListOf(), - var signature: String = String(), - var contentRating: ContentRating = ContentRating() -) { - @Ignore - private val installingStatusList = listOf( - Status.AWAITING, - Status.DOWNLOADING, - Status.DOWNLOADED, - Status.INSTALLING - ) - - fun isAppInstalling() = installingStatusList.contains(status) - - fun isAwaiting() = status == Status.AWAITING - - fun areFilesDownloaded() = downloadIdMap.isNotEmpty() && !downloadIdMap.values.contains(false) - - fun getAppIconUrl(): String { - if (this.source == Source.PLAY_STORE || this.source == Source.PWA) { - return "${CleanApkRetrofit.ASSET_URL}${this.iconImageUrl}" - } - return this.iconImageUrl - } -} +package foundation.e.apps.data.install.models + +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import com.aurora.gplayapi.data.models.ContentRating +import com.aurora.gplayapi.data.models.PlayFile +import foundation.e.apps.data.cleanapk.CleanApkRetrofit +import foundation.e.apps.data.enums.Source +import foundation.e.apps.data.enums.Status +import foundation.e.apps.data.enums.Type + +@Entity(tableName = "FusedDownload") +data class AppInstall( + @PrimaryKey val id: String = String(), + val source: Source = Source.PLAY_STORE, + var status: Status = Status.UNAVAILABLE, + val name: String = String(), + val packageName: String = String(), + var downloadURLList: MutableList = mutableListOf(), + var downloadIdMap: MutableMap = mutableMapOf(), + val orgStatus: Status = Status.UNAVAILABLE, + val type: Type = Type.NATIVE, + val iconImageUrl: String = String(), + val versionCode: Long = 1, + val offerType: Int = -1, + val isFree: Boolean = true, + var appSize: Long = 0, + var files: List = mutableListOf(), + var signature: String = String(), + var contentRating: ContentRating = ContentRating() +) { + @Ignore + private val installingStatusList = listOf( + Status.AWAITING, + Status.DOWNLOADING, + Status.DOWNLOADED, + Status.INSTALLING + ) + + fun isAppInstalling() = installingStatusList.contains(status) + + fun isAwaiting() = status == Status.AWAITING + + fun areFilesDownloaded() = downloadIdMap.isNotEmpty() && !downloadIdMap.values.contains(false) + + fun getAppIconUrl(): String { + if (this.source == Source.PLAY_STORE || this.source == Source.PWA) { + return "${CleanApkRetrofit.ASSET_URL}${this.iconImageUrl}" + } + return this.iconImageUrl + } +} diff --git a/app/src/main/java/foundation/e/apps/data/playstore/utils/EglExtensionProvider.kt b/app/src/main/java/foundation/e/apps/data/playstore/utils/EglExtensionProvider.kt index 992fe95e1ce9200f6c2e0d8bcde644571243e1bc..9dd9ed2bdf6a41adb825cc402ec55c9a8b615f6a 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/utils/EglExtensionProvider.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/utils/EglExtensionProvider.kt @@ -1,31 +1,31 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021, Rahul Kumar Patel - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.playstore.utils - -import android.opengl.GLES10 -import android.text.TextUtils -import javax.microedition.khronos.egl.EGL10 -import javax.microedition.khronos.egl.EGLConfig -import javax.microedition.khronos.egl.EGLContext -import javax.microedition.khronos.egl.EGLDisplay - +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021, Rahul Kumar Patel + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.playstore.utils + +import android.opengl.GLES10 +import android.text.TextUtils +import javax.microedition.khronos.egl.EGL10 +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.egl.EGLContext +import javax.microedition.khronos.egl.EGLDisplay + object EglExtensionProvider { private const val EGL_CONTEXT_CLIENT_VERSION = 0x3098 @@ -33,13 +33,13 @@ object EglExtensionProvider { val eglExtensions: List get() { val glExtensions: MutableSet = HashSet() - val egl10 = EGLContext.getEGL() as EGL10 - val display = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) - egl10.eglInitialize(display, IntArray(2)) - val cf = IntArray(1) - if (egl10.eglGetConfigs(display, null, 0, cf)) { - val configs = arrayOfNulls(cf[0]) - if (egl10.eglGetConfigs(display, configs, cf[0], cf)) { + val egl10 = EGLContext.getEGL() as EGL10 + val display = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) + egl10.eglInitialize(display, IntArray(2)) + val cf = IntArray(1) + if (egl10.eglGetConfigs(display, null, 0, cf)) { + val configs = arrayOfNulls(cf[0]) + if (egl10.eglGetConfigs(display, configs, cf[0], cf)) { val a1 = intArrayOf( EGL10.EGL_WIDTH, EGL10.EGL_PBUFFER_BIT, @@ -81,33 +81,33 @@ object EglExtensionProvider { display, configs[i], a1, - a2, - glExtensions - ) - } - } - } - } - } - } - egl10.eglTerminate(display) - val extensionsList: MutableList = ArrayList(glExtensions) - extensionsList.sort() - return extensionsList - } - - private fun addExtensionsForConfig( - egl10: EGL10, - eglDisplay: EGLDisplay, - eglConfig: EGLConfig?, - ai: IntArray, - ai1: IntArray?, - set: MutableSet - ) { - val eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, ai1) - if (eglContext === EGL10.EGL_NO_CONTEXT) { - return - } + a2, + glExtensions + ) + } + } + } + } + } + } + egl10.eglTerminate(display) + val extensionsList: MutableList = ArrayList(glExtensions) + extensionsList.sort() + return extensionsList + } + + private fun addExtensionsForConfig( + egl10: EGL10, + eglDisplay: EGLDisplay, + eglConfig: EGLConfig?, + ai: IntArray, + ai1: IntArray?, + set: MutableSet + ) { + val eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, ai1) + if (eglContext === EGL10.EGL_NO_CONTEXT) { + return + } val eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, eglConfig, ai) if (eglSurface === EGL10.EGL_NO_SURFACE) { egl10.eglDestroyContext(eglDisplay, eglContext) diff --git a/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt b/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt index 509af70343a54d6491e2932814fcc6b9404c7efb..787071b9daff189adc7fb55097e07d84f5120a37 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeDeviceInfoProviderModule.kt @@ -1,24 +1,24 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021, Rahul Kumar Patel - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.playstore.utils - +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021, Rahul Kumar Patel + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.playstore.utils + import android.app.ActivityManager import android.content.Context import android.content.Context.ACTIVITY_SERVICE @@ -33,83 +33,83 @@ import dagger.hilt.components.SingletonComponent import timber.log.Timber import java.util.Properties import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object NativeDeviceInfoProviderModule { - - @Singleton - @Provides - fun provideNativeDeviceProperties( - @ApplicationContext context: Context, - ): Properties { - val properties = Properties().apply { - setBuildProperties() - setConfigProperties(context) - setDisplayMetrics(context) - setGLExtensions(context) - setGoogleProperties(context) - setMiscProperties() - } - return properties - } - - private fun Properties.setMiscProperties() { - setProperty("Roaming", "mobile-notroaming") - setProperty("TimeZone", "UTC-10") - setProperty("CellOperator", "310") - setProperty("SimOperator", "38") - } - - private fun Properties.setGoogleProperties(context: Context) { - val gsfVersionProvider = NativeGsfVersionProvider(context) - setProperty("Client", "android-google") - setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}") - setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode()}") - setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString()) - } - - private fun Properties.setGLExtensions(context: Context) { - val activityManager = - context.getSystemService(ACTIVITY_SERVICE) as ActivityManager - setProperty( - "GL.Version", - activityManager.deviceConfigurationInfo.reqGlEsVersion.toString() - ) - setProperty( - "GL.Extensions", - EglExtensionProvider.eglExtensions.joinToString(separator = ",") - ) - } - - private fun Properties.setDisplayMetrics(context: Context) { - val metrics = context.resources.displayMetrics - setProperty("Screen.Density", "${metrics.densityDpi}") - setProperty("Screen.Width", "${metrics.widthPixels}") - setProperty("Screen.Height", "${metrics.heightPixels}") - setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ",")) - setProperty("Features", getFeatures(context).joinToString(separator = ",")) - setProperty("Locales", getLocales(context).joinToString(separator = ",")) - setProperty( - "SharedLibraries", - getSharedLibraries(context).joinToString(separator = ",") - ) - } - - private fun Properties.setConfigProperties(context: Context) { - val config = context.resources.configuration - setProperty("TouchScreen", "${config.touchscreen}") - setProperty("Keyboard", "${config.keyboard}") - setProperty("Navigation", "${config.navigation}") + +@Module +@InstallIn(SingletonComponent::class) +object NativeDeviceInfoProviderModule { + + @Singleton + @Provides + fun provideNativeDeviceProperties( + @ApplicationContext context: Context, + ): Properties { + val properties = Properties().apply { + setBuildProperties() + setConfigProperties(context) + setDisplayMetrics(context) + setGLExtensions(context) + setGoogleProperties(context) + setMiscProperties() + } + return properties + } + + private fun Properties.setMiscProperties() { + setProperty("Roaming", "mobile-notroaming") + setProperty("TimeZone", "UTC-10") + setProperty("CellOperator", "310") + setProperty("SimOperator", "38") + } + + private fun Properties.setGoogleProperties(context: Context) { + val gsfVersionProvider = NativeGsfVersionProvider(context) + setProperty("Client", "android-google") + setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}") + setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode()}") + setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString()) + } + + private fun Properties.setGLExtensions(context: Context) { + val activityManager = + context.getSystemService(ACTIVITY_SERVICE) as ActivityManager + setProperty( + "GL.Version", + activityManager.deviceConfigurationInfo.reqGlEsVersion.toString() + ) + setProperty( + "GL.Extensions", + EglExtensionProvider.eglExtensions.joinToString(separator = ",") + ) + } + + private fun Properties.setDisplayMetrics(context: Context) { + val metrics = context.resources.displayMetrics + setProperty("Screen.Density", "${metrics.densityDpi}") + setProperty("Screen.Width", "${metrics.widthPixels}") + setProperty("Screen.Height", "${metrics.heightPixels}") + setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ",")) + setProperty("Features", getFeatures(context).joinToString(separator = ",")) + setProperty("Locales", getLocales(context).joinToString(separator = ",")) + setProperty( + "SharedLibraries", + getSharedLibraries(context).joinToString(separator = ",") + ) + } + + private fun Properties.setConfigProperties(context: Context) { + val config = context.resources.configuration + setProperty("TouchScreen", "${config.touchscreen}") + setProperty("Keyboard", "${config.keyboard}") + setProperty("Navigation", "${config.navigation}") setProperty("ScreenLayout", "${config.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK}") - setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}") - setProperty( - "HasFiveWayNavigation", - "${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}" - ) - } - - private fun Properties.setBuildProperties() { + setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}") + setProperty( + "HasFiveWayNavigation", + "${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}" + ) + } + + private fun Properties.setBuildProperties() { setProperty("UserReadableName", "${Build.DEVICE}-default") setProperty("Build.HARDWARE", Build.HARDWARE) setProperty( @@ -120,18 +120,18 @@ object NativeDeviceInfoProviderModule { "unknown" } ) - setProperty("Build.FINGERPRINT", Build.FINGERPRINT) - setProperty("Build.BRAND", Build.BRAND) - setProperty("Build.DEVICE", Build.DEVICE) - setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}") - setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE) - setProperty("Build.MODEL", Build.MODEL) - setProperty("Build.MANUFACTURER", Build.MANUFACTURER) - setProperty("Build.PRODUCT", Build.PRODUCT) - setProperty("Build.ID", Build.ID) - setProperty("Build.BOOTLOADER", Build.BOOTLOADER) - } - + setProperty("Build.FINGERPRINT", Build.FINGERPRINT) + setProperty("Build.BRAND", Build.BRAND) + setProperty("Build.DEVICE", Build.DEVICE) + setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}") + setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE) + setProperty("Build.MODEL", Build.MODEL) + setProperty("Build.MANUFACTURER", Build.MANUFACTURER) + setProperty("Build.PRODUCT", Build.PRODUCT) + setProperty("Build.ID", Build.ID) + setProperty("Build.BOOTLOADER", Build.BOOTLOADER) + } + private fun getFeatures(context: Context): List { val featureStringList: MutableList = ArrayList() try { @@ -146,22 +146,22 @@ object NativeDeviceInfoProviderModule { } return featureStringList } - - private fun getLocales(context: Context): List { - val localeList: MutableList = ArrayList() - localeList.addAll(listOf(*context.assets.locales)) - val locales: MutableList = ArrayList() - for (locale in localeList) { - if (TextUtils.isEmpty(locale)) { - continue - } - locales.add(locale.replace("-", "_")) - } - return locales - } - - private fun getSharedLibraries(context: Context): List { - val systemSharedLibraryNames = context.packageManager.systemSharedLibraryNames + + private fun getLocales(context: Context): List { + val localeList: MutableList = ArrayList() + localeList.addAll(listOf(*context.assets.locales)) + val locales: MutableList = ArrayList() + for (locale in localeList) { + if (TextUtils.isEmpty(locale)) { + continue + } + locales.add(locale.replace("-", "_")) + } + return locales + } + + private fun getSharedLibraries(context: Context): List { + val systemSharedLibraryNames = context.packageManager.systemSharedLibraryNames val libraries: MutableList = ArrayList() try { if (systemSharedLibraryNames != null) { diff --git a/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeGsfVersionProvider.kt b/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeGsfVersionProvider.kt index 39b08e550d396e4cfdaf096ca69690a34b16db15..7a108a42c13f723f5cc77a03c7ed1f0ce7c9ce39 100644 --- a/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeGsfVersionProvider.kt +++ b/app/src/main/java/foundation/e/apps/data/playstore/utils/NativeGsfVersionProvider.kt @@ -1,39 +1,39 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021, Rahul Kumar Patel - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.data.playstore.utils - -import android.content.Context -import android.content.pm.PackageManager -import androidx.core.content.pm.PackageInfoCompat - -class NativeGsfVersionProvider(context: Context) { - private var gsfVersionCode = 0L - private val packageManager = context.packageManager - - init { - try { - val gsfPkgInfo = packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0) - gsfVersionCode = PackageInfoCompat.getLongVersionCode(gsfPkgInfo) - } catch (e: PackageManager.NameNotFoundException) { - // com.google.android.gms not found - } +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021, Rahul Kumar Patel + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.data.playstore.utils + +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.pm.PackageInfoCompat + +class NativeGsfVersionProvider(context: Context) { + private var gsfVersionCode = 0L + private val packageManager = context.packageManager + + init { + try { + val gsfPkgInfo = packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0) + gsfVersionCode = PackageInfoCompat.getLongVersionCode(gsfPkgInfo) + } catch (e: PackageManager.NameNotFoundException) { + // com.google.android.gms not found + } } fun getGsfVersionCode(defaultIfNotFound: Boolean): Long { @@ -43,19 +43,19 @@ class NativeGsfVersionProvider(context: Context) { gsfVersionCode } } - - fun getVendingVersionCode(): Long { - return GOOGLE_VENDING_VERSION_CODE - } - - fun getVendingVersionString(): String { - return GOOGLE_VENDING_VERSION_STRING - } - - companion object { - private const val GOOGLE_SERVICES_PACKAGE_ID = "com.google.android.gms" - private const val GOOGLE_SERVICES_VERSION_CODE = 203019037L - private const val GOOGLE_VENDING_VERSION_CODE = 82151710L - private const val GOOGLE_VENDING_VERSION_STRING = "21.5.17-21 [0] [PR] 326734551" - } -} + + fun getVendingVersionCode(): Long { + return GOOGLE_VENDING_VERSION_CODE + } + + fun getVendingVersionString(): String { + return GOOGLE_VENDING_VERSION_STRING + } + + companion object { + private const val GOOGLE_SERVICES_PACKAGE_ID = "com.google.android.gms" + private const val GOOGLE_SERVICES_VERSION_CODE = 203019037L + private const val GOOGLE_VENDING_VERSION_CODE = 82151710L + private const val GOOGLE_VENDING_VERSION_STRING = "21.5.17-21 [0] [PR] 326734551" + } +} diff --git a/app/src/main/java/foundation/e/apps/di/CommonUtilsModule.kt b/app/src/main/java/foundation/e/apps/di/CommonUtilsModule.kt index ae0d53e5bd348c59e48b4312943f6e690e567470..44af528d97cca4e7d5924f8f9d510ca4dcfe2f19 100644 --- a/app/src/main/java/foundation/e/apps/di/CommonUtilsModule.kt +++ b/app/src/main/java/foundation/e/apps/di/CommonUtilsModule.kt @@ -1,43 +1,43 @@ -/* - * Copyright (C) 2021-2025 e Foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.di - -import android.content.ClipboardManager -import android.content.Context -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.os.StatFs -import androidx.annotation.IdRes -import androidx.navigation.NavController -import com.aurora.gplayapi.data.serializers.LocaleSerializer -import com.aurora.gplayapi.data.serializers.PropertiesSerializer -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import foundation.e.apps.BuildConfig -import foundation.e.apps.di.qualifiers.IoCoroutineScope -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob +/* + * Copyright (C) 2021-2025 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.di + +import android.content.ClipboardManager +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.os.StatFs +import androidx.annotation.IdRes +import androidx.navigation.NavController +import com.aurora.gplayapi.data.serializers.LocaleSerializer +import com.aurora.gplayapi.data.serializers.PropertiesSerializer +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.BuildConfig +import foundation.e.apps.di.qualifiers.IoCoroutineScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.contextual @@ -45,9 +45,9 @@ import okhttp3.Cache import timber.log.Timber import javax.inject.Named import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) + +@Module +@InstallIn(SingletonComponent::class) object CommonUtilsModule { val LIST_OF_NULL = listOf("null") @@ -58,61 +58,61 @@ object CommonUtilsModule { * Check supported ABIs by device * @return An ordered list of ABIs supported by this device */ - @Singleton - @Provides - fun provideArchitecture(): Array { - return Build.SUPPORTED_ABIS - } - - /** - * Check system build type - * @return Type of the system build, like "release" or "test" - */ - @Singleton - @Provides - @Named("buildType") - fun provideBuildType(): String { - return Build.TAGS.split("-")[0] - } - - /** + @Singleton + @Provides + fun provideArchitecture(): Array { + return Build.SUPPORTED_ABIS + } + + /** + * Check system build type + * @return Type of the system build, like "release" or "test" + */ + @Singleton + @Provides + @Named("buildType") + fun provideBuildType(): String { + return Build.TAGS.split("-")[0] + } + + /** * Path to application's external cache directory. * ExternalCacheDir is required because we use downloadManager to download & save file. * DownloadManager doesn't have access to cacheDir (which is app private). * @param context [Context] * @return absolute path to cache directory or hardcoded string * (/storage/emulated/0/Android/data//cache) if not available - */ - @Singleton - @Provides - @Named("cacheDir") - fun provideCacheDir(@ApplicationContext context: Context): String { - return context.externalCacheDir?.absolutePath.let { - if (it.isNullOrBlank()) "/storage/emulated/0/Android/data/${BuildConfig.APPLICATION_ID}/cache" else it - } - } - - /** - * Available free space on the device in bytes - * @return available bytes in the data directory - */ - @Singleton - @Provides - fun provideAvailableMegaBytes(): Long { - val stat = StatFs(Environment.getDataDirectory().path) - return stat.availableBytes - } - - @Singleton - @Provides - fun providesJsonInstance(): Json { - val module = SerializersModule { - contextual(LocaleSerializer) - contextual(PropertiesSerializer) - } - - return Json { - prettyPrint = true + */ + @Singleton + @Provides + @Named("cacheDir") + fun provideCacheDir(@ApplicationContext context: Context): String { + return context.externalCacheDir?.absolutePath.let { + if (it.isNullOrBlank()) "/storage/emulated/0/Android/data/${BuildConfig.APPLICATION_ID}/cache" else it + } + } + + /** + * Available free space on the device in bytes + * @return available bytes in the data directory + */ + @Singleton + @Provides + fun provideAvailableMegaBytes(): Long { + val stat = StatFs(Environment.getDataDirectory().path) + return stat.availableBytes + } + + @Singleton + @Provides + fun providesJsonInstance(): Json { + val module = SerializersModule { + contextual(LocaleSerializer) + contextual(PropertiesSerializer) + } + + return Json { + prettyPrint = true ignoreUnknownKeys = true coerceInputValues = true serializersModule = module @@ -125,7 +125,7 @@ object CommonUtilsModule { val cacheSize = CACHE_SIZE_MB * BYTES_IN_MB return Cache(context.cacheDir, cacheSize) } - + /** * Prevents calling a route if the navigation is already done, i.e. prevents duplicate calls. * Source: @@ -147,16 +147,16 @@ object CommonUtilsModule { Timber.w(e, "Navigation to destination $id failed") } } - - @Provides - @IoCoroutineScope - fun provideIoCoroutineScope(): CoroutineScope { - return CoroutineScope(SupervisorJob() + Dispatchers.IO) - } - - @Singleton - @Provides - fun provideClipboardService(@ApplicationContext context: Context): ClipboardManager { - return context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - } -} + + @Provides + @IoCoroutineScope + fun provideIoCoroutineScope(): CoroutineScope { + return CoroutineScope(SupervisorJob() + Dispatchers.IO) + } + + @Singleton + @Provides + fun provideClipboardService(@ApplicationContext context: Context): ClipboardManager { + return context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + } +} diff --git a/app/src/main/java/foundation/e/apps/di/DatabaseModule.kt b/app/src/main/java/foundation/e/apps/di/DatabaseModule.kt index 0ede4e137a865d8c3e988bac34c47ed5ed4b35a6..00be9761f1741e5d38ff2c57b283bae7e0a28ebc 100644 --- a/app/src/main/java/foundation/e/apps/di/DatabaseModule.kt +++ b/app/src/main/java/foundation/e/apps/di/DatabaseModule.kt @@ -1,27 +1,27 @@ -package foundation.e.apps.di - -import android.content.Context -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import foundation.e.apps.data.database.install.AppInstallDatabase -import foundation.e.apps.data.install.AppInstallDAO -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object DatabaseModule { - @Singleton - @Provides - fun provideDatabaseInstance(@ApplicationContext context: Context): AppInstallDatabase { - return AppInstallDatabase.getInstance(context) - } - - @Singleton - @Provides - fun provideFusedDaoInstance(appInstallDatabase: AppInstallDatabase): AppInstallDAO { - return appInstallDatabase.fusedDownloadDao() - } -} +package foundation.e.apps.di + +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.data.database.install.AppInstallDatabase +import foundation.e.apps.data.install.AppInstallDAO +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DatabaseModule { + @Singleton + @Provides + fun provideDatabaseInstance(@ApplicationContext context: Context): AppInstallDatabase { + return AppInstallDatabase.getInstance(context) + } + + @Singleton + @Provides + fun provideFusedDaoInstance(appInstallDatabase: AppInstallDatabase): AppInstallDAO { + return appInstallDatabase.fusedDownloadDao() + } +} diff --git a/app/src/main/java/foundation/e/apps/di/DownloadManagerModule.kt b/app/src/main/java/foundation/e/apps/di/DownloadManagerModule.kt index f902b05974512d98cbc569ba75530d52e479f0df..91f2d7fb57d84806e635d3a5cf034ad7c2c3ce57 100644 --- a/app/src/main/java/foundation/e/apps/di/DownloadManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/di/DownloadManagerModule.kt @@ -1,45 +1,45 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.di - -import android.app.DownloadManager -import android.content.Context -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object DownloadManagerModule { - - @Provides - @Singleton - fun provideDownloadManagerInstance(@ApplicationContext context: Context): DownloadManager { - return context.getSystemService(DownloadManager::class.java) - } - - @Provides - @Singleton - fun provideDownloadManagerQueryInstance(): DownloadManager.Query { - return DownloadManager.Query() - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.di + +import android.app.DownloadManager +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object DownloadManagerModule { + + @Provides + @Singleton + fun provideDownloadManagerInstance(@ApplicationContext context: Context): DownloadManager { + return context.getSystemService(DownloadManager::class.java) + } + + @Provides + @Singleton + fun provideDownloadManagerQueryInstance(): DownloadManager.Query { + return DownloadManager.Query() + } +} diff --git a/app/src/main/java/foundation/e/apps/di/NotificationManagerModule.kt b/app/src/main/java/foundation/e/apps/di/NotificationManagerModule.kt index cfe68f818fec8c91f22dee5e9120e629fa3a2aa0..3f7ffe30cc7f40547a07a601d889d8a433a7de3e 100644 --- a/app/src/main/java/foundation/e/apps/di/NotificationManagerModule.kt +++ b/app/src/main/java/foundation/e/apps/di/NotificationManagerModule.kt @@ -1,72 +1,72 @@ -/* - * Copyright MURENA SAS 2023 - * Apps Quickly and easily install Android apps onto your device! - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.di - -import android.app.NotificationChannel -import android.app.NotificationManager -import android.content.Context -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.android.qualifiers.ApplicationContext -import dagger.hilt.components.SingletonComponent -import foundation.e.apps.R -import javax.inject.Named -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -object NotificationManagerModule { - - const val DOWNLOADS = "DOWNLOADS" - const val UPDATES = "UPDATES" - - @Singleton - @Provides - fun provideNotificationManagerInstance(@ApplicationContext context: Context): NotificationManager { - return context.getSystemService(NotificationManager::class.java) - } - - @Singleton - @Provides - @Named("download") - fun provideDownloadNotificationChannel( - @ApplicationContext context: Context - ): NotificationChannel { - return NotificationChannel( - DOWNLOADS, - context.getString(R.string.downloads), - NotificationManager.IMPORTANCE_DEFAULT - ) - } - - @Singleton - @Provides - @Named("update") - fun provideUpdateNotificationChannel( - @ApplicationContext context: Context - ): NotificationChannel { - return NotificationChannel( - UPDATES, - context.getString(R.string.updates), - NotificationManager.IMPORTANCE_DEFAULT - ) - } -} +/* + * Copyright MURENA SAS 2023 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.di + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import foundation.e.apps.R +import javax.inject.Named +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NotificationManagerModule { + + const val DOWNLOADS = "DOWNLOADS" + const val UPDATES = "UPDATES" + + @Singleton + @Provides + fun provideNotificationManagerInstance(@ApplicationContext context: Context): NotificationManager { + return context.getSystemService(NotificationManager::class.java) + } + + @Singleton + @Provides + @Named("download") + fun provideDownloadNotificationChannel( + @ApplicationContext context: Context + ): NotificationChannel { + return NotificationChannel( + DOWNLOADS, + context.getString(R.string.downloads), + NotificationManager.IMPORTANCE_DEFAULT + ) + } + + @Singleton + @Provides + @Named("update") + fun provideUpdateNotificationChannel( + @ApplicationContext context: Context + ): NotificationChannel { + return NotificationChannel( + UPDATES, + context.getString(R.string.updates), + NotificationManager.IMPORTANCE_DEFAULT + ) + } +} diff --git a/app/src/main/java/foundation/e/apps/install/download/DownloadManagerBR.kt b/app/src/main/java/foundation/e/apps/install/download/DownloadManagerBR.kt index fee2e5366b75857082381cf7e8dd3860c97c0a43..819302f306de34a80e1fe677f27a5695f05863cb 100644 --- a/app/src/main/java/foundation/e/apps/install/download/DownloadManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/install/download/DownloadManagerBR.kt @@ -1,54 +1,54 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.install.download - -import android.app.DownloadManager -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import androidx.annotation.UiThread -import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.DelicateCoroutinesApi -import timber.log.Timber -import javax.inject.Inject - -@AndroidEntryPoint -@DelicateCoroutinesApi -class DownloadManagerBR : BroadcastReceiver() { - - @Inject - lateinit var downloadManagerUtils: DownloadManagerUtils - - @UiThread - override fun onReceive(context: Context?, intent: Intent?) { - val action = intent?.action - if (context != null && action != null) { - val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) - Timber.i("onReceive: DownloadBR $action $id") - when (action) { - DownloadManager.ACTION_DOWNLOAD_COMPLETE -> { - downloadManagerUtils.updateDownloadStatus(id) - } - DownloadManager.ACTION_NOTIFICATION_CLICKED -> { - if (id != 0L) downloadManagerUtils.cancelDownload(id) - } - } - } - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.install.download + +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import androidx.annotation.UiThread +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.DelicateCoroutinesApi +import timber.log.Timber +import javax.inject.Inject + +@AndroidEntryPoint +@DelicateCoroutinesApi +class DownloadManagerBR : BroadcastReceiver() { + + @Inject + lateinit var downloadManagerUtils: DownloadManagerUtils + + @UiThread + override fun onReceive(context: Context?, intent: Intent?) { + val action = intent?.action + if (context != null && action != null) { + val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) + Timber.i("onReceive: DownloadBR $action $id") + when (action) { + DownloadManager.ACTION_DOWNLOAD_COMPLETE -> { + downloadManagerUtils.updateDownloadStatus(id) + } + DownloadManager.ACTION_NOTIFICATION_CLICKED -> { + if (id != 0L) downloadManagerUtils.cancelDownload(id) + } + } + } + } +} diff --git a/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgress.kt b/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgress.kt index e2132b4de44d8199460d12c17cf230359668c7a2..26702236f143147b27533ec1d595323bcb93f9fd 100644 --- a/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgress.kt +++ b/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgress.kt @@ -1,8 +1,8 @@ -package foundation.e.apps.install.download.data - -data class DownloadProgress( - var totalSizeBytes: MutableMap = mutableMapOf(), - var bytesDownloadedSoFar: MutableMap = mutableMapOf(), - var status: MutableMap = mutableMapOf(), - var downloadId: Long = -1 -) +package foundation.e.apps.install.download.data + +data class DownloadProgress( + var totalSizeBytes: MutableMap = mutableMapOf(), + var bytesDownloadedSoFar: MutableMap = mutableMapOf(), + var status: MutableMap = mutableMapOf(), + var downloadId: Long = -1 +) diff --git a/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt b/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt index 75a54e15c77909bd484a42fcfd22bb4700828566..f5972e70e120321cf9e201e4e4a45e73e288e8d5 100644 --- a/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt +++ b/app/src/main/java/foundation/e/apps/install/download/data/DownloadProgressLD.kt @@ -1,60 +1,60 @@ -/* - * Copyright ECORP SAS 2022 - * Apps Quickly and easily install Android apps onto your device! - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.install.download.data - -import android.app.DownloadManager -import android.app.DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR -import android.app.DownloadManager.COLUMN_ID -import android.app.DownloadManager.COLUMN_STATUS -import android.app.DownloadManager.COLUMN_TOTAL_SIZE_BYTES -import android.database.Cursor -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer -import foundation.e.apps.data.install.AppManagerWrapper -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.delay -import kotlinx.coroutines.launch -import timber.log.Timber -import javax.inject.Inject -import kotlin.coroutines.CoroutineContext - -class DownloadProgressLD @Inject constructor( - private val downloadManager: DownloadManager, - private val downloadManagerQuery: DownloadManager.Query, - private val appManagerWrapper: AppManagerWrapper -) : LiveData(), CoroutineScope { - - private lateinit var job: Job - private var downloadProgress = DownloadProgress() - - override val coroutineContext: CoroutineContext - get() = Dispatchers.IO + job - - override fun observe(owner: LifecycleOwner, observer: Observer) { - job = Job() - super.observe(owner, observer) - launch { +/* + * Copyright ECORP SAS 2022 + * Apps Quickly and easily install Android apps onto your device! + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.install.download.data + +import android.app.DownloadManager +import android.app.DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR +import android.app.DownloadManager.COLUMN_ID +import android.app.DownloadManager.COLUMN_STATUS +import android.app.DownloadManager.COLUMN_TOTAL_SIZE_BYTES +import android.database.Cursor +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer +import foundation.e.apps.data.install.AppManagerWrapper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext + +class DownloadProgressLD @Inject constructor( + private val downloadManager: DownloadManager, + private val downloadManagerQuery: DownloadManager.Query, + private val appManagerWrapper: AppManagerWrapper +) : LiveData(), CoroutineScope { + + private lateinit var job: Job + private var downloadProgress = DownloadProgress() + + override val coroutineContext: CoroutineContext + get() = Dispatchers.IO + job + + override fun observe(owner: LifecycleOwner, observer: Observer) { + job = Job() + super.observe(owner, observer) + launch { while (hasActiveObservers() || owner.lifecycle.currentState == Lifecycle.State.RESUMED) { val downloads = appManagerWrapper.getDownloadList() val downloadingList = @@ -74,85 +74,85 @@ class DownloadProgressLD @Inject constructor( } } } - - @Suppress("SpreadOperator") // DownloadManager#Query requires vararg ids; unavoidable spread. - private fun findDownloadProgress(downloadingIds: MutableList) { - val idsToQuery = downloadingIds.toLongArray() - if (idsToQuery.isEmpty()) { - return - } - - downloadManager.query(downloadManagerQuery.setFilterById(*idsToQuery)) - ?.use { safeCursor -> - processCursor(safeCursor, downloadingIds) - } - ?: run { - Timber.w("DownloadManager returned null cursor for ids $downloadingIds") - postValue(downloadProgress) - } - } - - private fun processCursor( - cursor: Cursor, - downloadingIds: List - ) { - if (!cursor.moveToFirst()) { - postValue(downloadProgress) - return - } - - do { - val id = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)) - val status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)) - val totalSizeBytes = - cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_TOTAL_SIZE_BYTES)) - val bytesDownloadedSoFar = - cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_BYTES_DOWNLOADED_SO_FAR)) - - downloadProgress.downloadId = id - - if (!downloadProgress.totalSizeBytes.containsKey(id) || - downloadProgress.totalSizeBytes[id] != totalSizeBytes - ) { - downloadProgress.totalSizeBytes[id] = totalSizeBytes - } - - if (!downloadProgress.bytesDownloadedSoFar.containsKey(id) || - downloadProgress.bytesDownloadedSoFar[id] != bytesDownloadedSoFar - ) { - downloadProgress.bytesDownloadedSoFar[id] = bytesDownloadedSoFar - } - - downloadProgress.status[id] = - status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED - - if (downloadingIds.isEmpty()) { - cancel() - } - } while (cursor.moveToNext()) - - postValue(downloadProgress) - } - - override fun onInactive() { - super.onInactive() - job.cancel() - } - - companion object { - - const val TAG = "DownloadProgressLD" - - var downloadId = mutableListOf() - - fun setDownloadId(id: Long) { - if (id == -1L) { - clearDownload() - return - } - downloadId.add(id) - } - + + @Suppress("SpreadOperator") // DownloadManager#Query requires vararg ids; unavoidable spread. + private fun findDownloadProgress(downloadingIds: MutableList) { + val idsToQuery = downloadingIds.toLongArray() + if (idsToQuery.isEmpty()) { + return + } + + downloadManager.query(downloadManagerQuery.setFilterById(*idsToQuery)) + ?.use { safeCursor -> + processCursor(safeCursor, downloadingIds) + } + ?: run { + Timber.w("DownloadManager returned null cursor for ids $downloadingIds") + postValue(downloadProgress) + } + } + + private fun processCursor( + cursor: Cursor, + downloadingIds: List + ) { + if (!cursor.moveToFirst()) { + postValue(downloadProgress) + return + } + + do { + val id = cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_ID)) + val status = cursor.getInt(cursor.getColumnIndexOrThrow(COLUMN_STATUS)) + val totalSizeBytes = + cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_TOTAL_SIZE_BYTES)) + val bytesDownloadedSoFar = + cursor.getLong(cursor.getColumnIndexOrThrow(COLUMN_BYTES_DOWNLOADED_SO_FAR)) + + downloadProgress.downloadId = id + + if (!downloadProgress.totalSizeBytes.containsKey(id) || + downloadProgress.totalSizeBytes[id] != totalSizeBytes + ) { + downloadProgress.totalSizeBytes[id] = totalSizeBytes + } + + if (!downloadProgress.bytesDownloadedSoFar.containsKey(id) || + downloadProgress.bytesDownloadedSoFar[id] != bytesDownloadedSoFar + ) { + downloadProgress.bytesDownloadedSoFar[id] = bytesDownloadedSoFar + } + + downloadProgress.status[id] = + status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED + + if (downloadingIds.isEmpty()) { + cancel() + } + } while (cursor.moveToNext()) + + postValue(downloadProgress) + } + + override fun onInactive() { + super.onInactive() + job.cancel() + } + + companion object { + + const val TAG = "DownloadProgressLD" + + var downloadId = mutableListOf() + + fun setDownloadId(id: Long) { + if (id == -1L) { + clearDownload() + return + } + downloadId.add(id) + } + private fun clearDownload() { downloadId.clear() } diff --git a/app/src/main/java/foundation/e/apps/install/notification/StorageNotificationManager.kt b/app/src/main/java/foundation/e/apps/install/notification/StorageNotificationManager.kt index 51c24e529384f79f351e6b37083d5ebec7935048..292310b8e1c60b76f016978ec3fefacfcca30d03 100644 --- a/app/src/main/java/foundation/e/apps/install/notification/StorageNotificationManager.kt +++ b/app/src/main/java/foundation/e/apps/install/notification/StorageNotificationManager.kt @@ -1,43 +1,43 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * Copyright (C) 2023 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.install.notification - -import android.Manifest -import android.app.Notification -import android.content.Context -import android.content.pm.PackageManager -import androidx.core.app.ActivityCompat -import androidx.core.app.NotificationCompat -import androidx.core.app.NotificationManagerCompat -import dagger.hilt.android.qualifiers.ApplicationContext -import foundation.e.apps.R -import foundation.e.apps.data.DownloadManager -import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.di.NotificationManagerModule -import foundation.e.apps.utils.StorageComputer -import javax.inject.Inject - -class StorageNotificationManager @Inject constructor( - @ApplicationContext private val context: Context, - private val downloadManager: DownloadManager, -) { +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * Copyright (C) 2023 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.install.notification + +import android.Manifest +import android.app.Notification +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import dagger.hilt.android.qualifiers.ApplicationContext +import foundation.e.apps.R +import foundation.e.apps.data.DownloadManager +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.di.NotificationManagerModule +import foundation.e.apps.utils.StorageComputer +import javax.inject.Inject + +class StorageNotificationManager @Inject constructor( + @ApplicationContext private val context: Context, + private val downloadManager: DownloadManager, +) { companion object { const val NOT_ENOUGH_SPACE_NOTIFICATION_ID = 7874 } @@ -48,59 +48,59 @@ class StorageNotificationManager @Inject constructor( != PackageManager.PERMISSION_GRANTED ) { return - } - - val content = getNotEnoughSpaceNotificationContent(appInstall, downloadId) - - notify( - NOT_ENOUGH_SPACE_NOTIFICATION_ID, - getNotEnoughSpaceNotification(content) - ) - } - } - - private fun getNotEnoughSpaceNotificationContent( - appInstall: AppInstall, - downloadId: Long? = null - ): String { - val requiredInByte = getSpaceMissing(appInstall, downloadId) - - if (requiredInByte <= 0L) { - return context.getString(R.string.not_enough_storage) - } - - return context.getString( - R.string.free_space_for_update, - StorageComputer.humanReadableByteCountSI(requiredInByte) - ) - } - - private fun getSpaceMissing(appInstall: AppInstall, downloadId: Long? = null): Long { - if (appInstall.appSize > 0L) { - return calculateSpaceMissingFromFusedDownload(appInstall) - } - - if (downloadId == null) { - return 0 - } - - return downloadManager.getSizeRequired(downloadId) - } - - private fun calculateSpaceMissingFromFusedDownload(appInstall: AppInstall): Long { - var requiredInByte = StorageComputer.spaceMissing(appInstall) - if (requiredInByte <= 0L) { - requiredInByte = appInstall.appSize - } - - return requiredInByte - } - - private fun getNotEnoughSpaceNotification(content: String): Notification { - return NotificationCompat.Builder(context, NotificationManagerModule.DOWNLOADS) - .setContentText(content) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setSmallIcon(R.drawable.app_lounge_notification_icon) - .build() - } -} + } + + val content = getNotEnoughSpaceNotificationContent(appInstall, downloadId) + + notify( + NOT_ENOUGH_SPACE_NOTIFICATION_ID, + getNotEnoughSpaceNotification(content) + ) + } + } + + private fun getNotEnoughSpaceNotificationContent( + appInstall: AppInstall, + downloadId: Long? = null + ): String { + val requiredInByte = getSpaceMissing(appInstall, downloadId) + + if (requiredInByte <= 0L) { + return context.getString(R.string.not_enough_storage) + } + + return context.getString( + R.string.free_space_for_update, + StorageComputer.humanReadableByteCountSI(requiredInByte) + ) + } + + private fun getSpaceMissing(appInstall: AppInstall, downloadId: Long? = null): Long { + if (appInstall.appSize > 0L) { + return calculateSpaceMissingFromFusedDownload(appInstall) + } + + if (downloadId == null) { + return 0 + } + + return downloadManager.getSizeRequired(downloadId) + } + + private fun calculateSpaceMissingFromFusedDownload(appInstall: AppInstall): Long { + var requiredInByte = StorageComputer.spaceMissing(appInstall) + if (requiredInByte <= 0L) { + requiredInByte = appInstall.appSize + } + + return requiredInByte + } + + private fun getNotEnoughSpaceNotification(content: String): Notification { + return NotificationCompat.Builder(context, NotificationManagerModule.DOWNLOADS) + .setContentText(content) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setSmallIcon(R.drawable.app_lounge_notification_icon) + .build() + } +} diff --git a/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt b/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt index 498e05da8bb36a44c727ef54f3f3aa4e38d5ce44..4ae52e515db5fa793417e26ae8c6d2785a5295ee 100644 --- a/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt +++ b/app/src/main/java/foundation/e/apps/install/pkg/PkgManagerBR.kt @@ -1,21 +1,21 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package foundation.e.apps.install.pkg import android.content.BroadcastReceiver @@ -41,85 +41,85 @@ open class PkgManagerBR : BroadcastReceiver() { @Inject lateinit var appManagerWrapper: AppManagerWrapper - - @Inject - lateinit var appLoungePackageManager: AppLoungePackageManager - - @Inject - lateinit var faultyAppRepository: FaultyAppRepository - - @Inject - @IoCoroutineScope - lateinit var coroutineScope: CoroutineScope - - override fun onReceive(context: Context?, intent: Intent?) { - val action = intent?.action - if (context != null && action != null) { + + @Inject + lateinit var appLoungePackageManager: AppLoungePackageManager + + @Inject + lateinit var faultyAppRepository: FaultyAppRepository + + @Inject + @IoCoroutineScope + lateinit var coroutineScope: CoroutineScope + + override fun onReceive(context: Context?, intent: Intent?) { + val action = intent?.action + if (context != null && action != null) { val isUpdating = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false) val extra = intent.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, DEFAULT_INSTALL_STATUS) - val packageName = intent.data?.schemeSpecificPart - - Timber.d("onReceive: $packageName $action $extra $status") - packageName?.let { - handlePackageList(it, action, isUpdating, extra) - } - } - } - - private fun handlePackageList( - packageName: String, - action: String, - isUpdating: Boolean, - extra: String? - ) { - when (action) { - Intent.ACTION_PACKAGE_ADDED -> { - updateDownloadStatus(packageName) - removeFaultyAppByPackageName(packageName) - } - - Intent.ACTION_PACKAGE_REMOVED -> { - if (!isUpdating) deleteDownload(packageName) - removeFaultyAppByPackageName(packageName) - } - - AppLoungePackageManager.ERROR_PACKAGE_INSTALL -> { - Timber.e("Installation failed due to error: $extra") - updateInstallationIssue(packageName) - } - } - } - - private fun removeFaultyAppByPackageName(pkgName: String) { - coroutineScope.launch { - faultyAppRepository.deleteFaultyAppByPackageName(pkgName) - } - } - - private fun deleteDownload(pkgName: String) { - coroutineScope.launch { - val fusedDownload = appManagerWrapper.getFusedDownload(packageName = pkgName) - appManagerWrapper.cancelDownload(fusedDownload, pkgName) - } - } - - // TODO: FIND A BETTER WAY TO DO THIS - private fun updateDownloadStatus(pkgName: String) { - if (pkgName.isEmpty()) { - Timber.d("updateDownloadStatus: package name should not be empty!") - } - coroutineScope.launch { - val fusedDownload = appManagerWrapper.getFusedDownload(packageName = pkgName) - appLoungePackageManager.setFakeStoreAsInstallerIfNeeded(fusedDownload) - appManagerWrapper.updateDownloadStatus(fusedDownload, Status.INSTALLED) - } - } - - private fun updateInstallationIssue(pkgName: String) { - coroutineScope.launch { - val fusedDownload = appManagerWrapper.getFusedDownload(packageName = pkgName) - appManagerWrapper.installationIssue(fusedDownload) - } - } -} + val packageName = intent.data?.schemeSpecificPart + + Timber.d("onReceive: $packageName $action $extra $status") + packageName?.let { + handlePackageList(it, action, isUpdating, extra) + } + } + } + + private fun handlePackageList( + packageName: String, + action: String, + isUpdating: Boolean, + extra: String? + ) { + when (action) { + Intent.ACTION_PACKAGE_ADDED -> { + updateDownloadStatus(packageName) + removeFaultyAppByPackageName(packageName) + } + + Intent.ACTION_PACKAGE_REMOVED -> { + if (!isUpdating) deleteDownload(packageName) + removeFaultyAppByPackageName(packageName) + } + + AppLoungePackageManager.ERROR_PACKAGE_INSTALL -> { + Timber.e("Installation failed due to error: $extra") + updateInstallationIssue(packageName) + } + } + } + + private fun removeFaultyAppByPackageName(pkgName: String) { + coroutineScope.launch { + faultyAppRepository.deleteFaultyAppByPackageName(pkgName) + } + } + + private fun deleteDownload(pkgName: String) { + coroutineScope.launch { + val fusedDownload = appManagerWrapper.getFusedDownload(packageName = pkgName) + appManagerWrapper.cancelDownload(fusedDownload, pkgName) + } + } + + // TODO: FIND A BETTER WAY TO DO THIS + private fun updateDownloadStatus(pkgName: String) { + if (pkgName.isEmpty()) { + Timber.d("updateDownloadStatus: package name should not be empty!") + } + coroutineScope.launch { + val fusedDownload = appManagerWrapper.getFusedDownload(packageName = pkgName) + appLoungePackageManager.setFakeStoreAsInstallerIfNeeded(fusedDownload) + appManagerWrapper.updateDownloadStatus(fusedDownload, Status.INSTALLED) + } + } + + private fun updateInstallationIssue(pkgName: String) { + coroutineScope.launch { + val fusedDownload = appManagerWrapper.getFusedDownload(packageName = pkgName) + appManagerWrapper.installationIssue(fusedDownload) + } + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt index edb824d9164f2551d758a23ab3015fd3cc1ee3c6..f48c1990a09a5e19e20104212366a0dcb0e6ea5c 100644 --- a/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/MainActivityViewModel.kt @@ -1,131 +1,131 @@ -/* - * Copyright (C) 2021-2024 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.ui - -import android.content.Context -import android.content.Intent -import androidx.appcompat.app.AlertDialog -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import foundation.e.apps.R -import foundation.e.apps.data.application.ApplicationRepository -import foundation.e.apps.data.application.data.Application -import foundation.e.apps.data.application.mapper.toApplication -import foundation.e.apps.data.blockedApps.BlockedAppRepository -import foundation.e.apps.data.enums.User -import foundation.e.apps.data.enums.isInitialized -import foundation.e.apps.data.enums.isUnFiltered -import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository -import foundation.e.apps.data.install.AppManagerWrapper -import foundation.e.apps.data.install.models.AppInstall -import foundation.e.apps.data.login.core.AuthObject -import foundation.e.apps.data.login.state.LoginState -import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository -import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository -import foundation.e.apps.data.preference.AppLoungeDataStore +/* + * Copyright (C) 2021-2024 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.ui + +import android.content.Context +import android.content.Intent +import androidx.appcompat.app.AlertDialog +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import foundation.e.apps.R +import foundation.e.apps.data.application.ApplicationRepository +import foundation.e.apps.data.application.data.Application +import foundation.e.apps.data.application.mapper.toApplication +import foundation.e.apps.data.blockedApps.BlockedAppRepository +import foundation.e.apps.data.enums.User +import foundation.e.apps.data.enums.isInitialized +import foundation.e.apps.data.enums.isUnFiltered +import foundation.e.apps.data.gitlab.SystemAppsUpdatesRepository +import foundation.e.apps.data.install.AppManagerWrapper +import foundation.e.apps.data.install.models.AppInstall +import foundation.e.apps.data.login.core.AuthObject +import foundation.e.apps.data.login.state.LoginState +import foundation.e.apps.data.parentalcontrol.fdroid.FDroidAntiFeatureRepository +import foundation.e.apps.data.parentalcontrol.googleplay.GPlayContentRatingRepository +import foundation.e.apps.data.preference.AppLoungeDataStore import foundation.e.apps.data.preference.getSync -import foundation.e.apps.domain.application.ApplicationDomain -import foundation.e.apps.domain.login.ReportFaultyTokenUseCase -import foundation.e.apps.install.pkg.AppLoungePackageManager -import foundation.e.apps.install.pkg.PwaManager -import foundation.e.apps.install.workmanager.AppInstallProcessor -import foundation.e.apps.utils.NetworkStatusManager -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class MainActivityViewModel @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, - private val applicationRepository: ApplicationRepository, - private val appManagerWrapper: AppManagerWrapper, - private val appLoungePackageManager: AppLoungePackageManager, - private val pwaManager: PwaManager, - private val blockedAppRepository: BlockedAppRepository, - private val gPlayContentRatingRepository: GPlayContentRatingRepository, - private val fDroidAntiFeatureRepository: FDroidAntiFeatureRepository, +import foundation.e.apps.domain.application.ApplicationDomain +import foundation.e.apps.domain.login.ReportFaultyTokenUseCase +import foundation.e.apps.install.pkg.AppLoungePackageManager +import foundation.e.apps.install.pkg.PwaManager +import foundation.e.apps.install.workmanager.AppInstallProcessor +import foundation.e.apps.utils.NetworkStatusManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class MainActivityViewModel @Inject constructor( + private val appLoungeDataStore: AppLoungeDataStore, + private val applicationRepository: ApplicationRepository, + private val appManagerWrapper: AppManagerWrapper, + private val appLoungePackageManager: AppLoungePackageManager, + private val pwaManager: PwaManager, + private val blockedAppRepository: BlockedAppRepository, + private val gPlayContentRatingRepository: GPlayContentRatingRepository, + private val fDroidAntiFeatureRepository: FDroidAntiFeatureRepository, private val appInstallProcessor: AppInstallProcessor, private val systemAppsUpdatesRepository: SystemAppsUpdatesRepository, - private val reportFaultyTokenUseCase: ReportFaultyTokenUseCase, -) : ViewModel() { - - init { - updateAppWarningList() - updateContentRatings() - fetchUpdatableSystemAppsList() - } - - val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() - - private val _purchaseAppLiveData: MutableLiveData = MutableLiveData() - val purchaseAppLiveData: LiveData = _purchaseAppLiveData - val isAppPurchased: MutableLiveData = MutableLiveData() - val purchaseDeclined: MutableLiveData = MutableLiveData() - lateinit var internetConnection: LiveData - - // Downloads - val downloadList = appManagerWrapper.getDownloadLiveList() - private val _errorMessage = MutableLiveData() - val errorMessage: LiveData = _errorMessage - - private val _errorMessageStringResource = MutableLiveData() - val errorMessageStringResource: LiveData = _errorMessageStringResource - - data class MainUiState( - val navigateToSignIn: Boolean = false, - val finishAfterLogin: Boolean = false - ) - - private val _uiState = MutableLiveData(MainUiState()) - val uiState: LiveData = _uiState - - private val initialUiState = MainUiState() - - var gPlayLoginRequested = false - var closeAfterLogin = false - - var shouldIgnoreSessionError = false - - fun getTocStatus(): Boolean { - return appLoungeDataStore.tocStatus.getSync() - } - - fun getUser(): User { - return appLoungeDataStore.getUser() - } - - fun getLoginState(): LoginState { - return appLoungeDataStore.getLoginState() - } - - fun handleAuthObjects(authObjects: List?) { - if (authObjects == null) { - return - } - - if (authObjects.isEmpty()) { - handleEmptyAuthObjects() - return + private val reportFaultyTokenUseCase: ReportFaultyTokenUseCase, +) : ViewModel() { + + init { + updateAppWarningList() + updateContentRatings() + fetchUpdatableSystemAppsList() + } + + val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() + + private val _purchaseAppLiveData: MutableLiveData = MutableLiveData() + val purchaseAppLiveData: LiveData = _purchaseAppLiveData + val isAppPurchased: MutableLiveData = MutableLiveData() + val purchaseDeclined: MutableLiveData = MutableLiveData() + lateinit var internetConnection: LiveData + + // Downloads + val downloadList = appManagerWrapper.getDownloadLiveList() + private val _errorMessage = MutableLiveData() + val errorMessage: LiveData = _errorMessage + + private val _errorMessageStringResource = MutableLiveData() + val errorMessageStringResource: LiveData = _errorMessageStringResource + + data class MainUiState( + val navigateToSignIn: Boolean = false, + val finishAfterLogin: Boolean = false + ) + + private val _uiState = MutableLiveData(MainUiState()) + val uiState: LiveData = _uiState + + private val initialUiState = MainUiState() + + var gPlayLoginRequested = false + var closeAfterLogin = false + + var shouldIgnoreSessionError = false + + fun getTocStatus(): Boolean { + return appLoungeDataStore.tocStatus.getSync() + } + + fun getUser(): User { + return appLoungeDataStore.getUser() + } + + fun getLoginState(): LoginState { + return appLoungeDataStore.getLoginState() + } + + fun handleAuthObjects(authObjects: List?) { + if (authObjects == null) { + return + } + + if (authObjects.isEmpty()) { + handleEmptyAuthObjects() + return } val shouldFinishAfterLogin = shouldFinishAfterLogin(authObjects) @@ -133,249 +133,249 @@ class MainActivityViewModel @Inject constructor( if (user == User.ANONYMOUS) { viewModelScope.launch { reportFaultyTokenUseCase(authObjects) - } - } - - if (shouldFinishAfterLogin) { - _uiState.value = _uiState.value?.copy(finishAfterLogin = true) - ?: initialUiState.copy(finishAfterLogin = true) - } - } - - fun onNavigateToSignInHandled() { - _uiState.value = _uiState.value?.copy(navigateToSignIn = false) - ?: initialUiState - } - - fun onFinishAfterLoginHandled() { - _uiState.value = _uiState.value?.copy(finishAfterLogin = false) - ?: initialUiState - } - - private fun handleEmptyAuthObjects() { - if (gPlayLoginRequested) { - closeAfterLogin = true - } - - _uiState.value = _uiState.value?.copy(navigateToSignIn = true) - ?: initialUiState.copy(navigateToSignIn = true) - } - - private fun shouldFinishAfterLogin(authObjects: List): Boolean { - return closeAfterLogin && authObjects.all { it.result.isSuccess() } - } - - /* - * Notification functions - */ - - fun createNotificationChannels() { - appManagerWrapper.createNotificationChannels() - } - - /* - * Download and cancellation functions - */ - - /* - * Check and display a snack bar if app is paid and user is logged in in anonymous mode. - * Returns true if the snack bar was displayed, false otherwise. - * - * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/266 - */ - fun shouldShowPaidAppsSnackBar(app: Application): Boolean { - val authData = appLoungeDataStore.getAuthData() - if (!app.isFree && authData.isAnonymous) { - _errorMessageStringResource.value = R.string.paid_app_anonymous_message - return true - } - - return false - } - - fun shouldShowPaidAppsSnackBar(app: ApplicationDomain): Boolean { - return shouldShowPaidAppsSnackBar(app.toApplication()) - } - - /** - * Handle various cases of unsupported apps here. - * Returns true if the [application] is not supported by App Lounge. - * - * Pass [alertDialogContext] as null to prevent an alert dialog from being shown to the user. - * In that case, this method simply works as a validation. - * - * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/178 - */ - fun checkUnsupportedApplication( - application: Application, - alertDialogContext: Context? = null - ): Boolean { - if (!application.filterLevel.isUnFiltered()) { - alertDialogContext?.let { context -> - AlertDialog.Builder(context).apply { - setTitle(R.string.unsupported_app_title) - setMessage( - context.getString( - R.string.unsupported_app_unreleased, - application.name - ) - ) - setPositiveButton(android.R.string.ok, null) - }.show() - } - return true - } - return false - } - - fun checkUnsupportedApplication( - homeApp: ApplicationDomain, - alertDialogContext: Context? = null - ): Boolean { - if (!homeApp.filterLevel.isUnFiltered()) { - alertDialogContext?.let { context -> - AlertDialog.Builder(context).apply { - setTitle(R.string.unsupported_app_title) - setMessage( - context.getString( - R.string.unsupported_app_unreleased, - homeApp.name - ) - ) - setPositiveButton(android.R.string.ok, null) - }.show() - } - return true - } - return false - } - - /** - * Fetch the filter level of an app and perform some action. - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 - */ - fun verifyUiFilter(application: Application, method: () -> Unit) { - viewModelScope.launch { - if (application.filterLevel.isInitialized()) { - method() - } else { - applicationRepository.getAppFilterLevel(application).run { - if (isInitialized()) { - application.filterLevel = this - method() - } - } - } - } - } - - fun getApplication(app: Application) { - viewModelScope.launch(Dispatchers.IO) { - appInstallProcessor.initAppInstall(app) - } - } - - fun getApplication(homeApp: ApplicationDomain) { - getApplication(homeApp.toApplication()) - } - - suspend fun updateAwaitingForPurchasedApp(packageName: String): AppInstall? { - val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) - val authData = appLoungeDataStore.getAuthData() - if (!authData.isAnonymous) { - appInstallProcessor.enqueueFusedDownload(fusedDownload) - return fusedDownload - } - - return null - } - - suspend fun updateUnavailableForPurchaseDeclined(packageName: String) { - val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) - appManagerWrapper.updateUnavailable(fusedDownload) - } - - fun cancelDownload(app: Application) { - viewModelScope.launch { - val fusedDownload = - appManagerWrapper.getFusedDownload(packageName = app.package_name) - appManagerWrapper.cancelDownload(fusedDownload) - } - } - - fun cancelDownload(homeApp: ApplicationDomain) { - cancelDownload(homeApp.toApplication()) - } - - fun setupConnectivityManager(context: Context) { - internetConnection = NetworkStatusManager.init(context) - } - - fun updateStatusOfFusedApps( - applicationList: List, - appInstallList: List - ) { - applicationList.forEach { - val downloadingItem = appInstallList.find { fusedDownload -> - fusedDownload.source == it.source && - (fusedDownload.packageName == it.package_name || fusedDownload.id == it._id) - } - it.status = - downloadingItem?.status ?: applicationRepository.getFusedAppInstallationStatus(it) - } - } - - fun updateStatusOfApplicationDomains( - homeApps: List, - appInstallList: List - ): List { - return homeApps.map { homeApp -> - val downloadingItem = appInstallList.find { fusedDownload -> - fusedDownload.source == homeApp.source && - (fusedDownload.packageName == homeApp.packageName || fusedDownload.id == homeApp.id) - } - val status = downloadingItem?.status - ?: applicationRepository.getFusedAppInstallationStatus(homeApp.toApplication()) - homeApp.copy(status = status) - } - } - - fun updateAppWarningList() { - viewModelScope.launch { - blockedAppRepository.fetchUpdateOfAppWarningList() - } - } - - fun updateContentRatings() { - viewModelScope.launch { - fDroidAntiFeatureRepository.fetchNsfwApps() - gPlayContentRatingRepository.fetchContentRatingData() - } - } - - fun fetchUpdatableSystemAppsList() { - viewModelScope.launch { - systemAppsUpdatesRepository.fetchUpdatableSystemApps() - } - } - - fun getAppNameByPackageName(packageName: String): String { - return appLoungePackageManager.getAppNameFromPackageName(packageName) - } - - fun getLaunchIntentForPackageName(packageName: String): Intent? { - return appLoungePackageManager.getLaunchIntent(packageName) - } - - fun launchPwa(application: Application) { - pwaManager.launchPwa(application) - } - - fun launchPwa(homeApp: ApplicationDomain) { - launchPwa(homeApp.toApplication()) - } - - fun handleRatingFormat(rating: Double): String? { - return appManagerWrapper.handleRatingFormat(rating) - } -} + } + } + + if (shouldFinishAfterLogin) { + _uiState.value = _uiState.value?.copy(finishAfterLogin = true) + ?: initialUiState.copy(finishAfterLogin = true) + } + } + + fun onNavigateToSignInHandled() { + _uiState.value = _uiState.value?.copy(navigateToSignIn = false) + ?: initialUiState + } + + fun onFinishAfterLoginHandled() { + _uiState.value = _uiState.value?.copy(finishAfterLogin = false) + ?: initialUiState + } + + private fun handleEmptyAuthObjects() { + if (gPlayLoginRequested) { + closeAfterLogin = true + } + + _uiState.value = _uiState.value?.copy(navigateToSignIn = true) + ?: initialUiState.copy(navigateToSignIn = true) + } + + private fun shouldFinishAfterLogin(authObjects: List): Boolean { + return closeAfterLogin && authObjects.all { it.result.isSuccess() } + } + + /* + * Notification functions + */ + + fun createNotificationChannels() { + appManagerWrapper.createNotificationChannels() + } + + /* + * Download and cancellation functions + */ + + /* + * Check and display a snack bar if app is paid and user is logged in in anonymous mode. + * Returns true if the snack bar was displayed, false otherwise. + * + * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/266 + */ + fun shouldShowPaidAppsSnackBar(app: Application): Boolean { + val authData = appLoungeDataStore.getAuthData() + if (!app.isFree && authData.isAnonymous) { + _errorMessageStringResource.value = R.string.paid_app_anonymous_message + return true + } + + return false + } + + fun shouldShowPaidAppsSnackBar(app: ApplicationDomain): Boolean { + return shouldShowPaidAppsSnackBar(app.toApplication()) + } + + /** + * Handle various cases of unsupported apps here. + * Returns true if the [application] is not supported by App Lounge. + * + * Pass [alertDialogContext] as null to prevent an alert dialog from being shown to the user. + * In that case, this method simply works as a validation. + * + * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/178 + */ + fun checkUnsupportedApplication( + application: Application, + alertDialogContext: Context? = null + ): Boolean { + if (!application.filterLevel.isUnFiltered()) { + alertDialogContext?.let { context -> + AlertDialog.Builder(context).apply { + setTitle(R.string.unsupported_app_title) + setMessage( + context.getString( + R.string.unsupported_app_unreleased, + application.name + ) + ) + setPositiveButton(android.R.string.ok, null) + }.show() + } + return true + } + return false + } + + fun checkUnsupportedApplication( + homeApp: ApplicationDomain, + alertDialogContext: Context? = null + ): Boolean { + if (!homeApp.filterLevel.isUnFiltered()) { + alertDialogContext?.let { context -> + AlertDialog.Builder(context).apply { + setTitle(R.string.unsupported_app_title) + setMessage( + context.getString( + R.string.unsupported_app_unreleased, + homeApp.name + ) + ) + setPositiveButton(android.R.string.ok, null) + }.show() + } + return true + } + return false + } + + /** + * Fetch the filter level of an app and perform some action. + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5720 + */ + fun verifyUiFilter(application: Application, method: () -> Unit) { + viewModelScope.launch { + if (application.filterLevel.isInitialized()) { + method() + } else { + applicationRepository.getAppFilterLevel(application).run { + if (isInitialized()) { + application.filterLevel = this + method() + } + } + } + } + } + + fun getApplication(app: Application) { + viewModelScope.launch(Dispatchers.IO) { + appInstallProcessor.initAppInstall(app) + } + } + + fun getApplication(homeApp: ApplicationDomain) { + getApplication(homeApp.toApplication()) + } + + suspend fun updateAwaitingForPurchasedApp(packageName: String): AppInstall? { + val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) + val authData = appLoungeDataStore.getAuthData() + if (!authData.isAnonymous) { + appInstallProcessor.enqueueFusedDownload(fusedDownload) + return fusedDownload + } + + return null + } + + suspend fun updateUnavailableForPurchaseDeclined(packageName: String) { + val fusedDownload = appManagerWrapper.getFusedDownload(packageName = packageName) + appManagerWrapper.updateUnavailable(fusedDownload) + } + + fun cancelDownload(app: Application) { + viewModelScope.launch { + val fusedDownload = + appManagerWrapper.getFusedDownload(packageName = app.package_name) + appManagerWrapper.cancelDownload(fusedDownload) + } + } + + fun cancelDownload(homeApp: ApplicationDomain) { + cancelDownload(homeApp.toApplication()) + } + + fun setupConnectivityManager(context: Context) { + internetConnection = NetworkStatusManager.init(context) + } + + fun updateStatusOfFusedApps( + applicationList: List, + appInstallList: List + ) { + applicationList.forEach { + val downloadingItem = appInstallList.find { fusedDownload -> + fusedDownload.source == it.source && + (fusedDownload.packageName == it.package_name || fusedDownload.id == it._id) + } + it.status = + downloadingItem?.status ?: applicationRepository.getFusedAppInstallationStatus(it) + } + } + + fun updateStatusOfApplicationDomains( + homeApps: List, + appInstallList: List + ): List { + return homeApps.map { homeApp -> + val downloadingItem = appInstallList.find { fusedDownload -> + fusedDownload.source == homeApp.source && + (fusedDownload.packageName == homeApp.packageName || fusedDownload.id == homeApp.id) + } + val status = downloadingItem?.status + ?: applicationRepository.getFusedAppInstallationStatus(homeApp.toApplication()) + homeApp.copy(status = status) + } + } + + fun updateAppWarningList() { + viewModelScope.launch { + blockedAppRepository.fetchUpdateOfAppWarningList() + } + } + + fun updateContentRatings() { + viewModelScope.launch { + fDroidAntiFeatureRepository.fetchNsfwApps() + gPlayContentRatingRepository.fetchContentRatingData() + } + } + + fun fetchUpdatableSystemAppsList() { + viewModelScope.launch { + systemAppsUpdatesRepository.fetchUpdatableSystemApps() + } + } + + fun getAppNameByPackageName(packageName: String): String { + return appLoungePackageManager.getAppNameFromPackageName(packageName) + } + + fun getLaunchIntentForPackageName(packageName: String): Intent? { + return appLoungePackageManager.getLaunchIntent(packageName) + } + + fun launchPwa(application: Application) { + pwaManager.launchPwa(application) + } + + fun launchPwa(homeApp: ApplicationDomain) { + launchPwa(homeApp.toApplication()) + } + + fun handleRatingFormat(rating: Double): String? { + return appManagerWrapper.handleRatingFormat(rating) + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsDiffUtil.kt b/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsDiffUtil.kt index d68ba25f8f707c1d12f8f253e2f2fa7540d9c24a..22e385491c03b651612acef92a11b18e2d0f718f 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsDiffUtil.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsDiffUtil.kt @@ -1,42 +1,42 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.application.model - -import androidx.recyclerview.widget.DiffUtil - -class ApplicationScreenshotsDiffUtil( - private val oldList: List, - private val newList: List, -) : DiffUtil.Callback() { - override fun getOldListSize(): Int { - return oldList.size - } - - override fun getNewListSize(): Int { - return newList.size - } - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldList[oldItemPosition] == newList[newItemPosition] - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldList[oldItemPosition] == newList[newItemPosition] - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.application.model + +import androidx.recyclerview.widget.DiffUtil + +class ApplicationScreenshotsDiffUtil( + private val oldList: List, + private val newList: List, +) : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return oldList.size + } + + override fun getNewListSize(): Int { + return newList.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] == newList[newItemPosition] + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition] == newList[newItemPosition] + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsRVAdapter.kt index 6119dfb12a89d9748a9b8087b3fdedf38199730b..d26e553403d1ca5b1b172ef46c460036672cb647 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/model/ApplicationScreenshotsRVAdapter.kt @@ -1,89 +1,89 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.application.model - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.navigation.findNavController -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import coil.load -import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.Source -import foundation.e.apps.databinding.ApplicationScreenshotsListItemBinding -import foundation.e.apps.ui.application.ApplicationFragmentDirections - -class ApplicationScreenshotsRVAdapter( - private val source: Source -) : - RecyclerView.Adapter() { - - private var oldList = emptyList() - - inner class ViewHolder(val binding: ApplicationScreenshotsListItemBinding) : - RecyclerView.ViewHolder(binding.root) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder( - ApplicationScreenshotsListItemBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val imageView = holder.binding.imageView - when (source) { - Source.PWA -> { - imageView.load(CleanApkRetrofit.ASSET_URL + oldList[position]) - } - Source.OPEN_SOURCE -> { - imageView.load(CleanApkRetrofit.ASSET_URL + oldList[position]) - } - Source.PLAY_STORE -> { - imageView.load(oldList[position]) - } - Source.SYSTEM_APP -> { - // no operation - } - } - imageView.setOnClickListener { - val action = - ApplicationFragmentDirections.actionApplicationFragmentToScreenshotFragment( - oldList.toTypedArray(), - position, - source - ) - it.findNavController().navigate(action) - } - } - - override fun getItemCount(): Int { - return oldList.size - } - - fun setData(newList: List) { - val diffUtil = ApplicationScreenshotsDiffUtil(oldList, newList) - val diffResult = DiffUtil.calculateDiff(diffUtil) - oldList = newList - diffResult.dispatchUpdatesTo(this) - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.application.model + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.navigation.findNavController +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import coil.load +import foundation.e.apps.data.cleanapk.CleanApkRetrofit +import foundation.e.apps.data.enums.Source +import foundation.e.apps.databinding.ApplicationScreenshotsListItemBinding +import foundation.e.apps.ui.application.ApplicationFragmentDirections + +class ApplicationScreenshotsRVAdapter( + private val source: Source +) : + RecyclerView.Adapter() { + + private var oldList = emptyList() + + inner class ViewHolder(val binding: ApplicationScreenshotsListItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + ApplicationScreenshotsListItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val imageView = holder.binding.imageView + when (source) { + Source.PWA -> { + imageView.load(CleanApkRetrofit.ASSET_URL + oldList[position]) + } + Source.OPEN_SOURCE -> { + imageView.load(CleanApkRetrofit.ASSET_URL + oldList[position]) + } + Source.PLAY_STORE -> { + imageView.load(oldList[position]) + } + Source.SYSTEM_APP -> { + // no operation + } + } + imageView.setOnClickListener { + val action = + ApplicationFragmentDirections.actionApplicationFragmentToScreenshotFragment( + oldList.toTypedArray(), + position, + source + ) + it.findNavController().navigate(action) + } + } + + override fun getItemCount(): Int { + return oldList.size + } + + fun setData(newList: List) { + val diffUtil = ApplicationScreenshotsDiffUtil(oldList, newList) + val diffResult = DiffUtil.calculateDiff(diffUtil) + oldList = newList + diffResult.dispatchUpdatesTo(this) + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/application/model/DepthPageTransformer.kt b/app/src/main/java/foundation/e/apps/ui/application/model/DepthPageTransformer.kt index f9491f409a7c5e5405ccd50a7c339eb36c938716..a25e4a357c433eddbede502971577c143843afa2 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/model/DepthPageTransformer.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/model/DepthPageTransformer.kt @@ -1,28 +1,28 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.application.model - -import android.view.View -import androidx.viewpager2.widget.ViewPager2 -import kotlin.math.abs - -/** +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.application.model + +import android.view.View +import androidx.viewpager2.widget.ViewPager2 +import kotlin.math.abs + +/** * Code taken from https://developer.android.com/training/animation/screen-slide-2 * with comments removed */ @@ -30,33 +30,33 @@ class DepthPageTransformer : ViewPager2.PageTransformer { private companion object { private const val MIN_SCALE = 0.75f } - - override fun transformPage(view: View, position: Float) { - view.apply { - val pageWidth = width - when { - position < -1 -> { - alpha = 0f - } - position <= 0 -> { - alpha = 1f - translationX = 0f - translationZ = 0f - scaleX = 1f - scaleY = 1f - } - position <= 1 -> { - val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - abs(position))) - alpha = 1 - position - translationX = pageWidth * -position - translationZ = -1f - scaleX = scaleFactor - scaleY = scaleFactor - } - else -> { - alpha = 0f - } - } - } - } -} + + override fun transformPage(view: View, position: Float) { + view.apply { + val pageWidth = width + when { + position < -1 -> { + alpha = 0f + } + position <= 0 -> { + alpha = 1f + translationX = 0f + translationZ = 0f + scaleX = 1f + scaleY = 1f + } + position <= 1 -> { + val scaleFactor = (MIN_SCALE + (1 - MIN_SCALE) * (1 - abs(position))) + alpha = 1 - position + translationX = pageWidth * -position + translationZ = -1f + scaleX = scaleFactor + scaleY = scaleFactor + } + else -> { + alpha = 0f + } + } + } + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/application/model/ScreenshotRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/application/model/ScreenshotRVAdapter.kt index 1ce2a4c90349d9c9073cb1590ab7a38994182796..670c65fc9d7c94cdae296f779ccb705ca0605b19 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/model/ScreenshotRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/model/ScreenshotRVAdapter.kt @@ -1,81 +1,81 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.application.model - -import android.graphics.PorterDuff -import android.graphics.PorterDuffColorFilter -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import androidx.swiperefreshlayout.widget.CircularProgressDrawable -import coil.load -import foundation.e.apps.R -import foundation.e.apps.data.cleanapk.CleanApkRetrofit -import foundation.e.apps.data.enums.Source -import foundation.e.apps.databinding.ScreenshotListItemBinding - -class ScreenshotRVAdapter(private val list: List, private val source: Source) : - RecyclerView.Adapter() { - - private lateinit var circularProgressDrawable: CircularProgressDrawable - - inner class ViewHolder(val binding: ScreenshotListItemBinding) : - RecyclerView.ViewHolder(binding.root) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.application.model + +import android.graphics.PorterDuff +import android.graphics.PorterDuffColorFilter +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.CircularProgressDrawable +import coil.load +import foundation.e.apps.R +import foundation.e.apps.data.cleanapk.CleanApkRetrofit +import foundation.e.apps.data.enums.Source +import foundation.e.apps.databinding.ScreenshotListItemBinding + +class ScreenshotRVAdapter(private val list: List, private val source: Source) : + RecyclerView.Adapter() { + + private lateinit var circularProgressDrawable: CircularProgressDrawable + + inner class ViewHolder(val binding: ScreenshotListItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Setup progress drawable for coil placeholder circularProgressDrawable = CircularProgressDrawable(parent.context) circularProgressDrawable.strokeWidth = PROGRESS_STROKE_WIDTH circularProgressDrawable.centerRadius = PROGRESS_CENTER_RADIUS - circularProgressDrawable.colorFilter = PorterDuffColorFilter( - parent.context.getColor(R.color.colorAccent), - PorterDuff.Mode.SRC_IN - ) - - return ViewHolder( - ScreenshotListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val imageView = holder.binding.imageView - when (source) { - Source.PWA -> { - imageView.load(CleanApkRetrofit.ASSET_URL + list[position]) { - placeholder(circularProgressDrawable) - } - } - Source.OPEN_SOURCE -> { - imageView.load(CleanApkRetrofit.ASSET_URL + list[position]) { - placeholder(circularProgressDrawable) - } - } - Source.PLAY_STORE -> { - imageView.load(list[position]) { - placeholder(circularProgressDrawable) - } - } - - else -> {} - } - } - + circularProgressDrawable.colorFilter = PorterDuffColorFilter( + parent.context.getColor(R.color.colorAccent), + PorterDuff.Mode.SRC_IN + ) + + return ViewHolder( + ScreenshotListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val imageView = holder.binding.imageView + when (source) { + Source.PWA -> { + imageView.load(CleanApkRetrofit.ASSET_URL + list[position]) { + placeholder(circularProgressDrawable) + } + } + Source.OPEN_SOURCE -> { + imageView.load(CleanApkRetrofit.ASSET_URL + list[position]) { + placeholder(circularProgressDrawable) + } + } + Source.PLAY_STORE -> { + imageView.load(list[position]) { + placeholder(circularProgressDrawable) + } + } + + else -> {} + } + } + override fun getItemCount(): Int { return list.size } diff --git a/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt b/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt index fff5f5d158d24e0d48340611033e81636423d8d5..d7be4e8bbeff0774f0220e4ff4c12c8611ebde7a 100644 --- a/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/application/subFrags/ApplicationDialogFragment.kt @@ -1,39 +1,39 @@ -/* - * Copyright (C) 2021-2024 MURENA SAS - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.ui.application.subFrags - -import android.app.Dialog -import android.content.DialogInterface -import android.graphics.drawable.Drawable -import android.os.Bundle -import android.text.Html -import android.text.SpannableString -import android.text.TextPaint -import android.text.method.LinkMovementMethod -import android.text.style.URLSpan -import android.widget.TextView -import androidx.annotation.DrawableRes -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import dagger.hilt.android.AndroidEntryPoint -import foundation.e.apps.R - +/* + * Copyright (C) 2021-2024 MURENA SAS + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.ui.application.subFrags + +import android.app.Dialog +import android.content.DialogInterface +import android.graphics.drawable.Drawable +import android.os.Bundle +import android.text.Html +import android.text.SpannableString +import android.text.TextPaint +import android.text.method.LinkMovementMethod +import android.text.style.URLSpan +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import dagger.hilt.android.AndroidEntryPoint +import foundation.e.apps.R + @AndroidEntryPoint class ApplicationDialogFragment() : DialogFragment() { @@ -78,51 +78,51 @@ class ApplicationDialogFragment() : DialogFragment() { fun setOnDialogClosedListener(listener: () -> Unit) { onDismissListener = listener } - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val positiveButtonText = - positiveButtonText?.ifEmpty { getString(R.string.ok) } - val materialAlertDialogBuilder = MaterialAlertDialogBuilder(requireContext()) - .setTitle(Html.fromHtml(title ?: "", Html.FROM_HTML_MODE_COMPACT)) - .setMessage(Html.fromHtml(message ?: "", Html.FROM_HTML_MODE_COMPACT)) - .setPositiveButton(positiveButtonText) { _, _ -> - positiveButtonAction?.invoke() - this.dismiss() - } - if (cancelButtonText?.isNotEmpty() == true) { - materialAlertDialogBuilder.setNegativeButton(cancelButtonText) { _, _ -> - cancelButtonAction?.invoke() - this.dismiss() - } - } - if (drawableResId != -1) { - materialAlertDialogBuilder.setIcon(drawableResId) - } - - if (drawableResId == -1 && drawable != null) { - materialAlertDialogBuilder.setIcon(drawable) - } - - return materialAlertDialogBuilder.create() - } - - override fun onResume() { - super.onResume() - dialog?.findViewById(android.R.id.message)?.apply { - movementMethod = LinkMovementMethod.getInstance() - isClickable = true - removeUnderlineFromLinks() - } - } - - override fun onDismiss(dialog: DialogInterface) { - super.onDismiss(dialog) - onDismissListener?.invoke() - } - - private fun TextView.removeUnderlineFromLinks() { - val spannable = SpannableString(text) - for (urlSpan in spannable.getSpans(0, spannable.length, URLSpan::class.java)) { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val positiveButtonText = + positiveButtonText?.ifEmpty { getString(R.string.ok) } + val materialAlertDialogBuilder = MaterialAlertDialogBuilder(requireContext()) + .setTitle(Html.fromHtml(title ?: "", Html.FROM_HTML_MODE_COMPACT)) + .setMessage(Html.fromHtml(message ?: "", Html.FROM_HTML_MODE_COMPACT)) + .setPositiveButton(positiveButtonText) { _, _ -> + positiveButtonAction?.invoke() + this.dismiss() + } + if (cancelButtonText?.isNotEmpty() == true) { + materialAlertDialogBuilder.setNegativeButton(cancelButtonText) { _, _ -> + cancelButtonAction?.invoke() + this.dismiss() + } + } + if (drawableResId != -1) { + materialAlertDialogBuilder.setIcon(drawableResId) + } + + if (drawableResId == -1 && drawable != null) { + materialAlertDialogBuilder.setIcon(drawable) + } + + return materialAlertDialogBuilder.create() + } + + override fun onResume() { + super.onResume() + dialog?.findViewById(android.R.id.message)?.apply { + movementMethod = LinkMovementMethod.getInstance() + isClickable = true + removeUnderlineFromLinks() + } + } + + override fun onDismiss(dialog: DialogInterface) { + super.onDismiss(dialog) + onDismissListener?.invoke() + } + + private fun TextView.removeUnderlineFromLinks() { + val spannable = SpannableString(text) + for (urlSpan in spannable.getSpans(0, spannable.length, URLSpan::class.java)) { spannable.setSpan( object : URLSpan(urlSpan.url) { override fun updateDrawState(textPaint: TextPaint) { diff --git a/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesDiffUtil.kt b/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesDiffUtil.kt index c2e5e6d56d79ff0bc2a1f913e3dd182b98cb1f2a..be6b2a83d79b615f094656d6cae53d32a1c60a0a 100644 --- a/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesDiffUtil.kt +++ b/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesDiffUtil.kt @@ -1,50 +1,50 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.categories.model - -import androidx.recyclerview.widget.DiffUtil -import foundation.e.apps.data.application.data.Category - -class CategoriesDiffUtil( - private val oldList: List, - private val newList: List -) : DiffUtil.Callback() { - override fun getOldListSize(): Int { - return oldList.size - } - - override fun getNewListSize(): Int { - return newList.size - } - - override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return oldList[oldItemPosition].id == newList[newItemPosition].id - } - - override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { - return when { - oldList[oldItemPosition].id != newList[newItemPosition].id -> false - oldList[oldItemPosition].title != newList[newItemPosition].title -> false - oldList[oldItemPosition].drawable != newList[newItemPosition].drawable -> false - oldList[oldItemPosition].browseUrl != newList[newItemPosition].browseUrl -> false - oldList[oldItemPosition].imageUrl != newList[newItemPosition].imageUrl -> false - else -> true - } - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.categories.model + +import androidx.recyclerview.widget.DiffUtil +import foundation.e.apps.data.application.data.Category + +class CategoriesDiffUtil( + private val oldList: List, + private val newList: List +) : DiffUtil.Callback() { + override fun getOldListSize(): Int { + return oldList.size + } + + override fun getNewListSize(): Int { + return newList.size + } + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return oldList[oldItemPosition].id == newList[newItemPosition].id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return when { + oldList[oldItemPosition].id != newList[newItemPosition].id -> false + oldList[oldItemPosition].title != newList[newItemPosition].title -> false + oldList[oldItemPosition].drawable != newList[newItemPosition].drawable -> false + oldList[oldItemPosition].browseUrl != newList[newItemPosition].browseUrl -> false + oldList[oldItemPosition].imageUrl != newList[newItemPosition].imageUrl -> false + else -> true + } + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesRVAdapter.kt index 3890be7c1758eb759faa9adf12daccfb2f125709..541af026d4046f04c4bb2fff13c7b51fa7c51700 100644 --- a/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesRVAdapter.kt @@ -1,97 +1,97 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.categories.model - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.navigation.findNavController -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.RecyclerView -import coil.load -import foundation.e.apps.data.application.data.Category -import foundation.e.apps.databinding.CategoriesListItemBinding -import foundation.e.apps.ui.categories.CategoriesFragmentDirections - -class CategoriesRVAdapter : - RecyclerView.Adapter() { - - private var oldList = listOf() - - inner class ViewHolder(val binding: CategoriesListItemBinding) : - RecyclerView.ViewHolder(binding.root) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder( - CategoriesListItemBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - holder.binding.apply { - categoryLayout.setOnClickListener { - val direction = - CategoriesFragmentDirections.actionCategoriesFragmentToApplicationListFragment( - oldList[position].id, - oldList[position].title, - oldList[position].tag.getOperationalTag(), - oldList[position].browseUrl - ) - holder.itemView.findNavController().navigate(direction) - } - loadCategoryIcon(position) - categoryTitle.text = oldList[position].title - updateTag(position) - } - } - - private fun CategoriesListItemBinding.loadCategoryIcon(position: Int) { - if (oldList[position].drawable != -1) { - categoryIcon.load(oldList[position].drawable) - } else { - categoryIcon.load(oldList[position].imageUrl) - } - } - - private fun CategoriesListItemBinding.updateTag(position: Int) { - val tag = oldList[position].tag - if (tag.displayTag.isNotBlank()) { - categoryTag.visibility = View.VISIBLE - categoryTag.text = tag.displayTag - } else { - categoryTag.visibility = View.INVISIBLE - categoryTag.text = "" - } - } - - override fun getItemCount(): Int { - return oldList.size - } - - fun setData(newList: List) { - val diffUtil = CategoriesDiffUtil(oldList, newList) - val diffResult = DiffUtil.calculateDiff(diffUtil) - oldList = newList - diffResult.dispatchUpdatesTo(this) - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.categories.model + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.navigation.findNavController +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import coil.load +import foundation.e.apps.data.application.data.Category +import foundation.e.apps.databinding.CategoriesListItemBinding +import foundation.e.apps.ui.categories.CategoriesFragmentDirections + +class CategoriesRVAdapter : + RecyclerView.Adapter() { + + private var oldList = listOf() + + inner class ViewHolder(val binding: CategoriesListItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + CategoriesListItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.binding.apply { + categoryLayout.setOnClickListener { + val direction = + CategoriesFragmentDirections.actionCategoriesFragmentToApplicationListFragment( + oldList[position].id, + oldList[position].title, + oldList[position].tag.getOperationalTag(), + oldList[position].browseUrl + ) + holder.itemView.findNavController().navigate(direction) + } + loadCategoryIcon(position) + categoryTitle.text = oldList[position].title + updateTag(position) + } + } + + private fun CategoriesListItemBinding.loadCategoryIcon(position: Int) { + if (oldList[position].drawable != -1) { + categoryIcon.load(oldList[position].drawable) + } else { + categoryIcon.load(oldList[position].imageUrl) + } + } + + private fun CategoriesListItemBinding.updateTag(position: Int) { + val tag = oldList[position].tag + if (tag.displayTag.isNotBlank()) { + categoryTag.visibility = View.VISIBLE + categoryTag.text = tag.displayTag + } else { + categoryTag.visibility = View.INVISIBLE + categoryTag.text = "" + } + } + + override fun getItemCount(): Int { + return oldList.size + } + + fun setData(newList: List) { + val diffUtil = CategoriesDiffUtil(oldList, newList) + val diffResult = DiffUtil.calculateDiff(diffUtil) + oldList = newList + diffResult.dispatchUpdatesTo(this) + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesVPAdapter.kt b/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesVPAdapter.kt index cdb8da38b1ce55daf16e5de4ed959cb76d582972..45e08e2096b933194e097845f4f003df34d4ba44 100644 --- a/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesVPAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/categories/model/CategoriesVPAdapter.kt @@ -1,39 +1,39 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.categories.model - -import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.lifecycle.Lifecycle -import androidx.viewpager2.adapter.FragmentStateAdapter -import foundation.e.apps.ui.categories.AppsFragment -import foundation.e.apps.ui.categories.GamesFragment - -class CategoriesVPAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : - FragmentStateAdapter(fragmentManager, lifecycle) { - override fun getItemCount(): Int = 2 - - override fun createFragment(position: Int): Fragment { - return when (position) { - 0 -> AppsFragment() - 1 -> GamesFragment() - else -> AppsFragment() - } - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.categories.model + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import foundation.e.apps.ui.categories.AppsFragment +import foundation.e.apps.ui.categories.GamesFragment + +class CategoriesVPAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragmentManager, lifecycle) { + override fun getItemCount(): Int = 2 + + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> AppsFragment() + 1 -> GamesFragment() + else -> AppsFragment() + } + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/error/AppUnavailableDialog.kt b/app/src/main/java/foundation/e/apps/ui/error/AppUnavailableDialog.kt index ebe1ace6d73c259af44e1637bb63ad12eee453ae..f46fef1a99081ae94ee61c300abea35baff6a6a9 100644 --- a/app/src/main/java/foundation/e/apps/ui/error/AppUnavailableDialog.kt +++ b/app/src/main/java/foundation/e/apps/ui/error/AppUnavailableDialog.kt @@ -1,24 +1,24 @@ -package foundation.e.apps.ui.error - -import android.app.Dialog -import android.os.Bundle -import androidx.fragment.app.DialogFragment -import com.google.android.material.dialog.MaterialAlertDialogBuilder -import foundation.e.apps.R - -class AppUnavailableDialog : DialogFragment() { - - override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - return MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.app_unavailable_title) - .setMessage(R.string.app_unavailable_description) - .setCancelable(false) - .setPositiveButton( - android.R.string.ok, - { dialog, _ -> - dialog.dismiss() - } - ) - .create() - } -} +package foundation.e.apps.ui.error + +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import foundation.e.apps.R + +class AppUnavailableDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.app_unavailable_title) + .setMessage(R.string.app_unavailable_description) + .setCancelable(false) + .setPositiveButton( + android.R.string.ok, + { dialog, _ -> + dialog.dismiss() + } + ) + .create() + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/home/HomeFragment.kt b/app/src/main/java/foundation/e/apps/ui/home/HomeFragment.kt index 96f1ef4ecc020d2a643857b03a5454113d85587e..b43f23bcdf5a685ae69a364bba1fe503543c0d96 100644 --- a/app/src/main/java/foundation/e/apps/ui/home/HomeFragment.kt +++ b/app/src/main/java/foundation/e/apps/ui/home/HomeFragment.kt @@ -1,95 +1,95 @@ -/* - * Copyright (C) 2021-2025 e Foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.ui.home - -import android.os.Bundle -import android.view.View -import androidx.fragment.app.Fragment -import androidx.fragment.app.activityViewModels -import androidx.fragment.app.viewModels -import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import dagger.hilt.android.AndroidEntryPoint -import foundation.e.apps.R -import foundation.e.apps.data.enums.Status -import foundation.e.apps.databinding.FragmentHomeBinding -import foundation.e.apps.domain.application.ApplicationDomain -import foundation.e.apps.install.download.data.DownloadProgress -import foundation.e.apps.ui.AppInfoFetchViewModel -import foundation.e.apps.ui.AppProgressViewModel -import foundation.e.apps.ui.MainActivityViewModel -import foundation.e.apps.ui.application.subFrags.ApplicationDialogFragment -import foundation.e.apps.ui.home.model.HomeChildRVAdapter -import foundation.e.apps.ui.home.model.HomeParentRVAdapter -import kotlinx.coroutines.launch -import java.util.Locale - -@AndroidEntryPoint -class HomeFragment : Fragment(R.layout.fragment_home) { - - /* - * Make adapter nullable to avoid memory leaks. - * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 - */ - private var homeParentRVAdapter: HomeParentRVAdapter? = null - private var _binding: FragmentHomeBinding? = null - private val binding get() = _binding!! - - private val homeViewModel: HomeViewModel by viewModels() - val mainActivityViewModel: MainActivityViewModel by activityViewModels() - private val appProgressViewModel: AppProgressViewModel by viewModels() - private val appInfoFetchViewModel: AppInfoFetchViewModel by viewModels() - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - _binding = FragmentHomeBinding.bind(view) - - loadData() - - homeParentRVAdapter = initHomeParentRVAdapter() - - binding.parentRV.apply { - adapter = homeParentRVAdapter - layoutManager = LinearLayoutManager(view.context) - } - - observeHomeScreenData() - } - - private fun observeHomeScreenData() { - homeViewModel.homeScreenData.observe(viewLifecycleOwner) { - stopLoadingUI() - if (!it.isSuccess()) { - return@observe - } - - homeParentRVAdapter?.setData(it.data) - } - } - - private fun initHomeParentRVAdapter() = HomeParentRVAdapter( - mainActivityViewModel, - appInfoFetchViewModel, - viewLifecycleOwner, - { installApplication(it) }, - { cancelDownload(it) }, +/* + * Copyright (C) 2021-2025 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.ui.home + +import android.os.Bundle +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import dagger.hilt.android.AndroidEntryPoint +import foundation.e.apps.R +import foundation.e.apps.data.enums.Status +import foundation.e.apps.databinding.FragmentHomeBinding +import foundation.e.apps.domain.application.ApplicationDomain +import foundation.e.apps.install.download.data.DownloadProgress +import foundation.e.apps.ui.AppInfoFetchViewModel +import foundation.e.apps.ui.AppProgressViewModel +import foundation.e.apps.ui.MainActivityViewModel +import foundation.e.apps.ui.application.subFrags.ApplicationDialogFragment +import foundation.e.apps.ui.home.model.HomeChildRVAdapter +import foundation.e.apps.ui.home.model.HomeParentRVAdapter +import kotlinx.coroutines.launch +import java.util.Locale + +@AndroidEntryPoint +class HomeFragment : Fragment(R.layout.fragment_home) { + + /* + * Make adapter nullable to avoid memory leaks. + * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 + */ + private var homeParentRVAdapter: HomeParentRVAdapter? = null + private var _binding: FragmentHomeBinding? = null + private val binding get() = _binding!! + + private val homeViewModel: HomeViewModel by viewModels() + val mainActivityViewModel: MainActivityViewModel by activityViewModels() + private val appProgressViewModel: AppProgressViewModel by viewModels() + private val appInfoFetchViewModel: AppInfoFetchViewModel by viewModels() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentHomeBinding.bind(view) + + loadData() + + homeParentRVAdapter = initHomeParentRVAdapter() + + binding.parentRV.apply { + adapter = homeParentRVAdapter + layoutManager = LinearLayoutManager(view.context) + } + + observeHomeScreenData() + } + + private fun observeHomeScreenData() { + homeViewModel.homeScreenData.observe(viewLifecycleOwner) { + stopLoadingUI() + if (!it.isSuccess()) { + return@observe + } + + homeParentRVAdapter?.setData(it.data) + } + } + + private fun initHomeParentRVAdapter() = HomeParentRVAdapter( + mainActivityViewModel, + appInfoFetchViewModel, + viewLifecycleOwner, + { installApplication(it) }, + { cancelDownload(it) }, ) { fusedApp -> if (!mainActivityViewModel.shouldShowPaidAppsSnackBar(fusedApp)) { showPaidAppMessage(fusedApp) @@ -99,119 +99,119 @@ class HomeFragment : Fragment(R.layout.fragment_home) { private fun showPaidAppMessage(application: ApplicationDomain) { ApplicationDialogFragment( title = getString(R.string.dialog_title_paid_app, application.name), - message = getString( - R.string.dialog_paidapp_message, - application.name, - application.price - ), - positiveButtonText = getString(R.string.dialog_confirm), - positiveButtonAction = { - installApplication(application) - }, - cancelButtonText = getString(R.string.dialog_cancel), - ).show(childFragmentManager, "HomeFragment") - } - - private fun loadData() { - if (shouldLoadData()) { - showLoadingUI() - homeViewModel.getHomeScreenData(viewLifecycleOwner) - } - } - - private fun shouldLoadData() = homeViewModel.haveSourcesChanged() || !homeViewModel.hasData() - - private fun showLoadingUI() { - binding.shimmerLayout.startShimmer() - binding.shimmerLayout.visibility = View.VISIBLE - binding.parentRV.visibility = View.GONE - } - - private fun stopLoadingUI() { - binding.shimmerLayout.stopShimmer() - binding.shimmerLayout.visibility = View.GONE - binding.parentRV.visibility = View.VISIBLE - } - - private fun updateProgressOfDownloadingAppItemViews( - homeParentRVAdapter: HomeParentRVAdapter?, - downloadProgress: DownloadProgress - ) { - homeParentRVAdapter?.currentList?.forEach { fusedHome -> - val viewHolder = binding.parentRV.findViewHolderForAdapterPosition( - homeParentRVAdapter.currentList.indexOf(fusedHome) - ) - viewHolder?.let { parentViewHolder -> - val childRV = - (parentViewHolder as HomeParentRVAdapter.ViewHolder).binding.childRV - (childRV.adapter as HomeChildRVAdapter?)?.let { - findDownloadingItemsToShowProgress(it, downloadProgress, childRV) - } - } - } - } - - private fun findDownloadingItemsToShowProgress( - adapter: HomeChildRVAdapter, - downloadProgress: DownloadProgress, - childRV: RecyclerView - ) { - viewLifecycleOwner.lifecycleScope.launch { - updateDownloadProgressOfAppList(adapter, downloadProgress, childRV) - } - } - - private suspend fun updateDownloadProgressOfAppList( - adapter: HomeChildRVAdapter, - downloadProgress: DownloadProgress, - childRV: RecyclerView - ) { - adapter.currentList.forEach { fusedApp -> - if (fusedApp.status !in Status.downloadStatuses) { - return@forEach - } - - val progress = appProgressViewModel.calculateProgress(fusedApp, downloadProgress) - if (progress == -1) { - return@forEach - } - val childViewHolder = childRV.findViewHolderForAdapterPosition( - adapter.currentList.indexOf(fusedApp) - ) - childViewHolder?.let { - (childViewHolder as HomeChildRVAdapter.ViewHolder).binding.installButton.text = - String.format(Locale.getDefault(), "%d%%", progress) - } - } - } - - override fun onResume() { - super.onResume() - appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { - updateProgressOfDownloadingAppItemViews(homeParentRVAdapter, it) - } - } - - override fun onPause() { - binding.shimmerLayout.stopShimmer() - super.onPause() - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - /* - * Nullify adapter to avoid leaks. - * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 - */ - homeParentRVAdapter = null - } - - private fun installApplication(app: ApplicationDomain) { - mainActivityViewModel.getApplication(app) - } - - private fun cancelDownload(app: ApplicationDomain) { - mainActivityViewModel.cancelDownload(app) - } -} + message = getString( + R.string.dialog_paidapp_message, + application.name, + application.price + ), + positiveButtonText = getString(R.string.dialog_confirm), + positiveButtonAction = { + installApplication(application) + }, + cancelButtonText = getString(R.string.dialog_cancel), + ).show(childFragmentManager, "HomeFragment") + } + + private fun loadData() { + if (shouldLoadData()) { + showLoadingUI() + homeViewModel.getHomeScreenData(viewLifecycleOwner) + } + } + + private fun shouldLoadData() = homeViewModel.haveSourcesChanged() || !homeViewModel.hasData() + + private fun showLoadingUI() { + binding.shimmerLayout.startShimmer() + binding.shimmerLayout.visibility = View.VISIBLE + binding.parentRV.visibility = View.GONE + } + + private fun stopLoadingUI() { + binding.shimmerLayout.stopShimmer() + binding.shimmerLayout.visibility = View.GONE + binding.parentRV.visibility = View.VISIBLE + } + + private fun updateProgressOfDownloadingAppItemViews( + homeParentRVAdapter: HomeParentRVAdapter?, + downloadProgress: DownloadProgress + ) { + homeParentRVAdapter?.currentList?.forEach { fusedHome -> + val viewHolder = binding.parentRV.findViewHolderForAdapterPosition( + homeParentRVAdapter.currentList.indexOf(fusedHome) + ) + viewHolder?.let { parentViewHolder -> + val childRV = + (parentViewHolder as HomeParentRVAdapter.ViewHolder).binding.childRV + (childRV.adapter as HomeChildRVAdapter?)?.let { + findDownloadingItemsToShowProgress(it, downloadProgress, childRV) + } + } + } + } + + private fun findDownloadingItemsToShowProgress( + adapter: HomeChildRVAdapter, + downloadProgress: DownloadProgress, + childRV: RecyclerView + ) { + viewLifecycleOwner.lifecycleScope.launch { + updateDownloadProgressOfAppList(adapter, downloadProgress, childRV) + } + } + + private suspend fun updateDownloadProgressOfAppList( + adapter: HomeChildRVAdapter, + downloadProgress: DownloadProgress, + childRV: RecyclerView + ) { + adapter.currentList.forEach { fusedApp -> + if (fusedApp.status !in Status.downloadStatuses) { + return@forEach + } + + val progress = appProgressViewModel.calculateProgress(fusedApp, downloadProgress) + if (progress == -1) { + return@forEach + } + val childViewHolder = childRV.findViewHolderForAdapterPosition( + adapter.currentList.indexOf(fusedApp) + ) + childViewHolder?.let { + (childViewHolder as HomeChildRVAdapter.ViewHolder).binding.installButton.text = + String.format(Locale.getDefault(), "%d%%", progress) + } + } + } + + override fun onResume() { + super.onResume() + appProgressViewModel.downloadProgress.observe(viewLifecycleOwner) { + updateProgressOfDownloadingAppItemViews(homeParentRVAdapter, it) + } + } + + override fun onPause() { + binding.shimmerLayout.stopShimmer() + super.onPause() + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + /* + * Nullify adapter to avoid leaks. + * Issue: https://gitlab.e.foundation/e/os/backlog/-/issues/485 + */ + homeParentRVAdapter = null + } + + private fun installApplication(app: ApplicationDomain) { + mainActivityViewModel.getApplication(app) + } + + private fun cancelDownload(app: ApplicationDomain) { + mainActivityViewModel.cancelDownload(app) + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt b/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt index cca7cb73059d3bd03dbfe3046a08bc5c71c1f7b6..025a3026083a03877e95999f1d134da4b8ae3cc9 100644 --- a/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/home/HomeViewModel.kt @@ -1,140 +1,140 @@ -/* - * Copyright (C) 2021-2025 e Foundation - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -package foundation.e.apps.ui.home - -import androidx.annotation.VisibleForTesting -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import foundation.e.apps.data.StoreRepository -import foundation.e.apps.data.Stores -import foundation.e.apps.data.enums.Source -import foundation.e.apps.domain.home.FetchHomeScreenDataUseCase -import foundation.e.apps.domain.home.HomeScreenResult -import foundation.e.apps.domain.home.HomeSection -import foundation.e.apps.ui.home.model.ApplicationDomainDiffUtil -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class HomeViewModel @Inject constructor( - private val fetchHomeScreenDataUseCase: FetchHomeScreenDataUseCase, - private val stores: Stores -) : ViewModel() { - - /* - * Hold list of applications, as well as application source type. - * Source type may change from user selected preference in case of timeout. - * - * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404 - */ - var homeScreenData = MutableLiveData() - - var currentHomes = emptyList() - - private var previousStores = mapOf() - - fun hasData(): Boolean { - return homeScreenData.value?.data?.isNotEmpty() ?: false - } - - fun haveSourcesChanged(): Boolean { - val newStores = stores.getStores() - if (newStores == previousStores) { - return false - } - - previousStores = newStores.toMutableMap() - return true - } - - fun getHomeScreenData(lifecycleOwner: LifecycleOwner) { - viewModelScope.launch { - fetchHomeScreenDataUseCase().observe(lifecycleOwner) { - postHomeResult(it) - - if (it.isSuccess()) { - return@observe - } - } - } - } - - private fun postHomeResult(homeResult: HomeScreenResult) { - if (shouldUpdateResult(homeResult)) { - homeScreenData.value = homeResult - // Here, homeResult.data is a mutableList which can be changed anytime. - // That's why we're setting copy of the list, so that currentHomes isn't changed, - // when homeresult.data is changed. - currentHomes = homeResult.data.map { it.deepCopy() } - return - } - } - - private fun shouldUpdateResult(homeResult: HomeScreenResult) = - (homeResult.isSuccess() && hasAnyChange(homeResult.data)) || !homeResult.isSuccess() - - @VisibleForTesting - fun hasAnyChange( - newHomes: List, - ) = currentHomes.isEmpty() || newHomes.size != currentHomes.size || compareWithNewData( - newHomes - ) - - private fun compareWithNewData(newHomes: List): Boolean { - currentHomes.forEachIndexed { index, home -> - val fusedHome = newHomes[index] - - if (!home.title.contentEquals(fusedHome.title) || !home.id.contentEquals(fusedHome.id) - || areFusedAppsUpdated(home, fusedHome) - ) { - return true - } - } - - return false - } - - private fun areFusedAppsUpdated( - oldHome: HomeSection, - newHome: HomeSection, - ) = oldHome.apps.size != newHome.apps.size || hasAppListsAnyChange(oldHome, newHome) - - private fun hasAppListsAnyChange( - oldHome: HomeSection, - newHome: HomeSection, - ): Boolean { - val fusedAppDiffUtil = ApplicationDomainDiffUtil() - - oldHome.apps.forEach { oldFusedApp -> - val indexOfOldFusedApp = oldHome.apps.indexOf(oldFusedApp) - val fusedApp = newHome.apps[indexOfOldFusedApp] - - if (!fusedAppDiffUtil.areContentsTheSame(oldFusedApp, fusedApp)) { - return true - } - } - - return false - } - - private fun HomeSection.deepCopy() = copy(apps = apps.map { it.copy() }) -} +/* + * Copyright (C) 2021-2025 e Foundation + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package foundation.e.apps.ui.home + +import androidx.annotation.VisibleForTesting +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import foundation.e.apps.data.StoreRepository +import foundation.e.apps.data.Stores +import foundation.e.apps.data.enums.Source +import foundation.e.apps.domain.home.FetchHomeScreenDataUseCase +import foundation.e.apps.domain.home.HomeScreenResult +import foundation.e.apps.domain.home.HomeSection +import foundation.e.apps.ui.home.model.ApplicationDomainDiffUtil +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class HomeViewModel @Inject constructor( + private val fetchHomeScreenDataUseCase: FetchHomeScreenDataUseCase, + private val stores: Stores +) : ViewModel() { + + /* + * Hold list of applications, as well as application source type. + * Source type may change from user selected preference in case of timeout. + * + * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404 + */ + var homeScreenData = MutableLiveData() + + var currentHomes = emptyList() + + private var previousStores = mapOf() + + fun hasData(): Boolean { + return homeScreenData.value?.data?.isNotEmpty() ?: false + } + + fun haveSourcesChanged(): Boolean { + val newStores = stores.getStores() + if (newStores == previousStores) { + return false + } + + previousStores = newStores.toMutableMap() + return true + } + + fun getHomeScreenData(lifecycleOwner: LifecycleOwner) { + viewModelScope.launch { + fetchHomeScreenDataUseCase().observe(lifecycleOwner) { + postHomeResult(it) + + if (it.isSuccess()) { + return@observe + } + } + } + } + + private fun postHomeResult(homeResult: HomeScreenResult) { + if (shouldUpdateResult(homeResult)) { + homeScreenData.value = homeResult + // Here, homeResult.data is a mutableList which can be changed anytime. + // That's why we're setting copy of the list, so that currentHomes isn't changed, + // when homeresult.data is changed. + currentHomes = homeResult.data.map { it.deepCopy() } + return + } + } + + private fun shouldUpdateResult(homeResult: HomeScreenResult) = + (homeResult.isSuccess() && hasAnyChange(homeResult.data)) || !homeResult.isSuccess() + + @VisibleForTesting + fun hasAnyChange( + newHomes: List, + ) = currentHomes.isEmpty() || newHomes.size != currentHomes.size || compareWithNewData( + newHomes + ) + + private fun compareWithNewData(newHomes: List): Boolean { + currentHomes.forEachIndexed { index, home -> + val fusedHome = newHomes[index] + + if (!home.title.contentEquals(fusedHome.title) || !home.id.contentEquals(fusedHome.id) + || areFusedAppsUpdated(home, fusedHome) + ) { + return true + } + } + + return false + } + + private fun areFusedAppsUpdated( + oldHome: HomeSection, + newHome: HomeSection, + ) = oldHome.apps.size != newHome.apps.size || hasAppListsAnyChange(oldHome, newHome) + + private fun hasAppListsAnyChange( + oldHome: HomeSection, + newHome: HomeSection, + ): Boolean { + val fusedAppDiffUtil = ApplicationDomainDiffUtil() + + oldHome.apps.forEach { oldFusedApp -> + val indexOfOldFusedApp = oldHome.apps.indexOf(oldFusedApp) + val fusedApp = newHome.apps[indexOfOldFusedApp] + + if (!fusedAppDiffUtil.areContentsTheSame(oldFusedApp, fusedApp)) { + return true + } + } + + return false + } + + private fun HomeSection.deepCopy() = copy(apps = apps.map { it.copy() }) +} diff --git a/app/src/main/java/foundation/e/apps/ui/home/model/HomeParentRVAdapter.kt b/app/src/main/java/foundation/e/apps/ui/home/model/HomeParentRVAdapter.kt index f76572103e96f64d5af253256f0633ff663a4288..79b4bd210b6ee5b226fd38efa94b72b83d3a1ef1 100644 --- a/app/src/main/java/foundation/e/apps/ui/home/model/HomeParentRVAdapter.kt +++ b/app/src/main/java/foundation/e/apps/ui/home/model/HomeParentRVAdapter.kt @@ -1,133 +1,133 @@ -/* - * Apps Quickly and easily install Android apps onto your device! - * Copyright (C) 2021 E FOUNDATION - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package foundation.e.apps.ui.home.model - -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.lifecycle.LifecycleOwner -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import foundation.e.apps.databinding.HomeParentListItemBinding -import foundation.e.apps.domain.application.ApplicationDomain -import foundation.e.apps.domain.home.HomeSection -import foundation.e.apps.ui.AppInfoFetchViewModel -import foundation.e.apps.ui.MainActivityViewModel - -class HomeParentRVAdapter( - private val mainActivityViewModel: MainActivityViewModel, - private val appInfoFetchViewModel: AppInfoFetchViewModel, - private var lifecycleOwner: LifecycleOwner?, - private val installHandler: (ApplicationDomain) -> Unit, - private val cancelHandler: (ApplicationDomain) -> Unit, - private val paidAppHandler: ((ApplicationDomain) -> Unit)? = null -) : ListAdapter(FusedHomeDiffUtil()) { - - private val viewPool = RecyclerView.RecycledViewPool() - private var isDetachedFromRecyclerView = false - - inner class ViewHolder(val binding: HomeParentListItemBinding) : - RecyclerView.ViewHolder(binding.root) - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - return ViewHolder( - HomeParentListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) - } - - override fun onBindViewHolder(holder: ViewHolder, position: Int) { - val fusedHome = getItem(position) - - holder.binding.titleTV.text = fusedHome.title - handleChildShimmerView(fusedHome, holder) - - if (fusedHome.apps.isEmpty()) { - return - } - - val homeChildRVAdapter = - HomeChildRVAdapter( - appInfoFetchViewModel, - mainActivityViewModel, - lifecycleOwner, - installHandler, - cancelHandler, - paidAppHandler - ) - - homeChildRVAdapter.setData(fusedHome.apps) - - holder.binding.childRV.apply { - recycledViewPool.setMaxRecycledViews(0, 0) - adapter = homeChildRVAdapter - layoutManager = - LinearLayoutManager( - holder.binding.root.context, - LinearLayoutManager.HORIZONTAL, - false - ) - setRecycledViewPool(viewPool) - } - - observeAppInstall(fusedHome, homeChildRVAdapter) - } - - private fun handleChildShimmerView(home: HomeSection, holder: ViewHolder) { - if (home.apps.isEmpty()) { - holder.binding.shimmerLayout.visibility = View.VISIBLE - holder.binding.shimmerLayout.startShimmer() - holder.binding.childRV.visibility = View.GONE - return - } - - holder.binding.shimmerLayout.visibility = View.GONE - holder.binding.shimmerLayout.stopShimmer() - holder.binding.childRV.visibility = View.VISIBLE - } - - private fun observeAppInstall( - home: HomeSection, - homeChildRVAdapter: RecyclerView.Adapter<*>? - ) { - lifecycleOwner?.let { - mainActivityViewModel.downloadList.observe(it) { - val updatedApps = mainActivityViewModel.updateStatusOfApplicationDomains(home.apps, it) - (homeChildRVAdapter as HomeChildRVAdapter).setData(updatedApps) - } - } - } - - fun setData(newList: List) { - submitList(newList.map { it.copy(apps = it.apps.map { app -> app.copy() }) }) - } - - override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { - super.onDetachedFromRecyclerView(recyclerView) - isDetachedFromRecyclerView = true - lifecycleOwner = null - } - - override fun onViewDetachedFromWindow(holder: ViewHolder) { - super.onViewDetachedFromWindow(holder) - if (isDetachedFromRecyclerView) { - holder.binding.childRV.adapter = null - } - } -} +/* + * Apps Quickly and easily install Android apps onto your device! + * Copyright (C) 2021 E FOUNDATION + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package foundation.e.apps.ui.home.model + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import foundation.e.apps.databinding.HomeParentListItemBinding +import foundation.e.apps.domain.application.ApplicationDomain +import foundation.e.apps.domain.home.HomeSection +import foundation.e.apps.ui.AppInfoFetchViewModel +import foundation.e.apps.ui.MainActivityViewModel + +class HomeParentRVAdapter( + private val mainActivityViewModel: MainActivityViewModel, + private val appInfoFetchViewModel: AppInfoFetchViewModel, + private var lifecycleOwner: LifecycleOwner?, + private val installHandler: (ApplicationDomain) -> Unit, + private val cancelHandler: (ApplicationDomain) -> Unit, + private val paidAppHandler: ((ApplicationDomain) -> Unit)? = null +) : ListAdapter(FusedHomeDiffUtil()) { + + private val viewPool = RecyclerView.RecycledViewPool() + private var isDetachedFromRecyclerView = false + + inner class ViewHolder(val binding: HomeParentListItemBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + return ViewHolder( + HomeParentListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + } + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val fusedHome = getItem(position) + + holder.binding.titleTV.text = fusedHome.title + handleChildShimmerView(fusedHome, holder) + + if (fusedHome.apps.isEmpty()) { + return + } + + val homeChildRVAdapter = + HomeChildRVAdapter( + appInfoFetchViewModel, + mainActivityViewModel, + lifecycleOwner, + installHandler, + cancelHandler, + paidAppHandler + ) + + homeChildRVAdapter.setData(fusedHome.apps) + + holder.binding.childRV.apply { + recycledViewPool.setMaxRecycledViews(0, 0) + adapter = homeChildRVAdapter + layoutManager = + LinearLayoutManager( + holder.binding.root.context, + LinearLayoutManager.HORIZONTAL, + false + ) + setRecycledViewPool(viewPool) + } + + observeAppInstall(fusedHome, homeChildRVAdapter) + } + + private fun handleChildShimmerView(home: HomeSection, holder: ViewHolder) { + if (home.apps.isEmpty()) { + holder.binding.shimmerLayout.visibility = View.VISIBLE + holder.binding.shimmerLayout.startShimmer() + holder.binding.childRV.visibility = View.GONE + return + } + + holder.binding.shimmerLayout.visibility = View.GONE + holder.binding.shimmerLayout.stopShimmer() + holder.binding.childRV.visibility = View.VISIBLE + } + + private fun observeAppInstall( + home: HomeSection, + homeChildRVAdapter: RecyclerView.Adapter<*>? + ) { + lifecycleOwner?.let { + mainActivityViewModel.downloadList.observe(it) { + val updatedApps = mainActivityViewModel.updateStatusOfApplicationDomains(home.apps, it) + (homeChildRVAdapter as HomeChildRVAdapter).setData(updatedApps) + } + } + } + + fun setData(newList: List) { + submitList(newList.map { it.copy(apps = it.apps.map { app -> app.copy() }) }) + } + + override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) { + super.onDetachedFromRecyclerView(recyclerView) + isDetachedFromRecyclerView = true + lifecycleOwner = null + } + + override fun onViewDetachedFromWindow(holder: ViewHolder) { + super.onViewDetachedFromWindow(holder) + if (isDetachedFromRecyclerView) { + holder.binding.childRV.adapter = null + } + } +} diff --git a/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt b/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt index f12caf9cd4c1d3eefc94328774b18f19f9c41eda..7bff8a487aa9c55aee12925e668422034741cff3 100644 --- a/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/setup/signin/SignInViewModel.kt @@ -1,21 +1,21 @@ -package foundation.e.apps.ui.setup.signin - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import com.aurora.gplayapi.data.models.AuthData -import dagger.hilt.android.lifecycle.HiltViewModel -import foundation.e.apps.data.preference.AppLoungeDataStore -import javax.inject.Inject - -@HiltViewModel -class SignInViewModel @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore, -) : ViewModel() { - - val userType: LiveData = appLoungeDataStore.userType.asLiveData() - - private val _authLiveData: MutableLiveData = MutableLiveData() - val authLiveData: LiveData = _authLiveData -} +package foundation.e.apps.ui.setup.signin + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import com.aurora.gplayapi.data.models.AuthData +import dagger.hilt.android.lifecycle.HiltViewModel +import foundation.e.apps.data.preference.AppLoungeDataStore +import javax.inject.Inject + +@HiltViewModel +class SignInViewModel @Inject constructor( + private val appLoungeDataStore: AppLoungeDataStore, +) : ViewModel() { + + val userType: LiveData = appLoungeDataStore.userType.asLiveData() + + private val _authLiveData: MutableLiveData = MutableLiveData() + val authLiveData: LiveData = _authLiveData +} diff --git a/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt b/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt index bc1ec1052435ba5c4903a083718dd34f7aee62d5..c9472443dc9059d3b74ab36fb6d3816498eb9e65 100644 --- a/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt +++ b/app/src/main/java/foundation/e/apps/ui/setup/tos/TOSViewModel.kt @@ -1,26 +1,26 @@ -package foundation.e.apps.ui.setup.tos - -import androidx.lifecycle.LiveData -import androidx.lifecycle.ViewModel -import androidx.lifecycle.asLiveData -import androidx.lifecycle.viewModelScope -import dagger.hilt.android.lifecycle.HiltViewModel -import foundation.e.apps.data.preference.AppLoungeDataStore -import kotlinx.coroutines.launch -import javax.inject.Inject - -@HiltViewModel -class TOSViewModel @Inject constructor( - private val appLoungeDataStore: AppLoungeDataStore -) : ViewModel() { - - val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() - - fun saveTOCStatus(status: Boolean) { - viewModelScope.launch { - appLoungeDataStore.saveTOCStatus(status, TOS_VERSION) - } - } -} - -const val TOS_VERSION = "1.0.3" +package foundation.e.apps.ui.setup.tos + +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import foundation.e.apps.data.preference.AppLoungeDataStore +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class TOSViewModel @Inject constructor( + private val appLoungeDataStore: AppLoungeDataStore +) : ViewModel() { + + val tocStatus: LiveData = appLoungeDataStore.tocStatus.asLiveData() + + fun saveTOCStatus(status: Boolean) { + viewModelScope.launch { + appLoungeDataStore.saveTOCStatus(status, TOS_VERSION) + } + } +} + +const val TOS_VERSION = "1.0.3" diff --git a/app/src/main/res/color-night/install_button_background.xml b/app/src/main/res/color-night/install_button_background.xml index 7fc02c0bab8a3aafaabe913b9d0ec119aaf31d50..8aaa555b85ac150598fbe6f35785de7562957bba 100644 --- a/app/src/main/res/color-night/install_button_background.xml +++ b/app/src/main/res/color-night/install_button_background.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/install_button_background.xml b/app/src/main/res/color/install_button_background.xml index 516c010a651a43a1006b532c3d1b43611e63d1da..690462547b3f12a2daedd9bfeae151ebd62c23a8 100644 --- a/app/src/main/res/color/install_button_background.xml +++ b/app/src/main/res/color/install_button_background.xml @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_auto_and_vehicles.xml b/app/src/main/res/drawable/ic_auto_and_vehicles.xml index 5bddbd4ab1510c8dd155307f38c070f3d8b3294c..3c9d877a1761e063f71ba180b203d0fea2ddb4c0 100644 --- a/app/src/main/res/drawable/ic_auto_and_vehicles.xml +++ b/app/src/main/res/drawable/ic_auto_and_vehicles.xml @@ -1,63 +1,63 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_beauty.xml b/app/src/main/res/drawable/ic_beauty.xml index 9bcf651e48d5a1bb678cd10ee30dcbe174b6a56a..63a2089339bbebd65d3268fc272ad357a290e98a 100644 --- a/app/src/main/res/drawable/ic_beauty.xml +++ b/app/src/main/res/drawable/ic_beauty.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_books_and_reference.xml b/app/src/main/res/drawable/ic_books_and_reference.xml index 8b50add71bbcbed230adb25be99413ea65ce3511..f6a9e36975c4f6f25f036cf92eccdf9f0a12daf5 100644 --- a/app/src/main/res/drawable/ic_books_and_reference.xml +++ b/app/src/main/res/drawable/ic_books_and_reference.xml @@ -1,22 +1,22 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_business.xml b/app/src/main/res/drawable/ic_business.xml index 24ee195b2f490c0663dfdad58d567c770ff3a700..121e3a70b303510b01180208a2e099affeb32f5e 100644 --- a/app/src/main/res/drawable/ic_business.xml +++ b/app/src/main/res/drawable/ic_business.xml @@ -1,42 +1,42 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_comics.xml b/app/src/main/res/drawable/ic_cat_comics.xml index cfc4b6b4f4f55286f3053baa2e35c30f202b85cb..432f21e7259412c376484f2ccc9f1a4d7677b0cc 100644 --- a/app/src/main/res/drawable/ic_cat_comics.xml +++ b/app/src/main/res/drawable/ic_cat_comics.xml @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_communication.xml b/app/src/main/res/drawable/ic_cat_communication.xml index 7295356ed8bf40e30a39b2a102d8897d39595e30..a44858a2b44071679d5b7ab77980431a69ce8a06 100644 --- a/app/src/main/res/drawable/ic_cat_communication.xml +++ b/app/src/main/res/drawable/ic_cat_communication.xml @@ -1,30 +1,30 @@ - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_dating.xml b/app/src/main/res/drawable/ic_cat_dating.xml index f7f2497bbfc5596eff8f5eb6923f79c31a9939e1..4f47c88f1cc553891e5ee57edd503eb9daf3c61f 100644 --- a/app/src/main/res/drawable/ic_cat_dating.xml +++ b/app/src/main/res/drawable/ic_cat_dating.xml @@ -1,30 +1,30 @@ - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_education.xml b/app/src/main/res/drawable/ic_cat_education.xml index bb39ec9f511578b6da003a99b48c481130299bbd..8fc472fb250ba6627d770c69a17b75f0d045bb78 100644 --- a/app/src/main/res/drawable/ic_cat_education.xml +++ b/app/src/main/res/drawable/ic_cat_education.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_entertainment.xml b/app/src/main/res/drawable/ic_cat_entertainment.xml index 5e9437145ba9d077c79aed4e9bb82d2c64333fe6..0759a50d66a1c87d0adbe4be06329be2e2762c44 100644 --- a/app/src/main/res/drawable/ic_cat_entertainment.xml +++ b/app/src/main/res/drawable/ic_cat_entertainment.xml @@ -1,30 +1,30 @@ - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_events.xml b/app/src/main/res/drawable/ic_cat_events.xml index 84c7e967d8f7aa5ce03a46468438a9b641265ddb..5810f7d0799fe83fdb070cfc8a58e2f7c0e84b54 100644 --- a/app/src/main/res/drawable/ic_cat_events.xml +++ b/app/src/main/res/drawable/ic_cat_events.xml @@ -1,72 +1,72 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_family.xml b/app/src/main/res/drawable/ic_cat_family.xml index 7f46134ac406bea1ff6fb26f25c036abb1676a0f..1232cca9a7e6a1da51d6c8c79b7a1b8b02e7178e 100644 --- a/app/src/main/res/drawable/ic_cat_family.xml +++ b/app/src/main/res/drawable/ic_cat_family.xml @@ -1,27 +1,27 @@ - - - - - + + + + + diff --git a/app/src/main/res/drawable/ic_cat_finance.xml b/app/src/main/res/drawable/ic_cat_finance.xml index 1d6ce4205ca731175d154cd0b4c5c7958657a8d5..9cab9df804f8345fca167aebdf2a7b74e686d8bc 100644 --- a/app/src/main/res/drawable/ic_cat_finance.xml +++ b/app/src/main/res/drawable/ic_cat_finance.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_food_and_drink.xml b/app/src/main/res/drawable/ic_cat_food_and_drink.xml index 224645904203e96e03da71e4d563e95010404696..a25cd5cab50fb3d814b75e473043e60f05cb218c 100644 --- a/app/src/main/res/drawable/ic_cat_food_and_drink.xml +++ b/app/src/main/res/drawable/ic_cat_food_and_drink.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_food_nd_drink.xml b/app/src/main/res/drawable/ic_cat_food_nd_drink.xml index dbce6d9c232a1a78dd46794dd9a8cf5986da54eb..4477dbe90d7b97e271556f1ffb5c4f54a2bb1b69 100644 --- a/app/src/main/res/drawable/ic_cat_food_nd_drink.xml +++ b/app/src/main/res/drawable/ic_cat_food_nd_drink.xml @@ -1,27 +1,27 @@ - - - - - + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_action.xml b/app/src/main/res/drawable/ic_cat_game_action.xml index dda4f97e201c12a4d066a3b50ea158598aaa3fd6..e591af53f2711ffe8c4283ef1e8113920c3df196 100644 --- a/app/src/main/res/drawable/ic_cat_game_action.xml +++ b/app/src/main/res/drawable/ic_cat_game_action.xml @@ -1,55 +1,55 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_adventure.xml b/app/src/main/res/drawable/ic_cat_game_adventure.xml index 6c7092d51d3ea1da9d4d2c28bdb08573da76915a..2c1d178d0d71a025c7fa1b64dae831683fb6538a 100644 --- a/app/src/main/res/drawable/ic_cat_game_adventure.xml +++ b/app/src/main/res/drawable/ic_cat_game_adventure.xml @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_arcade.xml b/app/src/main/res/drawable/ic_cat_game_arcade.xml index 747acaace30f77acaaa7246b823adc6964caafc6..4d3b2cd01337513f427254bb78815bb6acf4bad7 100644 --- a/app/src/main/res/drawable/ic_cat_game_arcade.xml +++ b/app/src/main/res/drawable/ic_cat_game_arcade.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_board.xml b/app/src/main/res/drawable/ic_cat_game_board.xml index 264444d18f70ad3fba111d8509a282bfcc0b7074..d4a13fe5df97531155344a669f32d9982c5b3fe4 100644 --- a/app/src/main/res/drawable/ic_cat_game_board.xml +++ b/app/src/main/res/drawable/ic_cat_game_board.xml @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_card.xml b/app/src/main/res/drawable/ic_cat_game_card.xml index bb129146468f13709ac7524f53a06f880ad17be2..2ef1a2eed7bbcbbce1a287233adda8fcd06e78f3 100644 --- a/app/src/main/res/drawable/ic_cat_game_card.xml +++ b/app/src/main/res/drawable/ic_cat_game_card.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_casino.xml b/app/src/main/res/drawable/ic_cat_game_casino.xml index 452b224ee920be27e6bb64de1f956fc1947834e6..0c096651e1040db4761d1f61f679a506e33c718e 100644 --- a/app/src/main/res/drawable/ic_cat_game_casino.xml +++ b/app/src/main/res/drawable/ic_cat_game_casino.xml @@ -1,39 +1,39 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_casual.xml b/app/src/main/res/drawable/ic_cat_game_casual.xml index 952da2e66cef02a4af357d2f2205ebeb12ccbc6a..84ef26244e0afb6de623dcba05a45244f1a0f892 100644 --- a/app/src/main/res/drawable/ic_cat_game_casual.xml +++ b/app/src/main/res/drawable/ic_cat_game_casual.xml @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_educational.xml b/app/src/main/res/drawable/ic_cat_game_educational.xml index 48344b89ae818af246b7a578737a9f880c0fcfa2..2ba98767d0d049a5776942e14922a181fb36bd86 100644 --- a/app/src/main/res/drawable/ic_cat_game_educational.xml +++ b/app/src/main/res/drawable/ic_cat_game_educational.xml @@ -1,54 +1,54 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_music.xml b/app/src/main/res/drawable/ic_cat_game_music.xml index 327b5c5ffb578467922fab49265bc7004eda362c..62d1cc606de7b60600062184a7f999e719849ea7 100644 --- a/app/src/main/res/drawable/ic_cat_game_music.xml +++ b/app/src/main/res/drawable/ic_cat_game_music.xml @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_puzzle.xml b/app/src/main/res/drawable/ic_cat_game_puzzle.xml index 8a975f73ef0d1844f722d8196c39c29eaf500731..85117c6098a0c4e43e1627b811ccb02f4a84b319 100644 --- a/app/src/main/res/drawable/ic_cat_game_puzzle.xml +++ b/app/src/main/res/drawable/ic_cat_game_puzzle.xml @@ -1,30 +1,30 @@ - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_racing.xml b/app/src/main/res/drawable/ic_cat_game_racing.xml index 8f2c207109b4a2cf90e9d349fa67c97727411c41..26598b018f33ed6a2fda37df30dfceaf35f9b3b3 100644 --- a/app/src/main/res/drawable/ic_cat_game_racing.xml +++ b/app/src/main/res/drawable/ic_cat_game_racing.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_role_playing.xml b/app/src/main/res/drawable/ic_cat_game_role_playing.xml index 6b12e94d30441ee3b5a8048503b9d854cacc8d9a..e3f2679b3e537d98a7ce9bd0f9e1b2417c349ae6 100644 --- a/app/src/main/res/drawable/ic_cat_game_role_playing.xml +++ b/app/src/main/res/drawable/ic_cat_game_role_playing.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_simulation.xml b/app/src/main/res/drawable/ic_cat_game_simulation.xml index bf45d9286b6b53781ff550308ce159d4a8632fd2..2361ee201879420a4f1e59bfd88d8fc4d602251d 100644 --- a/app/src/main/res/drawable/ic_cat_game_simulation.xml +++ b/app/src/main/res/drawable/ic_cat_game_simulation.xml @@ -1,30 +1,30 @@ - - - - - - - - + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_sports.xml b/app/src/main/res/drawable/ic_cat_game_sports.xml index ecf0500216713e52f6790e565d49f497c4191bf1..afa8b3065a9b92e4fa334654d35706f1fc84a4a5 100644 --- a/app/src/main/res/drawable/ic_cat_game_sports.xml +++ b/app/src/main/res/drawable/ic_cat_game_sports.xml @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_strategy.xml b/app/src/main/res/drawable/ic_cat_game_strategy.xml index 3fc80d0cd1ec2821f60279b9ebabcc7a62bdbcdd..8cbf3d8479a79f4b985e6dc98e4438d4b5e84340 100644 --- a/app/src/main/res/drawable/ic_cat_game_strategy.xml +++ b/app/src/main/res/drawable/ic_cat_game_strategy.xml @@ -1,42 +1,42 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_trivia.xml b/app/src/main/res/drawable/ic_cat_game_trivia.xml index 9031d38a7b83f875aee1b6d6d7da10f26954c16f..7915cf5433a0fa06e765672b024e681b6f33797e 100644 --- a/app/src/main/res/drawable/ic_cat_game_trivia.xml +++ b/app/src/main/res/drawable/ic_cat_game_trivia.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_game_word.xml b/app/src/main/res/drawable/ic_cat_game_word.xml index 6a854aff1d72f743ac94a071e2b8f2cab7a0523c..b578d02b67f7882e1718bdc8fa6581e4436e7e53 100644 --- a/app/src/main/res/drawable/ic_cat_game_word.xml +++ b/app/src/main/res/drawable/ic_cat_game_word.xml @@ -1,33 +1,33 @@ - - - - - - - - - + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_libraries_and_demo.xml b/app/src/main/res/drawable/ic_cat_libraries_and_demo.xml index 9ccdb8135f9c30e41b5b12ad8510dd214ff3c7b9..5e951b4ceb6f1fe223370c059f30d79e7f81f198 100644 --- a/app/src/main/res/drawable/ic_cat_libraries_and_demo.xml +++ b/app/src/main/res/drawable/ic_cat_libraries_and_demo.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_lifestyle.xml b/app/src/main/res/drawable/ic_cat_lifestyle.xml index 4d1323f2cb66cd9791826679f74d69aa5d76f965..f0ba397780b1f0399ad235d7376dcac32c84163e 100644 --- a/app/src/main/res/drawable/ic_cat_lifestyle.xml +++ b/app/src/main/res/drawable/ic_cat_lifestyle.xml @@ -1,33 +1,33 @@ - - - - - - - - - + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_maps_and_navigation.xml b/app/src/main/res/drawable/ic_cat_maps_and_navigation.xml index 0d67b932a318cdae53a6fff6ec0198bf5bcf3e9d..f6616e3e8557487ec1a3e8950cf1b07df1c5919e 100644 --- a/app/src/main/res/drawable/ic_cat_maps_and_navigation.xml +++ b/app/src/main/res/drawable/ic_cat_maps_and_navigation.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_medical.xml b/app/src/main/res/drawable/ic_cat_medical.xml index 43240c34d4d97774496a18213c42c2c2700cfeef..4ed4e5a07303a504699d5e930c050388a7d1520e 100644 --- a/app/src/main/res/drawable/ic_cat_medical.xml +++ b/app/src/main/res/drawable/ic_cat_medical.xml @@ -1,39 +1,39 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_music_and_audio.xml b/app/src/main/res/drawable/ic_cat_music_and_audio.xml index 12d5147ee8d5d6293b81103308d894de1473889f..2c3463b528dc277fcfd891b1b31036f68cc11e8f 100644 --- a/app/src/main/res/drawable/ic_cat_music_and_audio.xml +++ b/app/src/main/res/drawable/ic_cat_music_and_audio.xml @@ -1,39 +1,39 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_news.xml b/app/src/main/res/drawable/ic_cat_news.xml index b2358ba80c3996d8c1900f5f3ca1023c2b19bc92..ebb835b2368247fc3d6995c68b9e4a0fb0ea9216 100644 --- a/app/src/main/res/drawable/ic_cat_news.xml +++ b/app/src/main/res/drawable/ic_cat_news.xml @@ -1,27 +1,27 @@ - - - - - + + + + + diff --git a/app/src/main/res/drawable/ic_cat_news_and_magazine.xml b/app/src/main/res/drawable/ic_cat_news_and_magazine.xml index 64019e5d3ba78f9cdab4ddfd0f488fd9eedd0ce2..a6759397906b939b2723f0e9c9263da571f13be8 100644 --- a/app/src/main/res/drawable/ic_cat_news_and_magazine.xml +++ b/app/src/main/res/drawable/ic_cat_news_and_magazine.xml @@ -1,57 +1,57 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_parenting.xml b/app/src/main/res/drawable/ic_cat_parenting.xml index 7f2ac326b3e58342d708668ad1b120206a52a9c8..6e50a02763debecc584cf52701ec9baf15b9640e 100644 --- a/app/src/main/res/drawable/ic_cat_parenting.xml +++ b/app/src/main/res/drawable/ic_cat_parenting.xml @@ -1,39 +1,39 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_personalization.xml b/app/src/main/res/drawable/ic_cat_personalization.xml index 9103e7e5a2241e6de4e0c32ea58a618ca5358dac..854d71085b8451f11c6df2b8df4fe874d4a16489 100644 --- a/app/src/main/res/drawable/ic_cat_personalization.xml +++ b/app/src/main/res/drawable/ic_cat_personalization.xml @@ -1,33 +1,33 @@ - - - - - - - - - + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_photography.xml b/app/src/main/res/drawable/ic_cat_photography.xml index f947506eb073ec54e953561ca186a20b977ac0c7..de1e53a117635e526b170448f949d9dc84385dde 100644 --- a/app/src/main/res/drawable/ic_cat_photography.xml +++ b/app/src/main/res/drawable/ic_cat_photography.xml @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_productivity.xml b/app/src/main/res/drawable/ic_cat_productivity.xml index 3495c47ab5cf749e35b4cc971b7fdb3c98db9953..74fa0e8a3afcc2326485259cf80b54a4905651d3 100644 --- a/app/src/main/res/drawable/ic_cat_productivity.xml +++ b/app/src/main/res/drawable/ic_cat_productivity.xml @@ -1,60 +1,60 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_shopping.xml b/app/src/main/res/drawable/ic_cat_shopping.xml index 1a9a8ee04fc286dbe949b417bf654ec2d23582e2..2e471f679459bfc354f7f4cb6c7e834b917315e5 100644 --- a/app/src/main/res/drawable/ic_cat_shopping.xml +++ b/app/src/main/res/drawable/ic_cat_shopping.xml @@ -1,48 +1,48 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_social.xml b/app/src/main/res/drawable/ic_cat_social.xml index b36668ce15554a816045570a3d8196d95fd25405..962e9f3908a2cbb2e96e630f251cc2db35eef387 100644 --- a/app/src/main/res/drawable/ic_cat_social.xml +++ b/app/src/main/res/drawable/ic_cat_social.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_sports.xml b/app/src/main/res/drawable/ic_cat_sports.xml index 00c4e5b3e902db529fbe2048a780fc1116a9744a..44fadfc70698c21eb9a4482b10f74dff61a6f98b 100644 --- a/app/src/main/res/drawable/ic_cat_sports.xml +++ b/app/src/main/res/drawable/ic_cat_sports.xml @@ -1,45 +1,45 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_tools.xml b/app/src/main/res/drawable/ic_cat_tools.xml index 8943f5d32d4c574e9a0b5c15cdf4fb937fd1c355..3128ab6d5ac85278434685f2845c48595d6e0ecf 100644 --- a/app/src/main/res/drawable/ic_cat_tools.xml +++ b/app/src/main/res/drawable/ic_cat_tools.xml @@ -1,36 +1,36 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_travel.xml b/app/src/main/res/drawable/ic_cat_travel.xml index 6e557fd4c87d82d47ccfbc68fdc6d3dde389d9a4..061bf84e10d8951f1434fb80010b4e4b5bdfb4e5 100644 --- a/app/src/main/res/drawable/ic_cat_travel.xml +++ b/app/src/main/res/drawable/ic_cat_travel.xml @@ -1,27 +1,27 @@ - - - - - + + + + + diff --git a/app/src/main/res/drawable/ic_cat_travel_and_local.xml b/app/src/main/res/drawable/ic_cat_travel_and_local.xml index ee94ef46e0bf097f0267da7edcbf335e2402fc54..2c142486d39c5cf3d7624330b5f0ce36620868b9 100644 --- a/app/src/main/res/drawable/ic_cat_travel_and_local.xml +++ b/app/src/main/res/drawable/ic_cat_travel_and_local.xml @@ -1,39 +1,39 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_video_players.xml b/app/src/main/res/drawable/ic_cat_video_players.xml index 28f8380a59310fc8427a8ad8fc69c7754dc76aa9..f54bb65d1c182618f84c36fe870582bd2ad54fc5 100644 --- a/app/src/main/res/drawable/ic_cat_video_players.xml +++ b/app/src/main/res/drawable/ic_cat_video_players.xml @@ -1,51 +1,51 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cat_weather.xml b/app/src/main/res/drawable/ic_cat_weather.xml index 936a5d0d2214d6ed0a1661bb752f740e669e2e37..f42718e6102064a12d77a462986ff8706f6f68ee 100644 --- a/app/src/main/res/drawable/ic_cat_weather.xml +++ b/app/src/main/res/drawable/ic_cat_weather.xml @@ -1,54 +1,54 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_check_mark.xml b/app/src/main/res/drawable/ic_check_mark.xml index e4139348752c426c05e8d80bec19c31f8a1f0d04..cdf2ba74e70188e1e1678ec2e7247079b35d0a5c 100644 --- a/app/src/main/res/drawable/ic_check_mark.xml +++ b/app/src/main/res/drawable/ic_check_mark.xml @@ -1,9 +1,9 @@ - - - + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml index 03587e95346d6b2537815a343e07b428b23045b5..b4eb1b73790eae49f1dda76ab34b7b2c89fba07d 100644 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -1,47 +1,47 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml index 6cff918b1d79d2b7dc70effa766d923f12876ad8..d9f7ae352436175cd2781e6f55d83cb07de8c285 100644 --- a/app/src/main/res/drawable/ic_launcher_foreground.xml +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -1,91 +1,91 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_menu_home.xml b/app/src/main/res/drawable/ic_menu_home.xml index a6f9bc3d190b6424146df4bd329f64da7b31ec29..c6879673c5e3cf5bb9c6e07299c343573e670e5b 100644 --- a/app/src/main/res/drawable/ic_menu_home.xml +++ b/app/src/main/res/drawable/ic_menu_home.xml @@ -1,49 +1,49 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_user_icon.xml b/app/src/main/res/drawable/ic_user_icon.xml index 1ac1a3f7f63735d8ecf74bdc36a80de21b8467ee..b868207f61e64e7183fd68c1ea4ae9ec63056d42 100644 --- a/app/src/main/res/drawable/ic_user_icon.xml +++ b/app/src/main/res/drawable/ic_user_icon.xml @@ -1,35 +1,35 @@ - - - - - - - + + + + + + + diff --git a/app/src/main/res/layout/application_list_item.xml b/app/src/main/res/layout/application_list_item.xml index 5730547c9e1140a96b886e85e9dfcc0c5b2f9331..94ba4f54dfb75ebc29d3665e2443471ccf16888a 100644 --- a/app/src/main/res/layout/application_list_item.xml +++ b/app/src/main/res/layout/application_list_item.xml @@ -1,210 +1,210 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/application_screenshots_list_item.xml b/app/src/main/res/layout/application_screenshots_list_item.xml index 19179e8606a38aaa3cb203fe140fc7869a3df6b1..dcd7c828e1a1c37dfed9b6f72b60b4b007eeb587 100644 --- a/app/src/main/res/layout/application_screenshots_list_item.xml +++ b/app/src/main/res/layout/application_screenshots_list_item.xml @@ -1,40 +1,40 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/categories_list_item.xml b/app/src/main/res/layout/categories_list_item.xml index 489415ba6a1cfb1a262f9d2c549ea0c6343a758c..acb73d45d11f3cbd7b6f5ca7f0c86328a224d373 100644 --- a/app/src/main/res/layout/categories_list_item.xml +++ b/app/src/main/res/layout/categories_list_item.xml @@ -1,82 +1,82 @@ - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/custom_simple_list_item.xml b/app/src/main/res/layout/custom_simple_list_item.xml index 2260d9a82a081293100a31d1c19b79258d068ffa..302af53d24f08c17de7d1ddfa504d14ea81dbf9f 100644 --- a/app/src/main/res/layout/custom_simple_list_item.xml +++ b/app/src/main/res/layout/custom_simple_list_item.xml @@ -1,30 +1,30 @@ - - - - - + + + + + diff --git a/app/src/main/res/layout/fragment_application_download.xml b/app/src/main/res/layout/fragment_application_download.xml index e063b764667f3ad0b9edd8a5711c0f4c561669d7..0c9ab1480680854ed1adf667f72b2d6d95b8b687 100644 --- a/app/src/main/res/layout/fragment_application_download.xml +++ b/app/src/main/res/layout/fragment_application_download.xml @@ -1,101 +1,101 @@ - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_application_information.xml b/app/src/main/res/layout/fragment_application_information.xml index a0964ae36cc5c9c5ed1ac09d995a0e90f4b2fa54..adbe35358a2ccf4cba6f3e7d945fd319972db06a 100644 --- a/app/src/main/res/layout/fragment_application_information.xml +++ b/app/src/main/res/layout/fragment_application_information.xml @@ -1,102 +1,102 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_application_privacy.xml b/app/src/main/res/layout/fragment_application_privacy.xml index 62c4f402b81e9adbf239583e635dd084f2d5fa11..1181a378f92ab4c991548c87e907f20b7f1abb5b 100644 --- a/app/src/main/res/layout/fragment_application_privacy.xml +++ b/app/src/main/res/layout/fragment_application_privacy.xml @@ -1,82 +1,82 @@ - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_application_ratings.xml b/app/src/main/res/layout/fragment_application_ratings.xml index dd02f2ba61621d7cebe869f2c50b153525d33b3e..11aaf0d8b7a0d5d0901d75575bd85c48e41e900a 100644 --- a/app/src/main/res/layout/fragment_application_ratings.xml +++ b/app/src/main/res/layout/fragment_application_ratings.xml @@ -1,194 +1,194 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_application_title.xml b/app/src/main/res/layout/fragment_application_title.xml index 5c42233a8aad059bab96be91c7e1f2956031ab19..34c6b1e09d67d8d3d73cbc98275d850721ed37fc 100644 --- a/app/src/main/res/layout/fragment_application_title.xml +++ b/app/src/main/res/layout/fragment_application_title.xml @@ -1,159 +1,159 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 8ea33c002538a37f03f12151562cff3b69999d25..791b6d87654e5d831aa9935376ce9ffee859b413 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,72 +1,72 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index b6a29ac8de81dcc253374d36805b5a4f992e3307..d96827e5a5b0cac9740fb4d03d9bd7a41ad0b344 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -1,128 +1,128 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_search_hint.xml b/app/src/main/res/layout/fragment_search_hint.xml index 586c51c478b789f192a731fefc98eb3fe342ea12..23369f5e88d1bac52c30247973b6d4d844323dd2 100644 --- a/app/src/main/res/layout/fragment_search_hint.xml +++ b/app/src/main/res/layout/fragment_search_hint.xml @@ -1,26 +1,26 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_tos.xml b/app/src/main/res/layout/fragment_tos.xml index e578adc08c90e99da0dfae5bcf406045a2de1ca8..ee5899ddcff508c480134937d02ce30687540599 100644 --- a/app/src/main/res/layout/fragment_tos.xml +++ b/app/src/main/res/layout/fragment_tos.xml @@ -1,100 +1,100 @@ - - - - - - - - - - - - - - - -