commit 0a10f73143091db666da944d662f9be99e78a738 Author: Tosencen Date: Thu Jul 3 06:16:21 2025 +0800 feat: 初始化XMBOX项目 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..0532cb7b --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +ko_fi: fongmi +custom: [https://paypal.me/fongmitw] diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c7b1543e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.idea +.gradle +*.jks +lib-*.aar +*build +/media* +/Release +/local.properties \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..f288702d --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.md b/README.md new file mode 100644 index 00000000..fd129366 --- /dev/null +++ b/README.md @@ -0,0 +1,281 @@ +# XMBOX + +一个简单的视频播放器应用,支持以下功能: + +## 主要功能 +- 视频播放:支持多种格式视频播放 +- 直播观看:支持直播源播放 +- 收藏管理:可收藏喜欢的视频和直播源 +- 设置中心:自定义应用配置 + +## 技术特点 +- 基于 Android 原生开发 +- 使用 ExoPlayer 作为播放内核 +- 支持 TV 和手机双平台 +- Material Design 界面设计 + +## 应用截图 +- 视频列表 +- 播放界面 +- 设置中心 + +## 开发说明 +本项目仅用于学习 Android 开发,代码改自 [FongMi/TV](https://github.com/FongMi/TV)。 + +## 免责声明 +1. 本项目仅供学习交流使用,不得用于商业用途 +2. 项目中的内容均来自网络,如有侵权请联系删除 +3. 使用本项目产生的一切后果由使用者自行承担 + +## 许可证 +GPL-3.0 license + +# 影視 + +### 基於 CatVod 項目 + +https://github.com/CatVodTVOfficial/CatVodTVJarLoader + +### 點播欄位 + +| 欄位名稱 | 預設值 | 說明 | 其他 | +|------------|------|------|------------| +| searchable | 1 | 是否搜索 | 0:關閉;1:啟用 | +| changeable | 1 | 是否換源 | 0:關閉;1:啟用 | +| quickserch | 1 | 是否快搜 | 0:關閉;1:啟用 | +| indexs | 0 | 是否聚搜 | 0:關閉;1:啟用 | +| hide | 0 | 是否隱藏 | 0:顯示;1:隱藏 | +| timeout | 15 | 播放超時 | 單位:秒 | +| header | none | 請求標頭 | 格式:json | +| click | none | 點擊js | javascript | + +### 直播欄位 + +| 欄位名稱 | 預設值 | 說明 | 其他 | +|----------|-------|-------|------------| +| ua | none | 用戶代理 | | +| origin | none | 來源 | | +| referer | none | 參照地址 | | +| epg | none | 節目地址 | | +| logo | none | 台標地址 | | +| pass | false | 是否免密碼 | | +| boot | false | 是否自啟動 | | +| timeout | 15 | 播放超時 | 單位:秒 | +| header | none | 請求標頭 | 格式:json | +| click | none | 點擊js | javascript | +| catchup | none | 回看參數 | | +| timeZone | none | 時區 | | + +### 樣式 + +| 欄位名稱 | 值 | 說明 | +|-------|------|-----| +| type | rect | 矩形 | +| | oval | 橢圓 | +| | list | 列表 | +| ratio | 0.75 | 3:4 | +| | 1.33 | 4:3 | + +直式 + +```json +{ + "style": { + "type": "rect" + } +} +``` + +橫式 + +```json +{ + "style": { + "type": "rect", + "ratio": 1.33 + } +} +``` + +正方 + +```json +{ + "style": { + "type": "rect", + "ratio": 1 + } +} +``` + +正圓 + +```json +{ + "style": { + "type": "oval" + } +} +``` + +橢圓 + +```json +{ + "style": { + "type": "oval", + "ratio": 1.1 + } +} +``` + +### API + +刷新詳情 + +``` +http://127.0.0.1:9978/action?do=refresh&type=detail +``` + +刷新播放 + +``` +http://127.0.0.1:9978/action?do=refresh&type=player +``` + +刷新直播 + +``` +http://127.0.0.1:9978/action?do=refresh&type=live +``` + +推送字幕 + +``` +http://127.0.0.1:9978/action?do=refresh&type=subtitle&path=http://xxx +``` + +推送彈幕 + +``` +http://127.0.0.1:9978/action?do=refresh&type=danmaku&path=http://xxx +``` + +新增緩存字串 + +``` +http://127.0.0.1:9978/cache?do=set&key=xxx&value=xxx +``` + +取得緩存字串 + +``` +http://127.0.0.1:9978/cache?do=get&key=xxx +``` + +刪除緩存字串 + +``` +http://127.0.0.1:9978/cache?do=del&key=xxx +``` + +### Proxy + +scheme 支持 http, https, socks4, socks5 + +``` +scheme://username:password@host:port +``` + +配置新增 proxy 判斷域名是否走代理 +全局只需要加上一條規則 ".*." + +```json +{ + "spider": "", + "proxy": [ + "raw.githubusercontent.com", + "googlevideo.com" + ] +} +``` + +### Hosts + +```json +{ + "spider": "", + "hosts": [ + "cache.ott.*.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" + ] +} +``` + +### Headers + +```json +{ + "spider": "", + "headers": [ + { + "host": "gslbserv.itv.cmvideo.cn", + "header": { + "User-Agent": "okhttp/3.12.13", + "Referer": "test" + } + } + ] +} +``` + +### 爬蟲本地代理 + +Java + +``` +proxy:// +``` + +``` +Proxy.getUrl(boolean local) +``` + +Python + +``` +proxy://do=py +``` + +``` +getProxyUrl(boolean local) +``` + +JS + +``` +proxy://do=js +``` + +``` +getProxy(boolean local) +``` + +### 配置範例 + +[點播-線上](other/sample/vod/online.json) +[點播-本地](other/sample/vod/offline.json) +[直播-線上](other/sample/live/online.json) +[直播-本地](other/sample/live/offline.json) + +### 飛機群 + +[討論群組](https://t.me/+qTlg0qAVzP9kMmM1) +[發布頻道](https://t.me/fongmi_release) + +### 贊助 + +![photo_2024-01-10_11-39-12](https://github.com/FongMi/TV/assets/3471963/fdc12771-386c-4d5d-9a4d-d0bec0276fa7) + +### Star + +[![Star History Chart](https://api.star-history.com/svg?repos=FongMi/TV&type=Date)](https://www.star-history.com/#FongMi/TV&Date) diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..7607bd2c --- /dev/null +++ b/app/.gitignore @@ -0,0 +1,2 @@ +/build +/.cxx \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..e558525c --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,153 @@ +plugins { + id 'com.android.application' +} + +android { + namespace 'com.fongmi.android.tv' + + compileSdk 35 + flavorDimensions = ["mode", "abi"] + + signingConfigs { + release { + storeFile file("../keystore/release.jks") + storePassword "xmbox123" + keyAlias "xmbox" + keyPassword "xmbox123" + } + } + + defaultConfig { + applicationId "com.fongmi.android.tv" + minSdk 21 + //noinspection ExpiredTargetSdkVersion + targetSdk 28 + versionCode 300 + versionName "3.0.0" + javaCompileOptions { + annotationProcessorOptions { + arguments = ["room.schemaLocation": "$projectDir/schemas".toString(), "eventBusIndex": "com.fongmi.android.tv.event.EventIndex"] + } + } + } + + productFlavors { + leanback { + dimension "mode" + } + mobile { + dimension "mode" + } + arm64_v8a { + dimension "abi" + ndk { abiFilters "arm64-v8a" } + } + armeabi_v7a { + dimension "abi" + ndk { abiFilters "armeabi-v7a" } + } + } + + buildFeatures { + buildConfig true + viewBinding true + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + signingConfig signingConfigs.release + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + packagingOptions { + resources { + exclude 'META-INF/beans.xml' + exclude 'META-INF/versions/9/OSGI-INF/MANIFEST.MF' + } + } + + android.applicationVariants.configureEach { variant -> + variant.outputs.configureEach { output -> + outputFileName = "${variant.productFlavors[0].name}-${variant.productFlavors[1].name}.apk" + } + } + + configurations.configureEach { + resolutionStrategy { + force 'com.squareup.okhttp3:okhttp:' + okhttpVersion + } + } + + lint { + disable 'UnsafeOptInUsageError' + } + + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.aar"]) + implementation project(':catvod') + //implementation project(':chaquo') + implementation project(':quickjs') + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.media:media:1.7.0' + implementation 'androidx.media3:media3-common:' + media3Version + implementation 'androidx.media3:media3-container:' + media3Version + implementation 'androidx.media3:media3-database:' + media3Version + implementation 'androidx.media3:media3-datasource:' + media3Version + implementation 'androidx.media3:media3-datasource-okhttp:' + media3Version + implementation 'androidx.media3:media3-datasource-rtmp:' + media3Version + implementation 'androidx.media3:media3-decoder:' + media3Version + implementation 'androidx.media3:media3-effect:' + media3Version + implementation 'androidx.media3:media3-exoplayer:' + media3Version + implementation 'androidx.media3:media3-exoplayer-dash:' + media3Version + implementation 'androidx.media3:media3-exoplayer-hls:' + media3Version + implementation 'androidx.media3:media3-exoplayer-rtsp:' + media3Version + implementation 'androidx.media3:media3-exoplayer-smoothstreaming:' + media3Version + implementation 'androidx.media3:media3-extractor:' + media3Version + implementation 'androidx.media3:media3-ui:' + media3Version + implementation 'androidx.room:room-runtime:2.7.1' + implementation 'cat.ereza:customactivityoncrash:2.4.0' + implementation('com.github.anilbeesetti.nextlib:nextlib-media3ext:0.8.4') { exclude group: 'androidx.media3' } + implementation 'com.github.bassaer:materialdesigncolors:1.0.0' + implementation 'com.github.bumptech.glide:glide:4.16.0' + implementation 'com.github.bumptech.glide:annotations:4.16.0' + implementation('com.github.bumptech.glide:avif-integration:4.16.0') { exclude group: 'org.aomedia.avif.android', module: 'avif' } + implementation 'com.github.bumptech.glide:okhttp3-integration:4.16.0' + implementation 'com.github.jahirfiquitiva:TextDrawable:1.0.3' + implementation 'com.github.thegrizzlylabs:sardine-android:0.9' + implementation 'com.github.teamnewpipe:NewPipeExtractor:v0.24.6' + implementation 'com.google.android.material:material:1.12.0' + implementation 'com.google.zxing:core:3.5.3' + implementation 'com.guolindev.permissionx:permissionx:1.8.0' + implementation 'com.hierynomus:smbj:0.14.0' + implementation 'io.antmedia:rtmp-client:3.2.0' + implementation 'javax.servlet:javax.servlet-api:3.1.0' + implementation 'org.aomedia.avif.android:avif:1.1.1.14d8e3c4' + implementation 'org.eclipse.jetty:jetty-client:8.1.21.v20160908' + implementation('org.eclipse.jetty:jetty-server:8.1.21.v20160908') { exclude group: 'org.eclipse.jetty.orbit', module: 'javax.servlet' } + implementation('org.eclipse.jetty:jetty-servlet:8.1.21.v20160908') { exclude group: 'org.eclipse.jetty.orbit', module: 'javax.servlet' } + implementation 'org.fourthline.cling:cling-core:2.1.1' + implementation 'org.fourthline.cling:cling-support:2.1.1' + implementation 'org.greenrobot:eventbus:3.3.1' + implementation 'org.nanohttpd:nanohttpd:2.3.1' + implementation('org.simpleframework:simple-xml:2.7.1') { exclude group: 'stax', module: 'stax-api' exclude group: 'xpp3', module: 'xpp3' } + leanbackImplementation 'androidx.leanback:leanback:1.2.0' + leanbackImplementation 'com.github.JessYanCoding:AndroidAutoSize:1.2.1' + mobileImplementation 'androidx.biometric:biometric:1.1.0' + mobileImplementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' + mobileImplementation 'com.google.android.flexbox:flexbox:3.0.0' + mobileImplementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false } + annotationProcessor 'androidx.room:room-compiler:2.7.1' + annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' + annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.3.1' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.4' +} \ No newline at end of file diff --git a/app/libs/.gitignore b/app/libs/.gitignore new file mode 100644 index 00000000..fa9b0402 --- /dev/null +++ b/app/libs/.gitignore @@ -0,0 +1 @@ +*.aar \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..9a3d6851 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,94 @@ +# TV +-keep class com.fongmi.android.tv.bean.** { *; } + +# Gson +-keep class com.google.gson.** { *; } + +# SimpleXML +-keep interface org.simpleframework.xml.core.Label { public *; } +-keep class * implements org.simpleframework.xml.core.Label { public *; } +-keep interface org.simpleframework.xml.core.Parameter { public *; } +-keep class * implements org.simpleframework.xml.core.Parameter { public *; } +-keep interface org.simpleframework.xml.core.Extractor { public *; } +-keep class * implements org.simpleframework.xml.core.Extractor { public *; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Path ; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Root ; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Text ; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Element ; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Attribute ; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.ElementList ; } + +# OkHttp +-dontwarn okhttp3.** +-keep class okio.** { *; } +-keep class okhttp3.** { *; } + +# CatVod +-keep class com.github.catvod.Proxy { *; } +-keep class com.github.catvod.crawler.** { *; } +-keep class * extends com.github.catvod.crawler.Spider + +# Cling +-dontwarn javax.** +-dontwarn sun.net.** +-dontwarn java.awt.** +-dontwarn com.sun.net.** +-dontwarn org.ietf.jgss.** +-keep class org.fourthline.cling.** { *; } +-keep class javax.xml.** { *; } + +# Cronet +-keep class org.chromium.net.** { *; } +-keep class com.google.net.cronet.** { *; } + +# EXO +-dontwarn org.kxml2.io.** +-dontwarn org.xmlpull.v1.** +-dontwarn android.content.res.** +-dontwarn org.slf4j.impl.StaticLoggerBinder +-keep class org.xmlpull.** { *; } +-keepclassmembers class org.xmlpull.** { *; } + +# IJK +-keep class tv.danmaku.ijk.media.player.** { *; } + +# Jianpian +-keep class com.p2p.** { *; } + +# Nano +-keep class fi.iki.elonen.** { *; } + +# NewPipeExtractor +-keep class org.schabi.newpipe.extractor.timeago.patterns.** { *; } +-keep class org.mozilla.javascript.* { *; } +-keep class org.mozilla.javascript.** { *; } +-keep class org.mozilla.javascript.engine.** { *; } +-keep class javax.script.** { *; } +-keep class jdk.dynalink.** { *; } +-dontwarn org.mozilla.javascript.JavaToJSONConverters +-dontwarn org.mozilla.javascript.tools.** +-dontwarn javax.script.** +-dontwarn jdk.dynalink.** + +# QuickJS +-keep class com.fongmi.quickjs.method.** { *; } + +# Sardine +-keep class com.thegrizzlylabs.sardineandroid.** { *; } + +# Smbj +-keep class com.hierynomus.** { *; } +-keep class net.engio.mbassy.** { *; } + +# TVBus +-keep class com.tvbus.engine.** { *; } + +# XunLei +-keep class com.xunlei.downloadlib.** { *; } + +# ZLive +-keep class com.sun.jna.** { *; } +-keep class com.east.android.zlive.** { *; } + +# Zxing +-keep class com.google.zxing.** { *; } \ No newline at end of file diff --git a/app/schemas/com.fongmi.android.tv.db.AppDatabase/32.json b/app/schemas/com.fongmi.android.tv.db.AppDatabase/32.json new file mode 100644 index 00000000..0df524c5 --- /dev/null +++ b/app/schemas/com.fongmi.android.tv.db.AppDatabase/32.json @@ -0,0 +1,451 @@ +{ + "formatVersion": 1, + "database": { + "version": 32, + "identityHash": "23710113f88894310d2acbc92c9e5ef1", + "entities": [ + { + "tableName": "Keep", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `siteName` TEXT, `vodName` TEXT, `vodPic` TEXT, `createTime` INTEGER NOT NULL, `type` INTEGER NOT NULL, `cid` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteName", + "columnName": "siteName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodName", + "columnName": "vodName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodPic", + "columnName": "vodPic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createTime", + "columnName": "createTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Site", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `searchable` INTEGER, `changeable` INTEGER, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "searchable", + "columnName": "searchable", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "changeable", + "columnName": "changeable", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Live", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `boot` INTEGER NOT NULL, `pass` INTEGER NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "boot", + "columnName": "boot", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pass", + "columnName": "pass", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Track", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `track` INTEGER NOT NULL, `key` TEXT, `name` TEXT, `selected` INTEGER NOT NULL, `adaptive` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "selected", + "columnName": "selected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "adaptive", + "columnName": "adaptive", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Track_key_type", + "unique": true, + "columnNames": [ + "key", + "type" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Track_key_type` ON `${TABLE_NAME}` (`key`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `time` INTEGER NOT NULL, `url` TEXT, `json` TEXT, `name` TEXT, `logo` TEXT, `home` TEXT, `parse` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "json", + "columnName": "json", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "logo", + "columnName": "logo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "home", + "columnName": "home", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parse", + "columnName": "parse", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Config_url_type", + "unique": true, + "columnNames": [ + "url", + "type" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Config_url_type` ON `${TABLE_NAME}` (`url`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `uuid` TEXT, `name` TEXT, `ip` TEXT, `type` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ip", + "columnName": "ip", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Device_uuid_name", + "unique": true, + "columnNames": [ + "uuid", + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Device_uuid_name` ON `${TABLE_NAME}` (`uuid`, `name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "History", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `vodPic` TEXT, `vodName` TEXT, `vodFlag` TEXT, `vodRemarks` TEXT, `episodeUrl` TEXT, `revSort` INTEGER NOT NULL, `revPlay` INTEGER NOT NULL, `createTime` INTEGER NOT NULL, `opening` INTEGER NOT NULL, `ending` INTEGER NOT NULL, `position` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `speed` REAL NOT NULL, `scale` INTEGER NOT NULL, `cid` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vodPic", + "columnName": "vodPic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodName", + "columnName": "vodName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodFlag", + "columnName": "vodFlag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodRemarks", + "columnName": "vodRemarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "episodeUrl", + "columnName": "episodeUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "revSort", + "columnName": "revSort", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "revPlay", + "columnName": "revPlay", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createTime", + "columnName": "createTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "opening", + "columnName": "opening", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ending", + "columnName": "ending", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "speed", + "columnName": "speed", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "scale", + "columnName": "scale", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '23710113f88894310d2acbc92c9e5ef1')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/com.fongmi.android.tv.db.AppDatabase/33.json b/app/schemas/com.fongmi.android.tv.db.AppDatabase/33.json new file mode 100644 index 00000000..327097f3 --- /dev/null +++ b/app/schemas/com.fongmi.android.tv.db.AppDatabase/33.json @@ -0,0 +1,457 @@ +{ + "formatVersion": 1, + "database": { + "version": 33, + "identityHash": "52e51ac2d24c028878314c9ba50e8008", + "entities": [ + { + "tableName": "Keep", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `siteName` TEXT, `vodName` TEXT, `vodPic` TEXT, `createTime` INTEGER NOT NULL, `type` INTEGER NOT NULL, `cid` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "siteName", + "columnName": "siteName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodName", + "columnName": "vodName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodPic", + "columnName": "vodPic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createTime", + "columnName": "createTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Site", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `searchable` INTEGER, `changeable` INTEGER, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "searchable", + "columnName": "searchable", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "changeable", + "columnName": "changeable", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Live", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `keep` TEXT, `boot` INTEGER NOT NULL, `pass` INTEGER NOT NULL, PRIMARY KEY(`name`))", + "fields": [ + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "keep", + "columnName": "keep", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "boot", + "columnName": "boot", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pass", + "columnName": "pass", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "name" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Track", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `track` INTEGER NOT NULL, `key` TEXT, `name` TEXT, `selected` INTEGER NOT NULL, `adaptive` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "group", + "columnName": "group", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "track", + "columnName": "track", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "selected", + "columnName": "selected", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "adaptive", + "columnName": "adaptive", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Track_key_type", + "unique": true, + "columnNames": [ + "key", + "type" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Track_key_type` ON `${TABLE_NAME}` (`key`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Config", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `type` INTEGER NOT NULL, `time` INTEGER NOT NULL, `url` TEXT, `json` TEXT, `name` TEXT, `logo` TEXT, `home` TEXT, `parse` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "time", + "columnName": "time", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "json", + "columnName": "json", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "logo", + "columnName": "logo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "home", + "columnName": "home", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parse", + "columnName": "parse", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Config_url_type", + "unique": true, + "columnNames": [ + "url", + "type" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Config_url_type` ON `${TABLE_NAME}` (`url`, `type`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "Device", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `uuid` TEXT, `name` TEXT, `ip` TEXT, `type` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "ip", + "columnName": "ip", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_Device_uuid_name", + "unique": true, + "columnNames": [ + "uuid", + "name" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Device_uuid_name` ON `${TABLE_NAME}` (`uuid`, `name`)" + } + ], + "foreignKeys": [] + }, + { + "tableName": "History", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `vodPic` TEXT, `vodName` TEXT, `vodFlag` TEXT, `vodRemarks` TEXT, `episodeUrl` TEXT, `revSort` INTEGER NOT NULL, `revPlay` INTEGER NOT NULL, `createTime` INTEGER NOT NULL, `opening` INTEGER NOT NULL, `ending` INTEGER NOT NULL, `position` INTEGER NOT NULL, `duration` INTEGER NOT NULL, `speed` REAL NOT NULL, `scale` INTEGER NOT NULL, `cid` INTEGER NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "vodPic", + "columnName": "vodPic", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodName", + "columnName": "vodName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodFlag", + "columnName": "vodFlag", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "vodRemarks", + "columnName": "vodRemarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "episodeUrl", + "columnName": "episodeUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "revSort", + "columnName": "revSort", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "revPlay", + "columnName": "revPlay", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "createTime", + "columnName": "createTime", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "opening", + "columnName": "opening", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ending", + "columnName": "ending", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "speed", + "columnName": "speed", + "affinity": "REAL", + "notNull": true + }, + { + "fieldPath": "scale", + "columnName": "scale", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "cid", + "columnName": "cid", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '52e51ac2d24c028878314c9ba50e8008')" + ] + } +} \ No newline at end of file diff --git a/app/src/leanback/AndroidManifest.xml b/app/src/leanback/AndroidManifest.xml new file mode 100644 index 00000000..1e30263f --- /dev/null +++ b/app/src/leanback/AndroidManifest.xml @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/Product.java b/app/src/leanback/java/com/fongmi/android/tv/Product.java new file mode 100644 index 00000000..315b6d1e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/Product.java @@ -0,0 +1,37 @@ +package com.fongmi.android.tv; + +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.utils.ResUtil; + +public class Product { + + public static int getDeviceType() { + return 0; + } + + public static int getColumn() { + return Math.abs(Setting.getSize() - 7); + } + + public static int getColumn(Style style) { + return style.isLand() ? getColumn() - 1 : getColumn(); + } + + public static int[] getSpec(Style style) { + int column = getColumn(style); + int space = ResUtil.dp2px(48) + ResUtil.dp2px(16 * (column - 1)); + if (style.isOval()) space += ResUtil.dp2px(column * 16); + return getSpec(space, column, style); + } + + public static int[] getSpec(int space, int column, Style style) { + int base = ResUtil.getScreenWidth() - space; + int width = base / column; + int height = (int) (width / style.getRatio()); + return new int[]{width, height}; + } + + public static int getEms() { + return Math.min(ResUtil.getScreenWidth() / ResUtil.sp2px(24), 35); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/Updater.java b/app/src/leanback/java/com/fongmi/android/tv/Updater.java new file mode 100644 index 00000000..57354f64 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/Updater.java @@ -0,0 +1,139 @@ +package com.fongmi.android.tv; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.databinding.DialogUpdateBinding; +import com.fongmi.android.tv.utils.Download; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Github; +import com.github.catvod.utils.Path; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.json.JSONObject; + +import java.io.File; +import java.util.Locale; + +public class Updater implements Download.Callback { + + private DialogUpdateBinding binding; + private final Download download; + private AlertDialog dialog; + private boolean dev; + + private File getFile() { + return Path.cache("update.apk"); + } + + private String getJson() { + return Github.getJson(dev, BuildConfig.FLAVOR_mode); + } + + private String getApk() { + return Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi); + } + + public static Updater create() { + return new Updater(); + } + + public Updater() { + this.download = Download.create(getApk(), getFile(), this); + } + + public Updater force() { + Notify.show(R.string.update_check); + Setting.putUpdate(true); + return this; + } + + public Updater release() { + this.dev = false; + return this; + } + + public Updater dev() { + this.dev = true; + return this; + } + + private Updater check() { + dismiss(); + return this; + } + + public void start(Activity activity) { + App.execute(() -> doInBackground(activity)); + } + + private boolean need(int code, String name) { + return Setting.getUpdate() && (dev ? !name.equals(BuildConfig.VERSION_NAME) && code >= BuildConfig.VERSION_CODE : code > BuildConfig.VERSION_CODE); + } + + private void doInBackground(Activity activity) { + try { + JSONObject object = new JSONObject(OkHttp.string(getJson())); + String name = object.optString("name"); + String desc = object.optString("desc"); + int code = object.optInt("code"); + if (need(code, name)) App.post(() -> show(activity, name, desc)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void show(Activity activity, String version, String desc) { + binding = DialogUpdateBinding.inflate(LayoutInflater.from(activity)); + binding.version.setText(ResUtil.getString(R.string.update_version, version)); + binding.confirm.setOnClickListener(this::confirm); + binding.cancel.setOnClickListener(this::cancel); + check().create(activity).show(); + binding.desc.setText(desc); + } + + private AlertDialog create(Activity activity) { + return dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).setCancelable(false).create(); + } + + private void cancel(View view) { + Setting.putUpdate(false); + download.cancel(); + dismiss(); + } + + private void confirm(View view) { + binding.confirm.setEnabled(false); + download.start(); + } + + private void dismiss() { + try { + if (dialog != null) dialog.dismiss(); + } catch (Exception ignored) { + } + } + + @Override + public void progress(int progress) { + binding.confirm.setText(String.format(Locale.getDefault(), "%1$d%%", progress)); + } + + @Override + public void error(String msg) { + Notify.show(msg); + dismiss(); + } + + @Override + public void success(File file) { + FileUtil.openFile(file); + dismiss(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/bean/Func.java b/app/src/leanback/java/com/fongmi/android/tv/bean/Func.java new file mode 100644 index 00000000..c4ae9d98 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/bean/Func.java @@ -0,0 +1,60 @@ +package com.fongmi.android.tv.bean; + +import android.annotation.SuppressLint; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; + +public class Func { + + private final int resId; + private int drawable; + + public static Func create(int resId) { + return new Func(resId); + } + + public Func(int resId) { + this.resId = resId; + this.setDrawable(); + } + + public int getResId() { + return resId; + } + + public int getDrawable() { + return drawable; + } + + public String getText() { + return ResUtil.getString(resId); + } + + @SuppressLint("NonConstantResourceId") + public void setDrawable() { + switch (resId) { + case R.string.home_vod: + this.drawable = R.drawable.ic_home_vod; + break; + case R.string.home_live: + this.drawable = R.drawable.ic_home_live; + break; + case R.string.home_keep: + this.drawable = R.drawable.ic_home_keep; + break; + case R.string.home_push: + this.drawable = R.drawable.ic_home_push; + break; + case R.string.home_cast: + this.drawable = R.drawable.ic_home_cast; + break; + case R.string.home_search: + this.drawable = R.drawable.ic_home_search; + break; + case R.string.home_setting: + this.drawable = R.drawable.ic_home_setting; + break; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/receiver/BootReceiver.java b/app/src/leanback/java/com/fongmi/android/tv/receiver/BootReceiver.java new file mode 100644 index 00000000..dc817de3 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/receiver/BootReceiver.java @@ -0,0 +1,49 @@ +package com.fongmi.android.tv.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkRequest; +import android.os.Build; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.api.config.LiveConfig; + +public class BootReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + registerCallback(); + } + + private void registerCallback() { + ConnectivityManager manager = (ConnectivityManager) App.get().getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) manager.registerDefaultNetworkCallback(new Callback()); + else manager.registerNetworkCallback(new NetworkRequest.Builder().addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build(), new Callback()); + } + + static class Callback extends ConnectivityManager.NetworkCallback { + + private boolean first; + + @Override + public void onAvailable(@NonNull Network network) { + if (first) doJob(); + else first = true; + } + + @Override + public void onLost(@NonNull Network network) { + } + + private void doJob() { + LiveConfig.get().init().load(); + ((ConnectivityManager) App.get().getSystemService(Context.CONNECTIVITY_SERVICE)).unregisterNetworkCallback(this); + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/CastActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/CastActivity.java new file mode 100644 index 00000000..aa015485 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/CastActivity.java @@ -0,0 +1,559 @@ +package com.fongmi.android.tv.ui.activity; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.IBinder; +import android.view.KeyEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.media3.common.C; +import androidx.media3.common.Player; +import androidx.viewbinding.ViewBinding; + +import com.android.cast.dlna.dmr.CastAction; +import com.android.cast.dlna.dmr.DLNARendererService; +import com.android.cast.dlna.dmr.RenderControl; +import com.android.cast.dlna.dmr.RenderState; +import com.android.cast.dlna.dmr.RendererServiceBinder; +import com.android.cast.dlna.dmr.service.RendererInterfaceKt; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.bean.Sub; +import com.fongmi.android.tv.databinding.ActivityCastBinding; +import com.fongmi.android.tv.event.ActionEvent; +import com.fongmi.android.tv.event.ErrorEvent; +import com.fongmi.android.tv.event.PlayerEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.player.Players; +import com.fongmi.android.tv.player.exo.ExoUtil; +import com.fongmi.android.tv.service.PlaybackService; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.CustomKeyDownCast; +import com.fongmi.android.tv.ui.dialog.SubtitleDialog; +import com.fongmi.android.tv.ui.dialog.TrackDialog; +import com.fongmi.android.tv.utils.Clock; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Traffic; + +import org.fourthline.cling.support.contentdirectory.DIDLParser; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.UUID; + +public class CastActivity extends BaseActivity implements CustomKeyDownCast.Listener, TrackDialog.Listener, RenderControl, ServiceConnection, Clock.Callback { + + private ActivityCastBinding mBinding; + private DLNARendererService mService; + private CustomKeyDownCast mKeyDown; + private RenderState mState; + private CastAction mAction; + private Players mPlayers; + private Runnable mR1; + private Runnable mR2; + private Clock mClock; + private boolean redirect; + private long position; + private long duration; + private String tag; + private int scale; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, CastActivity.class)); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityCastBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (intent.hasExtra(RendererInterfaceKt.keyExtraCastAction)) setAction(intent); + else finish(); + } + + @Override + protected void initView() { + bindService(new Intent(this, DLNARendererService.class), this, Context.BIND_AUTO_CREATE); + mClock = Clock.create(mBinding.widget.clock); + mKeyDown = CustomKeyDownCast.create(this); + mPlayers = Players.create(this); + mR1 = this::hideControl; + mR2 = this::setTraffic; + setVideoView(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + protected void initEvent() { + mBinding.control.seek.setListener(mPlayers); + mBinding.control.speed.setUpListener(this::onSpeedAdd); + mBinding.control.speed.setDownListener(this::onSpeedSub); + mBinding.control.text.setUpListener(this::onSubtitleClick); + mBinding.control.text.setDownListener(this::onSubtitleClick); + mBinding.control.text.setOnClickListener(this::onTrack); + mBinding.control.audio.setOnClickListener(this::onTrack); + mBinding.control.video.setOnClickListener(this::onTrack); + mBinding.control.scale.setOnClickListener(view -> onScale()); + mBinding.control.speed.setOnClickListener(view -> onSpeed()); + mBinding.control.reset.setOnClickListener(view -> onReset()); + mBinding.control.player.setOnClickListener(view -> onChoose()); + mBinding.control.decode.setOnClickListener(view -> onDecode()); + mBinding.control.speed.setOnLongClickListener(view -> onSpeedLong()); + mBinding.video.setOnTouchListener((view, event) -> mKeyDown.onTouchEvent(event)); + } + + private String getName() { + try { + return new DIDLParser().parse(mAction.getCurrentURIMetaData()).getItems().get(0).getId(); + } catch (Exception e) { + return mAction.getCurrentURI(); + } + } + + private void setAction(Intent intent) { + mAction = intent.getParcelableExtra(RendererInterfaceKt.keyExtraCastAction); + mBinding.widget.waiting.setVisibility(View.GONE); + mBinding.widget.title.setText(getName()); + mBinding.widget.title.setSelected(true); + position = duration = C.TIME_UNSET; + mService.bindRealPlayer(this); + start(); + } + + private void start() { + mPlayers.setMediaItem(mAction.getCurrentURI()); + showProgress(); + setMetadata(); + hideCenter(); + } + + private void setVideoView() { + mPlayers.init(mBinding.exo); + PlaybackService.start(mPlayers); + setScale(scale = Setting.getScale()); + ExoUtil.setSubtitleView(mBinding.exo); + mPlayers.setTag(tag = UUID.randomUUID().toString()); + findViewById(R.id.timeBar).setNextFocusUpId(R.id.reset); + mBinding.control.speed.setText(mPlayers.getSpeedText()); + mBinding.control.decode.setText(mPlayers.getDecodeText()); + mBinding.control.reset.setText(ResUtil.getStringArray(R.array.select_reset)[0]); + } + + private void setDecode() { + mBinding.control.decode.setText(mPlayers.getDecodeText()); + } + + private void setScale(int scale) { + mBinding.exo.setResizeMode(scale); + mBinding.control.scale.setText(ResUtil.getStringArray(R.array.select_scale)[scale]); + } + + private void onScale() { + String[] array = ResUtil.getStringArray(R.array.select_scale); + scale = scale == array.length - 1 ? 0 : ++scale; + setScale(scale); + } + + private void onSpeed() { + mBinding.control.speed.setText(mPlayers.addSpeed()); + } + + private void onSpeedAdd() { + mBinding.control.speed.setText(mPlayers.addSpeed(0.25f)); + } + + private void onSpeedSub() { + mBinding.control.speed.setText(mPlayers.subSpeed(0.25f)); + } + + private boolean onSpeedLong() { + mBinding.control.speed.setText(mPlayers.toggleSpeed()); + return true; + } + + private void onReset() { + position = duration = C.TIME_UNSET; + if (mPlayers.isEmpty()) return; + start(); + } + + private void onChoose() { + mPlayers.choose(this, mBinding.widget.title.getText()); + setRedirect(true); + } + + private void onDecode() { + mPlayers.toggleDecode(); + setDecode(); + } + + private void onTrack(View view) { + TrackDialog.create().player(mPlayers).type(Integer.parseInt(view.getTag().toString())).show(this); + hideControl(); + } + + private void onToggle() { + if (isVisible(mBinding.control.getRoot())) hideControl(); + else showControl(); + } + + private void showProgress() { + mBinding.widget.progress.setVisibility(View.VISIBLE); + App.post(mR2, 0); + hideError(); + } + + private void hideProgress() { + mBinding.widget.progress.setVisibility(View.GONE); + App.removeCallbacks(mR2); + Traffic.reset(); + } + + private void showError(String text) { + mBinding.widget.error.setVisibility(View.VISIBLE); + mBinding.widget.text.setText(text); + hideProgress(); + } + + private void hideError() { + mBinding.widget.error.setVisibility(View.GONE); + mBinding.widget.text.setText(""); + } + + private void showInfo() { + mBinding.widget.info.setVisibility(View.VISIBLE); + mBinding.widget.center.setVisibility(View.VISIBLE); + mBinding.widget.exoDuration.setText(mPlayers.getDurationTime()); + mBinding.widget.exoPosition.setText(mPlayers.getPositionTime(0)); + } + + private void hideInfo() { + mBinding.widget.info.setVisibility(View.GONE); + mBinding.widget.center.setVisibility(View.GONE); + } + + private void showControl() { + mBinding.control.getRoot().setVisibility(View.VISIBLE); + mBinding.control.reset.requestFocus(); + setR1Callback(); + } + + private void hideControl() { + mBinding.control.text.setText(R.string.play_track_text); + mBinding.control.getRoot().setVisibility(View.GONE); + App.removeCallbacks(mR1); + } + + private void hideCenter() { + mBinding.widget.action.setImageResource(R.drawable.ic_widget_play); + hideInfo(); + } + + private void setTraffic() { + Traffic.setSpeed(mBinding.widget.traffic); + App.post(mR2, Constant.INTERVAL_TRAFFIC); + } + + private void setR1Callback() { + App.post(mR1, Constant.INTERVAL_HIDE); + } + + public boolean isRedirect() { + return redirect; + } + + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + + private void checkPlayImg() { + ActionEvent.update(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onActionEvent(ActionEvent event) { + if (ActionEvent.PLAY.equals(event.getAction()) || ActionEvent.PAUSE.equals(event.getAction())) { + onKeyCenter(); + } else if (ActionEvent.STOP.equals(event.getAction())) { + finish(); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType() == RefreshEvent.Type.PLAYER) onReset(); + else if (event.getType() == RefreshEvent.Type.SUBTITLE) mPlayers.setSub(Sub.from(event.getPath())); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerEvent(PlayerEvent event) { + if (!event.getTag().equals(tag)) return; + switch (event.getState()) { + case PlayerEvent.PREPARE: + setDecode(); + setState(RenderState.PREPARING); + break; + case Player.STATE_IDLE: + setState(RenderState.IDLE); + break; + case Player.STATE_BUFFERING: + showProgress(); + setState(RenderState.PREPARING); + break; + case Player.STATE_READY: + hideProgress(); + checkPlayImg(); + mPlayers.reset(); + setState(RenderState.PLAYING); + break; + case Player.STATE_ENDED: + showControl(); + setState(RenderState.STOPPED); + break; + case PlayerEvent.TRACK: + setMetadata(); + setTrackVisible(); + mClock.setCallback(this); + break; + case PlayerEvent.SIZE: + mBinding.widget.size.setText(mPlayers.getSizeText()); + break; + } + } + + private void setTrackVisible() { + mBinding.control.text.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_TEXT) || mPlayers.isVod() ? View.VISIBLE : View.GONE); + mBinding.control.audio.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_AUDIO) ? View.VISIBLE : View.GONE); + mBinding.control.video.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_VIDEO) ? View.VISIBLE : View.GONE); + } + + private void setMetadata() { + mPlayers.setMetadata(mBinding.widget.title.getText().toString(), "", "", mBinding.exo.getDefaultArtwork()); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onErrorEvent(ErrorEvent event) { + if (!event.getTag().equals(tag)) return; + if (mPlayers.retried()) onError(event); + else onReset(); + } + + private void onError(ErrorEvent event) { + showError(event.getMsg()); + mPlayers.resetTrack(); + onStopped(); + } + + private void onPaused() { + setState(RenderState.PAUSED); + mPlayers.pause(); + checkPlayImg(); + showInfo(); + } + + private void onPlay() { + if (mPlayers.isEmpty()) return; + setState(RenderState.PLAYING); + mPlayers.play(); + checkPlayImg(); + hideCenter(); + } + + private void onStopped() { + setState(RenderState.STOPPED); + mPlayers.clearMediaItems(); + mPlayers.reset(); + mPlayers.stop(); + } + + private void setState(RenderState state) { + if (mService != null) mService.notifyAvTransportLastChange(mState = state); + } + + @NonNull + @Override + public RenderState getState() { + return mState; + } + + @Override + public void onSubtitleClick() { + App.post(this::hideControl, 200); + App.post(() -> SubtitleDialog.create().view(mBinding.exo.getSubtitleView()).full(true).show(this), 200); + } + + @Override + public void onTimeChanged() { + position = mPlayers.getPosition(); + duration = mPlayers.getDuration(); + } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + mService = ((RendererServiceBinder) service).getService(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + + @Override + public long getCurrentPosition() { + return position; + } + + @Override + public long getDuration() { + return duration; + } + + @Override + public void seek(long time) { + App.post(() -> mPlayers.seekTo(time)); + } + + @Override + public void pause() { + App.post(this::onPaused); + } + + @Override + public void play(@Nullable Double speed) { + App.post(this::onPlay); + } + + @Override + public void stop() { + App.post(this::onStopped); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (KeyUtil.isMenuKey(event)) onToggle(); + if (isVisible(mBinding.control.getRoot())) setR1Callback(); + if (isGone(mBinding.control.getRoot()) && mKeyDown.hasEvent(event)) return mKeyDown.onKeyDown(event); + return super.dispatchKeyEvent(event); + } + + @Override + public void onSeeking(long time) { + if (mPlayers.isEmpty()) return; + mBinding.widget.center.setVisibility(View.VISIBLE); + mBinding.widget.exoDuration.setText(mPlayers.getDurationTime()); + mBinding.widget.exoPosition.setText(mPlayers.getPositionTime(time)); + mBinding.widget.action.setImageResource(time > 0 ? R.drawable.ic_widget_forward : R.drawable.ic_widget_rewind); + hideProgress(); + } + + @Override + public void onSeekTo(long time) { + if (mPlayers.isEmpty()) return; + mKeyDown.resetTime(); + mPlayers.seek(time); + showProgress(); + onPlay(); + } + + @Override + public void onSpeedUp() { + if (!mPlayers.isPlaying()) return; + mBinding.control.speed.setText(mPlayers.setSpeed(Setting.getSpeed())); + mBinding.widget.speed.startAnimation(ResUtil.getAnim(R.anim.forward)); + mBinding.widget.speed.setVisibility(View.VISIBLE); + } + + @Override + public void onSpeedEnd() { + mBinding.control.speed.setText(mPlayers.setSpeed(1.0f)); + mBinding.widget.speed.setVisibility(View.GONE); + mBinding.widget.speed.clearAnimation(); + } + + @Override + public void onKeyUp() { + showControl(); + } + + @Override + public void onKeyDown() { + showControl(); + } + + @Override + public void onKeyCenter() { + if (mPlayers.isPlaying()) onPaused(); + else onPlay(); + hideControl(); + } + + @Override + public void onSingleTap() { + onToggle(); + } + + @Override + public void onDoubleTap() { + onKeyCenter(); + } + + @Override + protected void onStart() { + super.onStart(); + mClock.stop().start(); + onPlay(); + } + + @Override + protected void onResume() { + super.onResume(); + if (isRedirect()) onPlay(); + setRedirect(false); + } + + @Override + protected void onPause() { + super.onPause(); + if (isRedirect()) onPaused(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Setting.isBackgroundOff()) onPaused(); + if (Setting.isBackgroundOff()) mClock.stop(); + } + + @Override + public void onBackPressed() { + if (isVisible(mBinding.control.getRoot())) { + hideControl(); + } else if (isVisible(mBinding.widget.center)) { + hideCenter(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mClock.release(); + mPlayers.release(); + unbindService(this); + PlaybackService.stop(); + mService.bindRealPlayer(null); + App.removeCallbacks(mR1, mR2); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/CollectActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/CollectActivity.java new file mode 100644 index 00000000..1df55b29 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/CollectActivity.java @@ -0,0 +1,231 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Parcelable; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.OnChildViewHolderSelectedListener; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; +import androidx.viewpager.widget.ViewPager; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Collect; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.ActivityCollectBinding; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.fragment.CollectFragment; +import com.fongmi.android.tv.ui.presenter.CollectPresenter; +import com.fongmi.android.tv.utils.PauseExecutor; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.gson.reflect.TypeToken; + +import java.util.ArrayList; +import java.util.List; + +public class CollectActivity extends BaseActivity { + + private ActivityCollectBinding mBinding; + private ArrayObjectAdapter mAdapter; + private SiteViewModel mViewModel; + private PauseExecutor mExecutor; + private List mSites; + private View mOldView; + + public static void start(Activity activity, String keyword) { + Intent intent = new Intent(activity, CollectActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("keyword", keyword); + activity.startActivity(intent); + } + + private CollectFragment getFragment() { + return (CollectFragment) mBinding.pager.getAdapter().instantiateItem(mBinding.pager, 0); + } + + private String getKeyword() { + return getIntent().getStringExtra("keyword"); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityCollectBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + getIntent().putExtras(intent); + mAdapter.clear(); + setPager(); + search(); + } + + @Override + protected void initView() { + setRecyclerView(); + setViewModel(); + saveKeyword(); + setPager(); + setSite(); + search(); + } + + @Override + protected void initEvent() { + mBinding.pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + mBinding.recycler.setSelectedPosition(position); + } + }); + mBinding.recycler.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + onChildSelected(child); + } + }); + } + + private void setRecyclerView() { + mBinding.recycler.setHorizontalSpacing(ResUtil.dp2px(16)); + mBinding.recycler.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.recycler.setAdapter(new ItemBridgeAdapter(mAdapter = new ArrayObjectAdapter(new CollectPresenter()))); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.search.observe(this, result -> { + getFragment().addVideo(result.getList()); + mAdapter.add(Collect.create(result.getList())); + mBinding.pager.getAdapter().notifyDataSetChanged(); + }); + } + + private void setPager() { + mBinding.pager.setAdapter(new PageAdapter(getSupportFragmentManager())); + } + + private void setSite() { + mSites = new ArrayList<>(); + for (Site site : VodConfig.get().getSites()) if (site.isSearchable()) mSites.add(site); + Site home = VodConfig.get().getHome(); + if (!mSites.contains(home)) return; + mSites.remove(home); + mSites.add(0, home); + } + + private void search() { + mAdapter.add(Collect.all()); + if (mExecutor != null) stop(); + mBinding.pager.getAdapter().notifyDataSetChanged(); + mExecutor = new PauseExecutor(10); + mBinding.result.setText(getString(R.string.collect_result, getKeyword())); + for (Site site : mSites) mExecutor.execute(() -> search(site)); + } + + private void search(Site site) { + try { + mViewModel.searchContent(site, getKeyword(), false); + } catch (Throwable ignored) { + } + } + + private void saveKeyword() { + List items = Setting.getKeyword().isEmpty() ? new ArrayList<>() : App.gson().fromJson(Setting.getKeyword(), new TypeToken>() {}.getType()); + items.remove(getKeyword()); + items.add(0, getKeyword()); + if (items.size() > 8) items.remove(8); + Setting.putKeyword(App.gson().toJson(items)); + } + + private void stop() { + if (mExecutor == null) return; + mExecutor.shutdownNow(); + mExecutor = null; + } + + private void onChildSelected(@Nullable RecyclerView.ViewHolder child) { + if (mOldView != null) mOldView.setActivated(false); + if (child == null) return; + mOldView = child.itemView; + mOldView.setActivated(true); + App.post(mRunnable, 200); + } + + private final Runnable mRunnable = new Runnable() { + @Override + public void run() { + mBinding.pager.setCurrentItem(mBinding.recycler.getSelectedPosition()); + } + }; + + @Override + protected void onResume() { + super.onResume(); + if (mExecutor != null) mExecutor.resume(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mExecutor != null) mExecutor.pause(); + } + + @Override + public void onBackPressed() { + super.onBackPressed(); + stop(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + stop(); + } + + class PageAdapter extends FragmentStatePagerAdapter { + + public PageAdapter(@NonNull FragmentManager fm) { + super(fm); + } + + @NonNull + @Override + public Fragment getItem(int position) { + return CollectFragment.newInstance(getKeyword(), (Collect) mAdapter.get(position)); + } + + @Override + public int getCount() { + return mAdapter.size(); + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + } + + @Nullable + @Override + public Parcelable saveState() { + return null; + } + + @Override + public void restoreState(@Nullable Parcelable state, @Nullable ClassLoader loader) { + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/FileActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/FileActivity.java new file mode 100644 index 00000000..1a7bbc29 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/FileActivity.java @@ -0,0 +1,78 @@ +package com.fongmi.android.tv.ui.activity; + +import android.Manifest; +import android.content.Intent; +import android.net.Uri; + +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.ActivityFileBinding; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.presenter.FilePresenter; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.utils.Path; +import com.permissionx.guolindev.PermissionX; + +import java.io.File; + +public class FileActivity extends BaseActivity implements FilePresenter.OnClickListener { + + private ActivityFileBinding mBinding; + private ArrayObjectAdapter mAdapter; + private File dir; + + private boolean isRoot() { + return Path.root().equals(dir); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityFileBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + setRecyclerView(); + checkPermission(); + } + + private void checkPermission() { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> { + if (allGranted) update(Path.root()); + else finish(); + }); + } + + private void setRecyclerView() { + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.setVerticalSpacing(ResUtil.dp2px(16)); + mBinding.recycler.setAdapter(new ItemBridgeAdapter(mAdapter = new ArrayObjectAdapter(new FilePresenter(this)))); + } + + private void update(File dir) { + mBinding.recycler.setSelectedPosition(0); + mAdapter.setItems(Path.list(this.dir = dir), null); + mBinding.progressLayout.showContent(true, mAdapter.size()); + } + + @Override + public void onItemClick(File file) { + if (file.isDirectory()) { + update(file); + } else { + setResult(RESULT_OK, new Intent().setData(Uri.fromFile(file))); + finish(); + } + } + + @Override + public void onBackPressed() { + if (isRoot()) { + super.onBackPressed(); + } else { + update(dir.getParentFile()); + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java new file mode 100644 index 00000000..952e8d16 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java @@ -0,0 +1,496 @@ +package com.fongmi.android.tv.ui.activity; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.KeyEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.ListRow; +import androidx.leanback.widget.OnChildViewHolderSelectedListener; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Updater; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Func; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.ActivityHomeBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.CastEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.event.ServerEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.CustomRowPresenter; +import com.fongmi.android.tv.ui.custom.CustomSelector; +import com.fongmi.android.tv.ui.custom.CustomTitleView; +import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.ui.presenter.FuncPresenter; +import com.fongmi.android.tv.ui.presenter.HeaderPresenter; +import com.fongmi.android.tv.ui.presenter.HistoryPresenter; +import com.fongmi.android.tv.ui.presenter.ProgressPresenter; +import com.fongmi.android.tv.ui.presenter.VodPresenter; +import com.fongmi.android.tv.utils.Clock; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.google.common.collect.Lists; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.List; + +public class HomeActivity extends BaseActivity implements CustomTitleView.Listener, VodPresenter.OnClickListener, FuncPresenter.OnClickListener, HistoryPresenter.OnClickListener { + + private ActivityHomeBinding mBinding; + private ArrayObjectAdapter mHistoryAdapter; + private HistoryPresenter mPresenter; + private ArrayObjectAdapter mAdapter; + private SiteViewModel mViewModel; + private boolean loading; + private Result mResult; + private Clock mClock; + + private Site getHome() { + return VodConfig.get().getHome(); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityHomeBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + checkAction(intent); + } + + @Override + protected void initView() { + mClock = Clock.create(mBinding.clock).format("MM/dd HH:mm:ss"); + mBinding.progressLayout.showProgress(); + Updater.create().release().start(this); + mResult = Result.empty(); + Server.get().start(); + setRecyclerView(); + setViewModel(); + setAdapter(); + initConfig(); + setLogo(); + } + + @Override + protected void initEvent() { + mBinding.title.setListener(this); + mBinding.recycler.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + mBinding.toolbar.setVisibility(position == 0 ? View.VISIBLE : View.GONE); + if (mPresenter.isDelete()) setHistoryDelete(false); + } + }); + } + + private void checkAction(Intent intent) { + if (Intent.ACTION_SEND.equals(intent.getAction())) { + VideoActivity.push(this, intent.getStringExtra(Intent.EXTRA_TEXT)); + } else if (Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() != null) { + if ("text/plain".equals(intent.getType()) || UrlUtil.path(intent.getData()).endsWith(".m3u")) { + loadLive("file:/" + FileChooser.getPathFromUri(this, intent.getData())); + } else { + VideoActivity.push(this, intent.getData().toString()); + } + } + } + + private void setRecyclerView() { + CustomSelector selector = new CustomSelector(); + selector.addPresenter(Integer.class, new HeaderPresenter()); + selector.addPresenter(String.class, new ProgressPresenter()); + selector.addPresenter(ListRow.class, new CustomRowPresenter(16), VodPresenter.class); + selector.addPresenter(ListRow.class, new CustomRowPresenter(16), FuncPresenter.class); + selector.addPresenter(ListRow.class, new CustomRowPresenter(16), HistoryPresenter.class); + mBinding.recycler.setAdapter(new ItemBridgeAdapter(mAdapter = new ArrayObjectAdapter(selector))); + mBinding.recycler.setVerticalSpacing(ResUtil.dp2px(16)); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.result.observe(this, result -> { + mAdapter.remove("progress"); + addVideo(mResult = result); + }); + } + + private void setAdapter() { + mAdapter.add(getFuncRow()); + mAdapter.add(R.string.home_history); + mAdapter.add(R.string.home_recommend); + mHistoryAdapter = new ArrayObjectAdapter(mPresenter = new HistoryPresenter(this)); + } + + private void initConfig() { + if (isLoading()) return; + WallConfig.get().init(); + LiveConfig.get().init().load(); + VodConfig.get().init().load(getCallback()); + setLoading(true); + } + + private Callback getCallback() { + return new Callback() { + @Override + public void success(String result) { + Notify.show(result); + } + + @Override + public void success() { + mBinding.progressLayout.showContent(); + checkAction(getIntent()); + getHistory(); + getVideo(); + setFocus(); + setLogo(); + } + + @Override + public void error(String msg) { + mBinding.progressLayout.showContent(); + mResult = Result.empty(); + Notify.show(msg); + setFocus(); + } + }; + } + + private void loadLive(String url) { + LiveConfig.load(Config.find(url, 1), new Callback() { + @Override + public void success() { + LiveActivity.start(getActivity()); + } + }); + } + + private void setFocus() { + setLoading(false); + App.post(() -> mBinding.title.setFocusable(true), 500); + if (!mBinding.title.hasFocus()) mBinding.recycler.requestFocus(); + } + + private void getVideo() { + mResult = Result.empty(); + int index = getRecommendIndex(); + String title = getHome().getName(); + mBinding.title.setText(title.isEmpty() ? ResUtil.getString(R.string.app_name) : title); + if (mAdapter.size() > index) mAdapter.removeItems(index, mAdapter.size() - index); + if (getHome().getKey().isEmpty()) return; + mViewModel.homeContent(); + mAdapter.add("progress"); + } + + private void addVideo(Result result) { + Style style = result.getStyle(getHome().getStyle()); + for (List items : Lists.partition(result.getList(), Product.getColumn(style))) { + ArrayObjectAdapter adapter = new ArrayObjectAdapter(new VodPresenter(this, style)); + adapter.setItems(items, null); + mAdapter.add(new ListRow(adapter)); + } + } + + private ListRow getFuncRow() { + ArrayObjectAdapter adapter = new ArrayObjectAdapter(new FuncPresenter(this)); + adapter.add(Func.create(R.string.home_vod)); + adapter.add(Func.create(R.string.home_live)); + adapter.add(Func.create(R.string.home_search)); + adapter.add(Func.create(R.string.home_keep)); + adapter.add(Func.create(R.string.home_push)); + adapter.add(Func.create(R.string.home_cast)); + adapter.add(Func.create(R.string.home_setting)); + return new ListRow(adapter); + } + + private void getHistory() { + getHistory(false); + } + + private void getHistory(boolean renew) { + List items = History.get(); + int historyIndex = getHistoryIndex(); + int recommendIndex = getRecommendIndex(); + boolean exist = recommendIndex - historyIndex == 2; + if (renew) mHistoryAdapter = new ArrayObjectAdapter(mPresenter = new HistoryPresenter(this)); + if ((items.isEmpty() && exist) || (renew && exist)) mAdapter.removeItems(historyIndex, 1); + if ((!items.isEmpty() && !exist) || (renew && exist)) mAdapter.add(historyIndex, new ListRow(mHistoryAdapter)); + mHistoryAdapter.setItems(items, null); + } + + private void setHistoryDelete(boolean delete) { + mPresenter.setDelete(delete); + mHistoryAdapter.notifyArrayItemRangeChanged(0, mHistoryAdapter.size()); + } + + private void clearHistory() { + mAdapter.removeItems(getHistoryIndex(), 1); + History.delete(VodConfig.getCid()); + mPresenter.setDelete(false); + mHistoryAdapter.clear(); + } + + private int getHistoryIndex() { + for (int i = 0; i < mAdapter.size(); i++) if (mAdapter.get(i).equals(R.string.home_history)) return i + 1; + return -1; + } + + private int getRecommendIndex() { + for (int i = 0; i < mAdapter.size(); i++) if (mAdapter.get(i).equals(R.string.home_recommend)) return i + 1; + return -1; + } + + private boolean isLoading() { + return loading; + } + + private void setLoading(boolean loading) { + this.loading = loading; + } + + private void setLogo() { + Glide.with(App.get()).load(UrlUtil.convert(VodConfig.get().getConfig().getLogo())).circleCrop().override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).listener(getListener()).into(mBinding.logo); + } + + private RequestListener getListener() { + return new RequestListener<>() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, @NonNull Target target, boolean isFirstResource) { + mBinding.logo.setVisibility(View.GONE); + return false; + } + + @Override + public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + mBinding.logo.setVisibility(View.VISIBLE); + return false; + } + }; + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + super.onRefreshEvent(event); + switch (event.getType()) { + case CONFIG: + setLogo(); + break; + case VIDEO: + getVideo(); + break; + case IMAGE: + int index = getRecommendIndex(); + mAdapter.notifyArrayItemRangeChanged(index, mAdapter.size() - index); + break; + case HISTORY: + getHistory(); + break; + case SIZE: + getVideo(); + getHistory(true); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onServerEvent(ServerEvent event) { + switch (event.getType()) { + case SEARCH: + CollectActivity.start(this, event.getText()); + break; + case PUSH: + VideoActivity.push(this, event.getText()); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onCastEvent(CastEvent event) { + if (VodConfig.get().getConfig().equals(event.getConfig())) { + VideoActivity.cast(this, event.getHistory().update(VodConfig.getCid())); + } else { + VodConfig.load(event.getConfig(), getCallback(event)); + } + } + + private Callback getCallback(CastEvent event) { + return new Callback() { + @Override + public void success() { + RefreshEvent.history(); + RefreshEvent.config(); + RefreshEvent.video(); + onCastEvent(event); + } + + @Override + public void error(String msg) { + Notify.show(msg); + } + }; + } + + @Override + public void onItemClick(Func item) { + switch (item.getResId()) { + case R.string.home_vod: + VodActivity.start(this, mResult.clear()); + break; + case R.string.home_live: + LiveActivity.start(this); + break; + case R.string.home_search: + SearchActivity.start(this); + break; + case R.string.home_keep: + KeepActivity.start(this); + break; + case R.string.home_push: + PushActivity.start(this); + break; + case R.string.home_cast: + CastActivity.start(this); + break; + case R.string.home_setting: + SettingActivity.start(this); + break; + } + } + + @Override + public void onItemClick(Vod item) { + if (item.isAction()) mViewModel.action(getHome().getKey(), item.getAction()); + else if (getHome().isIndex()) CollectActivity.start(getActivity(), item.getVodName()); + else VideoActivity.start(this, getHome().getKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public boolean onLongClick(Vod item) { + CollectActivity.start(this, item.getVodName()); + return true; + } + + @Override + public void onItemClick(History item) { + VideoActivity.start(this, item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public void onItemDelete(History item) { + mHistoryAdapter.remove(item.delete()); + if (mHistoryAdapter.size() > 0) return; + mAdapter.removeItems(getHistoryIndex(), 1); + mPresenter.setDelete(false); + } + + @Override + public boolean onLongClick() { + if (mPresenter.isDelete()) clearHistory(); + else setHistoryDelete(true); + return true; + } + + @Override + public void showDialog() { + SiteDialog.create(this).show(); + } + + @Override + public void onRefresh() { + getVideo(); + } + + @Override + public void setSite(Site item) { + VodConfig.get().setHome(item); + getVideo(); + } + + @Override + public void onChanged() { + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (KeyUtil.isMenuKey(event)) showDialog(); + return super.dispatchKeyEvent(event); + } + + @Override + protected void onResume() { + super.onResume(); + mClock.start(); + } + + @Override + protected void onPause() { + super.onPause(); + mClock.stop(); + } + + @Override + protected boolean handleBack() { + return true; + } + + @Override + protected void onBackPress() { + if (mBinding.progressLayout.isProgress()) { + mBinding.progressLayout.showContent(); + } else if (mPresenter.isDelete()) { + setHistoryDelete(false); + } else if (mBinding.recycler.getSelectedPosition() != 0) { + mBinding.recycler.scrollToPosition(0); + } else { + finish(); + } + } + + @Override + protected void onDestroy() { + WallConfig.get().clear(); + LiveConfig.get().clear(); + VodConfig.get().clear(); + OkHttp.get().clear(); + AppDatabase.backup(); + Server.get().stop(); + Source.get().exit(); + super.onDestroy(); + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/KeepActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/KeepActivity.java new file mode 100644 index 00000000..b0dfdcb1 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/KeepActivity.java @@ -0,0 +1,103 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.databinding.ActivityKeepBinding; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.adapter.KeepAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.Notify; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class KeepActivity extends BaseActivity implements KeepAdapter.OnClickListener { + + private ActivityKeepBinding mBinding; + private KeepAdapter mAdapter; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, KeepActivity.class)); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityKeepBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + setRecyclerView(); + getKeep(); + } + + private void setRecyclerView() { + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.setItemAnimator(null); + mBinding.recycler.setAdapter(mAdapter = new KeepAdapter(this)); + mBinding.recycler.setLayoutManager(new GridLayoutManager(this, Product.getColumn())); + mBinding.recycler.addItemDecoration(new SpaceItemDecoration(Product.getColumn(), 16)); + } + + private void getKeep() { + mAdapter.addAll(Keep.getVod()); + } + + private void loadConfig(Config config, Keep item) { + VodConfig.load(config, new Callback() { + @Override + public void success() { + VideoActivity.start(getActivity(), item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + RefreshEvent.history(); + RefreshEvent.config(); + RefreshEvent.video(); + } + + @Override + public void error(String msg) { + Notify.show(msg); + } + }); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType() == RefreshEvent.Type.KEEP) getKeep(); + } + + @Override + public void onItemClick(Keep item) { + Config config = Config.find(item.getCid()); + if (config == null) CollectActivity.start(this, item.getVodName()); + else if (item.getCid() != VodConfig.getCid()) loadConfig(config, item); + else VideoActivity.start(this, item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public void onItemDelete(Keep item) { + mAdapter.delete(item.delete()); + if (mAdapter.getItemCount() == 0) mAdapter.setDelete(false); + } + + @Override + public boolean onLongClick() { + mAdapter.setDelete(true); + return true; + } + + @Override + public void onBackPressed() { + if (mAdapter.isDelete()) mAdapter.setDelete(false); + else super.onBackPressed(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java new file mode 100644 index 00000000..036f6ce5 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java @@ -0,0 +1,1016 @@ +package com.fongmi.android.tv.ui.activity; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.KeyEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.OnChildViewHolderSelectedListener; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.C; +import androidx.media3.common.Player; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Epg; +import com.fongmi.android.tv.bean.EpgData; +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Track; +import com.fongmi.android.tv.databinding.ActivityLiveBinding; +import com.fongmi.android.tv.event.ActionEvent; +import com.fongmi.android.tv.event.ErrorEvent; +import com.fongmi.android.tv.event.PlayerEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.LiveCallback; +import com.fongmi.android.tv.impl.PassCallback; +import com.fongmi.android.tv.model.LiveViewModel; +import com.fongmi.android.tv.player.Players; +import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.player.exo.ExoUtil; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.service.PlaybackService; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.CustomKeyDownLive; +import com.fongmi.android.tv.ui.custom.CustomLiveListView; +import com.fongmi.android.tv.ui.dialog.LiveDialog; +import com.fongmi.android.tv.ui.dialog.PassDialog; +import com.fongmi.android.tv.ui.dialog.SubtitleDialog; +import com.fongmi.android.tv.ui.dialog.TrackDialog; +import com.fongmi.android.tv.ui.presenter.ChannelPresenter; +import com.fongmi.android.tv.ui.presenter.EpgDataPresenter; +import com.fongmi.android.tv.ui.presenter.GroupPresenter; +import com.fongmi.android.tv.utils.Clock; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Traffic; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +public class LiveActivity extends BaseActivity implements GroupPresenter.OnClickListener, ChannelPresenter.OnClickListener, EpgDataPresenter.OnClickListener, CustomKeyDownLive.Listener, CustomLiveListView.Callback, TrackDialog.Listener, PassCallback, LiveCallback { + + private ActivityLiveBinding mBinding; + private ArrayObjectAdapter mChannelAdapter; + private ArrayObjectAdapter mEpgDataAdapter; + private ArrayObjectAdapter mGroupAdapter; + private Observer mObserveUrl; + private CustomKeyDownLive mKeyDown; + private Observer mObserveEpg; + private LiveViewModel mViewModel; + private List mHides; + private Players mPlayers; + private Channel mChannel; + private View mOldView; + private Group mGroup; + private Runnable mR0; + private Runnable mR1; + private Runnable mR2; + private Runnable mR3; + private Runnable mR4; + private Clock mClock; + private boolean redirect; + private String tag; + private int count; + + public static void start(Context context) { + if (!LiveConfig.isEmpty()) context.startActivity(new Intent(context, LiveActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("empty", false)); + } + + private boolean isEmpty() { + return getIntent().getBooleanExtra("empty", true); + } + + private Group getKeep() { + return (Group) mGroupAdapter.get(0); + } + + private Live getHome() { + return LiveConfig.get().getHome(); + } + + private long getTimeout() { + return getHome().isEmpty() ? Constant.TIMEOUT_PLAY : getHome().getTimeout(); + } + + @Override + protected boolean customWall() { + return false; + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityLiveBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + mClock = Clock.create(mBinding.widget.clock); + mKeyDown = CustomKeyDownLive.create(this); + mPlayers = Players.create(this); + mObserveEpg = this::setEpg; + mObserveUrl = this::start; + mHides = new ArrayList<>(); + mR0 = this::setActivated; + mR1 = this::hideControl; + mR2 = this::setTraffic; + mR3 = this::hideInfo; + mR4 = this::hideUI; + Server.get().start(); + setRecyclerView(); + setVideoView(); + setViewModel(); + checkLive(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + protected void initEvent() { + mBinding.group.setListener(this); + mBinding.channel.setListener(this); + mBinding.epgData.setListener(this); + mBinding.control.seek.setListener(mPlayers); + mBinding.control.text.setOnClickListener(this::onTrack); + mBinding.control.audio.setOnClickListener(this::onTrack); + mBinding.control.video.setOnClickListener(this::onTrack); + mBinding.control.speed.setUpListener(this::onSpeedAdd); + mBinding.control.speed.setDownListener(this::onSpeedSub); + mBinding.control.text.setUpListener(this::onSubtitleClick); + mBinding.control.text.setDownListener(this::onSubtitleClick); + mBinding.control.home.setOnClickListener(view -> onHome()); + mBinding.control.line.setOnClickListener(view -> onLine()); + mBinding.control.scale.setOnClickListener(view -> onScale()); + mBinding.control.speed.setOnClickListener(view -> onSpeed()); + mBinding.control.action.setOnClickListener(view -> onAction()); + mBinding.control.invert.setOnClickListener(view -> onInvert()); + mBinding.control.across.setOnClickListener(view -> onAcross()); + mBinding.control.change.setOnClickListener(view -> onChange()); + mBinding.control.player.setOnClickListener(view -> onChoose()); + mBinding.control.decode.setOnClickListener(view -> onDecode()); + mBinding.control.speed.setOnLongClickListener(view -> onSpeedLong()); + mBinding.video.setOnTouchListener((view, event) -> mKeyDown.onTouchEvent(event)); + mBinding.group.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + if (mGroupAdapter.size() > 0) onChildSelected(child, mGroup = (Group) mGroupAdapter.get(position)); + } + }); + } + + private void setRecyclerView() { + mBinding.group.setItemAnimator(null); + mBinding.channel.setItemAnimator(null); + mBinding.epgData.setItemAnimator(null); + mBinding.group.setAdapter(new ItemBridgeAdapter(mGroupAdapter = new ArrayObjectAdapter(new GroupPresenter(this)))); + mBinding.channel.setAdapter(new ItemBridgeAdapter(mChannelAdapter = new ArrayObjectAdapter(new ChannelPresenter(this)))); + mBinding.epgData.setAdapter(new ItemBridgeAdapter(mEpgDataAdapter = new ArrayObjectAdapter(new EpgDataPresenter(this)))); + } + + private void setVideoView() { + mPlayers.init(mBinding.exo); + PlaybackService.start(mPlayers); + setScale(Setting.getLiveScale()); + ExoUtil.setSubtitleView(mBinding.exo); + mPlayers.setTag(tag = UUID.randomUUID().toString()); + findViewById(R.id.timeBar).setNextFocusUpId(R.id.home); + mBinding.control.invert.setActivated(Setting.isInvert()); + mBinding.control.across.setActivated(Setting.isAcross()); + mBinding.control.change.setActivated(Setting.isChange()); + mBinding.control.speed.setText(mPlayers.getSpeedText()); + mBinding.control.decode.setText(mPlayers.getDecodeText()); + } + + private void setDecode() { + mBinding.control.decode.setText(mPlayers.getDecodeText()); + } + + private void setScale(int scale) { + mBinding.exo.setResizeMode(scale); + mBinding.control.scale.setText(ResUtil.getStringArray(R.array.select_scale)[scale]); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(LiveViewModel.class); + mViewModel.url.observeForever(mObserveUrl); + mViewModel.xml.observe(this, this::setEpg); + mViewModel.epg.observeForever(mObserveEpg); + mViewModel.live.observe(this, live -> { + mViewModel.getXml(live); + hideProgress(); + setGroup(live); + setWidth(live); + }); + } + + private void checkLive() { + if (isEmpty()) { + LiveConfig.get().init().load(getCallback()); + } else { + getLive(); + } + } + + private Callback getCallback() { + return new Callback() { + @Override + public void success() { + getLive(); + } + + @Override + public void error(String msg) { + Notify.show(msg); + } + }; + } + + private void getLive() { + mBinding.control.home.setText(LiveConfig.isOnly() ? getString(R.string.live_refresh) : getHome().getName()); + mViewModel.getLive(getHome()); + showProgress(); + } + + private void setGroup(Live live) { + List items = new ArrayList<>(); + for (Group group : live.getGroups()) (group.isHidden() ? mHides : items).add(group); + mGroupAdapter.setItems(items, null); + setPosition(LiveConfig.get().find(items)); + } + + private void setWidth(Live live) { + int padding = ResUtil.dp2px(48); + if (live.getWidth() == 0) for (Group item : live.getGroups()) live.setWidth(Math.max(live.getWidth(), ResUtil.getTextWidth(item.getName(), 16))); + mBinding.group.getLayoutParams().width = live.getWidth() == 0 ? 0 : Math.min(live.getWidth() + padding, ResUtil.getScreenWidth() / 4); + } + + private Group setWidth(Group group) { + int logo = ResUtil.dp2px(60); + int padding = ResUtil.dp2px(60); + if (group.isKeep()) group.setWidth(0); + if (group.getWidth() == 0) for (Channel item : group.getChannel()) group.setWidth(Math.max(group.getWidth(), (item.getLogo().isEmpty() ? 0 : logo) + ResUtil.getTextWidth(item.getNumber() + item.getName(), 16))); + mBinding.channel.getLayoutParams().width = group.getWidth() == 0 ? 0 : Math.min(group.getWidth() + padding, ResUtil.getScreenWidth() / 2); + return group; + } + + private void setWidth(Epg epg) { + int padding = ResUtil.dp2px(48); + if (epg.getList().isEmpty()) return; + int minWidth = ResUtil.getTextWidth(epg.getList().get(0).getTime(), 16); + if (epg.getWidth() == 0) for (EpgData item : epg.getList()) epg.setWidth(Math.max(epg.getWidth(), ResUtil.getTextWidth(item.getTitle(), 16))); + mBinding.epgData.getLayoutParams().width = epg.getWidth() == 0 ? 0 : Math.min(Math.max(epg.getWidth(), minWidth) + padding, ResUtil.getScreenWidth() / 2); + } + + private void setPosition(int[] position) { + if (position[0] == -1) return; + int size = mGroupAdapter.size(); + if (size == 1 || position[0] >= size) return; + mGroup = (Group) mGroupAdapter.get(position[0]); + mBinding.group.setSelectedPosition(position[0]); + mGroup.setPosition(position[1]); + onItemClick(mGroup); + onItemClick(mGroup.current()); + } + + private void setPosition() { + if (mChannel == null) return; + mGroup = mChannel.getGroup(); + int position = mGroupAdapter.indexOf(mGroup); + boolean change = mBinding.group.getSelectedPosition() != position; + if (change) mBinding.group.setSelectedPosition(position); + if (change) mChannelAdapter.setItems(mGroup.getChannel(), null); + mBinding.channel.setSelectedPosition(mGroup.getPosition()); + } + + private void onChildSelected(@Nullable RecyclerView.ViewHolder child, Group group) { + if (mOldView != null) mOldView.setSelected(false); + if (child == null) return; + mOldView = child.itemView; + mOldView.setSelected(true); + onItemClick(group); + resetPass(); + } + + private void setActivated() { + for (int i = 0; i < mChannelAdapter.size(); i++) ((Channel) mChannelAdapter.get(i)).setSelected(mChannel); + notifyItemChanged(mBinding.channel, mChannelAdapter); + fetch(); + } + + private void setActivated(EpgData item) { + for (int i = 0; i < mEpgDataAdapter.size(); i++) ((EpgData) mEpgDataAdapter.get(i)).setSelected(item); + notifyItemChanged(mBinding.epgData, mEpgDataAdapter); + } + + private void checkPlay() { + if (mPlayers.isPlaying()) onPaused(); + else onPlay(); + } + + private void onTrack(View view) { + TrackDialog.create().player(mPlayers).type(Integer.parseInt(view.getTag().toString())).show(this); + hideControl(); + } + + private void onHome() { + if (LiveConfig.isOnly()) setLive(getHome()); + else LiveDialog.create(this).show(); + hideControl(); + } + + private void onLine() { + nextLine(false); + } + + private void onScale() { + int index = Setting.getLiveScale(); + String[] array = ResUtil.getStringArray(R.array.select_scale); + Setting.putLiveScale(index = index == array.length - 1 ? 0 : ++index); + setScale(index); + } + + private void onSpeed() { + mBinding.control.speed.setText(mPlayers.addSpeed()); + } + + private void onSpeedAdd() { + mBinding.control.speed.setText(mPlayers.addSpeed(0.25f)); + } + + private void onSpeedSub() { + mBinding.control.speed.setText(mPlayers.subSpeed(0.25f)); + } + + private boolean onSpeedLong() { + mBinding.control.speed.setText(mPlayers.toggleSpeed()); + return true; + } + + private void onAction() { + checkPlay(); + } + + private void onInvert() { + Setting.putInvert(!Setting.isInvert()); + mBinding.control.invert.setActivated(Setting.isInvert()); + } + + private void onAcross() { + Setting.putAcross(!Setting.isAcross()); + mBinding.control.across.setActivated(Setting.isAcross()); + } + + private void onChange() { + Setting.putChange(!Setting.isChange()); + mBinding.control.change.setActivated(Setting.isChange()); + } + + private void onChoose() { + mPlayers.choose(this, mBinding.widget.title.getText()); + setRedirect(true); + } + + private void onDecode() { + mPlayers.toggleDecode(); + setDecode(); + } + + private void hideUI() { + App.removeCallbacks(mR4); + if (isGone(mBinding.recycler)) return; + mBinding.recycler.setVisibility(View.GONE); + setPosition(); + } + + private void showUI() { + if (isVisible(mBinding.recycler)) return; + mBinding.recycler.setVisibility(View.VISIBLE); + setPosition(); + setUITimer(); + hideEpg(); + } + + @Override + public void showEpg(Channel item) { + if (mChannel == null || mChannel.getData().getList().isEmpty() || mEpgDataAdapter.size() == 0 || !mChannel.equals(item) || !mChannel.getGroup().equals(mGroup)) return; + mBinding.epgData.setSelectedPosition(mChannel.getData().getSelected()); + mBinding.epgData.setVisibility(View.VISIBLE); + mBinding.channel.setVisibility(View.GONE); + mBinding.group.setVisibility(View.GONE); + mBinding.epgData.requestFocus(); + } + + @Override + public void hideEpg() { + mBinding.channel.setVisibility(View.VISIBLE); + mBinding.group.setVisibility(View.VISIBLE); + mBinding.epgData.setVisibility(View.GONE); + mBinding.channel.requestFocus(); + } + + private void showProgress() { + mBinding.widget.progress.setVisibility(View.VISIBLE); + App.post(mR2, 0); + hideError(); + } + + private void hideProgress() { + mBinding.widget.progress.setVisibility(View.GONE); + App.removeCallbacks(mR2); + Traffic.reset(); + } + + private void showError(String text) { + mBinding.widget.error.setVisibility(View.VISIBLE); + mBinding.widget.text.setText(text); + hideProgress(); + } + + private void hideError() { + mBinding.widget.error.setVisibility(View.GONE); + mBinding.widget.text.setText(""); + } + + private void showControl(View view) { + mBinding.control.getRoot().setVisibility(View.VISIBLE); + mBinding.widget.top.setVisibility(View.VISIBLE); + App.post(view::requestFocus, 25); + setR1Callback(); + hideInfo(); + } + + private void hideControl() { + mBinding.control.getRoot().setVisibility(View.GONE); + mBinding.widget.top.setVisibility(View.GONE); + App.removeCallbacks(mR1); + } + + private void hideCenter() { + mBinding.widget.action.setImageResource(R.drawable.ic_widget_play); + mBinding.widget.center.setVisibility(View.GONE); + } + + private void showInfo() { + mBinding.widget.bottom.setVisibility(View.VISIBLE); + setR3Callback(); + setInfo(); + } + + private void hideInfo() { + mBinding.widget.bottom.setVisibility(View.GONE); + App.removeCallbacks(mR3); + } + + private void setTraffic() { + Traffic.setSpeed(mBinding.widget.traffic); + App.post(mR2, Constant.INTERVAL_TRAFFIC); + } + + private void setR1Callback() { + App.post(mR1, Constant.INTERVAL_HIDE); + } + + private void setR3Callback() { + App.post(mR3, Constant.INTERVAL_HIDE); + } + + private void onToggle() { + if (isVisible(mBinding.control.getRoot())) hideControl(); + else if (isVisible(mBinding.recycler)) hideUI(); + else showUI(); + hideInfo(); + } + + private void resetPass() { + this.count = 0; + } + + private void setArtwork(String url) { + ImgUtil.load(url, R.drawable.radio, new CustomTarget<>(ResUtil.getScreenWidth(), ResUtil.getScreenHeight()) { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + mBinding.exo.setDefaultArtwork(resource); + } + + @Override + public void onLoadFailed(@Nullable Drawable error) { + mBinding.exo.setDefaultArtwork(error); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + } + }); + } + + @Override + public void onItemClick(Group item) { + mChannelAdapter.setItems(setWidth(item).getChannel(), null); + mBinding.channel.setSelectedPosition(Math.max(item.getPosition(), 0)); + if (!item.isKeep() || ++count < 5 || mHides.isEmpty()) return; + PassDialog.create().show(this); + App.removeCallbacks(mR4); + resetPass(); + } + + @Override + public void onItemClick(Channel item) { + if (!item.getData().getList().isEmpty() && item.isSelected() && mChannel != null && mChannel.equals(item) && mChannel.getGroup().equals(mGroup)) { + showEpg(item); + } else { + mGroup.setPosition(mBinding.channel.getSelectedPosition()); + setChannel(item.group(mGroup)); + hideUI(); + } + } + + @Override + public boolean onLongClick(Channel item) { + if (mGroup.isHidden()) return false; + boolean exist = Keep.exist(item.getName()); + Notify.show(exist ? R.string.keep_del : R.string.keep_add); + if (exist) delKeep(item); + else addKeep(item); + return true; + } + + @Override + public void onItemClick(EpgData item) { + if (item.isSelected()) { + fetch(item); + } else if (mChannel.hasCatchup()) { + mBinding.widget.title.setText(getString(R.string.detail_title, mChannel.getName(), item.getTitle())); + Notify.show(getString(R.string.play_ready, item.getTitle())); + setActivated(item); + fetch(item); + } + } + + private void addKeep(Channel item) { + getKeep().add(item); + Keep keep = new Keep(); + keep.setKey(item.getName()); + keep.setType(1); + keep.save(); + } + + private void delKeep(Channel item) { + if (mGroup.isKeep()) mChannelAdapter.remove(item); + if (mChannelAdapter.size() == 0) mBinding.group.requestFocus(); + getKeep().getChannel().remove(item); + Keep.delete(item.getName()); + } + + private void setChannel(Channel item) { + setArtwork(item.getLogo()); + App.post(mR0, 100); + mChannel = item; + showInfo(); + } + + private void setInfo() { + mViewModel.getEpg(mChannel); + mBinding.widget.play.setText(""); + mChannel.loadLogo(mBinding.widget.logo); + mBinding.widget.title.setSelected(true); + mBinding.widget.name.setText(mChannel.getName()); + mBinding.widget.title.setText(mChannel.getName()); + mBinding.widget.line.setText(mChannel.getLineText()); + mBinding.widget.number.setText(mChannel.getNumber()); + mBinding.control.line.setText(mChannel.getLineText()); + mBinding.widget.line.setVisibility(mChannel.getLineVisible()); + mBinding.control.line.setVisibility(mChannel.getLineVisible()); + } + + private void setEpg() { + EpgData data = mChannel.getData().getEpgData(); + boolean hasTitle = !data.getTitle().isEmpty(); + mEpgDataAdapter.setItems(mChannel.getData().getList(), null); + if (hasTitle) mBinding.widget.title.setText(getString(R.string.detail_title, mChannel.getName(), data.getTitle())); + mBinding.widget.name.setMaxEms(hasTitle ? 12 : 48); + mBinding.widget.play.setText(data.format()); + setWidth(mChannel.getData()); + setMetadata(); + } + + private void setEpg(boolean success) { + if (mChannel != null && success) mViewModel.getEpg(mChannel); + } + + private void setEpg(Epg epg) { + if (mChannel != null && mChannel.getTvgName().equals(epg.getKey())) setEpg(); + } + + private void fetch(EpgData item) { + if (mChannel == null) return; + mViewModel.getUrl(mChannel, item); + mPlayers.clear(); + mPlayers.stop(); + hideUI(); + } + + private void fetch() { + if (mChannel == null) return; + LiveConfig.get().setKeep(mChannel); + mViewModel.getUrl(mChannel); + mPlayers.clear(); + mPlayers.stop(); + showProgress(); + } + + private void start(Channel result) { + mPlayers.start(result, getTimeout()); + } + + private void checkPlayImg() { + ActionEvent.update(); + mBinding.control.action.setText(mPlayers.isPlaying() ? R.string.pause : R.string.play); + } + + private void resetAdapter() { + mBinding.channel.getLayoutParams().width = 0; + mBinding.epgData.getLayoutParams().width = 0; + mBinding.group.getLayoutParams().width = 0; + mEpgDataAdapter.clear(); + mChannelAdapter.clear(); + mGroupAdapter.clear(); + mHides.clear(); + mChannel = null; + mGroup = null; + } + + @Override + public void onSubtitleClick() { + App.post(this::hideControl, 200); + App.post(() -> SubtitleDialog.create().view(mBinding.exo.getSubtitleView()).full(true).show(this), 200); + } + + @Override + public void setLive(Live item) { + if (item.isActivated()) item.getGroups().clear(); + LiveConfig.get().setHome(item); + mPlayers.reset(); + mPlayers.stop(); + resetAdapter(); + hideControl(); + getLive(); + } + + @Override + public void setPass(String pass) { + unlock(pass); + } + + private void unlock(String pass) { + boolean first = true; + int position = mGroupAdapter.size(); + Iterator iterator = mHides.iterator(); + while (iterator.hasNext()) { + Group item = iterator.next(); + if (pass != null && !pass.equals(item.getPass())) continue; + mGroupAdapter.add(mGroupAdapter.size(), item); + if (first) mBinding.group.setSelectedPosition(position); + if (first) onItemClick(mGroup = item); + iterator.remove(); + first = false; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onActionEvent(ActionEvent event) { + if (ActionEvent.PLAY.equals(event.getAction()) || ActionEvent.PAUSE.equals(event.getAction())) { + checkPlay(); + } else if (ActionEvent.NEXT.equals(event.getAction())) { + nextChannel(); + } else if (ActionEvent.PREV.equals(event.getAction())) { + prevChannel(); + } else if (ActionEvent.STOP.equals(event.getAction())) { + finish(); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + switch (event.getType()) { + case LIVE: + setLive(getHome()); + break; + case PLAYER: + fetch(); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerEvent(PlayerEvent event) { + if (!event.getTag().equals(tag)) return; + switch (event.getState()) { + case PlayerEvent.PREPARE: + setDecode(); + break; + case Player.STATE_BUFFERING: + showProgress(); + break; + case Player.STATE_READY: + hideProgress(); + checkPlayImg(); + mPlayers.reset(); + break; + case Player.STATE_ENDED: + checkNext(); + break; + case PlayerEvent.TRACK: + setMetadata(); + setTrackVisible(); + break; + case PlayerEvent.SIZE: + mBinding.widget.size.setText(mPlayers.getSizeText()); + break; + } + } + + private void setTrackVisible() { + mBinding.control.text.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_TEXT) || mPlayers.isVod() ? View.VISIBLE : View.GONE); + mBinding.control.audio.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_AUDIO) ? View.VISIBLE : View.GONE); + mBinding.control.video.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_VIDEO) ? View.VISIBLE : View.GONE); + mBinding.control.speed.setVisibility(mPlayers.isVod() ? View.VISIBLE : View.GONE); + } + + private void setMetadata() { + String title = mBinding.widget.name.getText().toString(); + String artist = mBinding.widget.play.getText().toString(); + mPlayers.setMetadata(title, artist, mChannel.getLogo(), mBinding.exo.getDefaultArtwork()); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onErrorEvent(ErrorEvent event) { + if (!event.getTag().equals(tag)) return; + if (mPlayers.retried()) onError(event); + else fetch(); + } + + private void onError(ErrorEvent event) { + Track.delete(mPlayers.getUrl()); + showError(event.getMsg()); + mPlayers.resetTrack(); + mPlayers.reset(); + mPlayers.stop(); + startFlow(); + } + + private void startFlow() { + if (!Setting.isChange()) return; + if (!mChannel.isLast()) nextLine(true); + } + + private void prevChannel() { + if (mGroup == null) return; + int position = mGroup.getPosition() - 1; + boolean limit = position < 0; + if (Setting.isAcross() & limit) prevGroup(true); + else mGroup.setPosition(limit ? mChannelAdapter.size() - 1 : position); + if (!mGroup.isEmpty()) setChannel(mGroup.current()); + } + + private void nextChannel() { + if (mGroup == null) return; + int position = mGroup.getPosition() + 1; + boolean limit = position > mChannelAdapter.size() - 1; + if (Setting.isAcross() && limit) nextGroup(true); + else mGroup.setPosition(limit ? 0 : position); + if (!mGroup.isEmpty()) setChannel(mGroup.current()); + } + + private void checkNext() { + int current = mChannel.getData().getInRange(); + int position = mChannel.getData().getSelected() + 1; + boolean hasNext = position <= current && position > 0; + if (hasNext) onItemClick(mChannel.getData().getList().get(position)); + else fetch(); + } + + private void prevLine() { + if (mChannel == null || mChannel.isOnly()) return; + mChannel.prevLine(); + showInfo(); + fetch(); + } + + private void nextLine(boolean show) { + if (mChannel == null || mChannel.isOnly()) return; + mChannel.nextLine(); + if (show) showInfo(); + else setInfo(); + fetch(); + } + + private void seek(long time) { + mKeyDown.resetTime(); + mPlayers.seek(time); + showProgress(); + hideCenter(); + } + + private void onPaused() { + mPlayers.pause(); + checkPlayImg(); + } + + private void onPlay() { + mPlayers.play(); + checkPlayImg(); + } + + public boolean isRedirect() { + return redirect; + } + + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (isVisible(mBinding.control.getRoot())) setR1Callback(); + if (mKeyDown.hasEvent(event)) mKeyDown.onKeyDown(event); + return super.dispatchKeyEvent(event); + } + + @Override + public void setUITimer() { + App.post(mR4, Constant.INTERVAL_HIDE); + } + + @Override + public boolean nextGroup(boolean skip) { + int position = mBinding.group.getSelectedPosition() + 1; + if (position > mGroupAdapter.size() - 1) position = 0; + if (mGroup.equals(mGroupAdapter.get(position))) return false; + mGroup = (Group) mGroupAdapter.get(position); + mBinding.group.setSelectedPosition(position); + if (skip && mGroup.skip()) return nextGroup(true); + mChannelAdapter.setItems(mGroup.getChannel(), null); + mGroup.setPosition(0); + return true; + } + + @Override + public boolean prevGroup(boolean skip) { + int position = mBinding.group.getSelectedPosition() - 1; + if (position < 0) position = mGroupAdapter.size() - 1; + if (mGroup.equals(mGroupAdapter.get(position))) return false; + mGroup = (Group) mGroupAdapter.get(position); + mBinding.group.setSelectedPosition(position); + if (skip && mGroup.skip()) return prevGroup(true); + mChannelAdapter.setItems(mGroup.getChannel(), null); + mGroup.setPosition(mGroup.getChannel().size() - 1); + return true; + } + + @Override + public boolean dispatch(boolean check) { + return !check || isGone(mBinding.recycler) && isGone(mBinding.control.getRoot()); + } + + @Override + public void onShow(String number) { + mBinding.widget.digital.setText(number); + mBinding.widget.digital.setVisibility(View.VISIBLE); + } + + @Override + public void onFind(String number) { + mBinding.widget.digital.setVisibility(View.GONE); + setPosition(LiveConfig.get().find(number, mGroupAdapter.unmodifiableList())); + } + + @Override + public void onSeeking(long time) { + if (mPlayers.isLive()) return; + mBinding.widget.center.setVisibility(View.VISIBLE); + mBinding.widget.exoDuration.setText(mPlayers.getDurationTime()); + mBinding.widget.exoPosition.setText(mPlayers.getPositionTime(time)); + mBinding.widget.action.setImageResource(time > 0 ? R.drawable.ic_widget_forward : R.drawable.ic_widget_rewind); + hideProgress(); + } + + @Override + public void onKeyUp() { + if (Setting.isInvert()) nextChannel(); + else prevChannel(); + } + + @Override + public void onKeyDown() { + if (Setting.isInvert()) prevChannel(); + else nextChannel(); + } + + @Override + public void onKeyLeft(long time) { + if (mPlayers.isLive()) prevLine(); + else App.post(() -> seek(time), 250); + } + + @Override + public void onKeyRight(long time) { + if (mPlayers.isLive()) nextLine(true); + else App.post(() -> seek(time), 250); + } + + @Override + public void onKeyCenter() { + hideInfo(); + showUI(); + } + + @Override + public void onMenu() { + showControl(mBinding.control.home); + } + + @Override + public void onSingleTap() { + onToggle(); + } + + @Override + public void onDoubleTap() { + if (isVisible(mBinding.recycler)) hideUI(); + else if (isVisible(mBinding.control.getRoot())) hideControl(); + else onMenu(); + } + + @Override + protected void onStart() { + super.onStart(); + mClock.stop().start(); + onPlay(); + } + + @Override + protected void onResume() { + super.onResume(); + if (isRedirect()) onPlay(); + setRedirect(false); + } + + @Override + protected void onPause() { + super.onPause(); + if (isRedirect()) onPaused(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Setting.isBackgroundOff()) onPaused(); + if (Setting.isBackgroundOff()) mClock.stop(); + } + + @Override + public void onBackPressed() { + if (isVisible(mBinding.control.getRoot())) { + hideControl(); + } else if (isVisible(mBinding.widget.bottom)) { + hideInfo(); + } else if (isVisible(mBinding.recycler)) { + hideUI(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mPlayers.release(); + Source.get().exit(); + PlaybackService.stop(); + mViewModel.url.removeObserver(mObserveUrl); + mViewModel.epg.removeObserver(mObserveEpg); + App.removeCallbacks(mR0, mR1, mR3, mR3, mR4); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/PushActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/PushActivity.java new file mode 100644 index 00000000..d059e2fd --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/PushActivity.java @@ -0,0 +1,65 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; +import android.view.View; + +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.ActivityPushBinding; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.utils.QRCode; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Sniffer; +import com.fongmi.android.tv.utils.Util; + +public class PushActivity extends BaseActivity { + + private ActivityPushBinding mBinding; + + public static void start(Activity activity) { + start(activity, 2); + } + + public static void start(Activity activity, int tab) { + Intent intent = new Intent(activity, PushActivity.class); + intent.putExtra("tab", tab); + activity.startActivity(intent); + } + + private int getTab() { + return getIntent().getIntExtra("tab", 2); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityPushBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + mBinding.code.setImageBitmap(QRCode.getBitmap(Server.get().getAddress(getTab()), 250, 1)); + mBinding.info.setText(ResUtil.getString(R.string.push_info, Server.get().getAddress())); + } + + @Override + protected void initEvent() { + mBinding.code.setOnClickListener(this::onCode); + mBinding.clip.setOnClickListener(this::onClip); + } + + private void onClip(View view) { + CharSequence text = Util.getClipText(); + if (!TextUtils.isEmpty(text)) VideoActivity.start(this, Sniffer.getUrl(text.toString())); + } + + private void onCode(View view) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(Server.get().getAddress(getTab()))); + startActivity(intent); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SearchActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SearchActivity.java new file mode 100644 index 00000000..2cd5f3b2 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SearchActivity.java @@ -0,0 +1,178 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.text.Editable; +import android.text.TextUtils; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.EditorInfo; + +import androidx.annotation.NonNull; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.bean.Hot; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Suggest; +import com.fongmi.android.tv.databinding.ActivitySearchBinding; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.ui.adapter.RecordAdapter; +import com.fongmi.android.tv.ui.adapter.WordAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.CustomKeyboard; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.Util; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.ZhuToPin; +import com.google.common.net.HttpHeaders; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.List; + +import okhttp3.Call; +import okhttp3.Headers; +import okhttp3.Response; + +public class SearchActivity extends BaseActivity implements WordAdapter.OnClickListener, RecordAdapter.OnClickListener, CustomKeyboard.Callback, SiteCallback { + + private ActivitySearchBinding mBinding; + private RecordAdapter mRecordAdapter; + private WordAdapter mWordAdapter; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, SearchActivity.class)); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivitySearchBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + CustomKeyboard.init(this, mBinding); + setRecyclerView(); + getHot(); + } + + @Override + protected void initEvent() { + mBinding.keyword.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) onSearch(); + return true; + }); + mBinding.keyword.addTextChangedListener(new CustomTextListener() { + @Override + public void afterTextChanged(Editable s) { + if (s.toString().isEmpty()) getHot(); + else getSuggest(s.toString()); + } + }); + mBinding.mic.setListener(this, new CustomTextListener() { + @Override + public void onEndOfSpeech() { + mBinding.keyword.requestFocus(); + mBinding.mic.stop(); + } + + @Override + public void onResults(String result) { + mBinding.keyword.setText(result); + mBinding.keyword.setSelection(mBinding.keyword.length()); + } + }); + } + + private void setRecyclerView() { + mBinding.wordRecycler.setHasFixedSize(true); + mBinding.wordRecycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + mBinding.wordRecycler.setAdapter(mWordAdapter = new WordAdapter(this)); + mBinding.recordRecycler.setHasFixedSize(true); + mBinding.recordRecycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + mBinding.recordRecycler.setAdapter(mRecordAdapter = new RecordAdapter(this)); + } + + private void getHot() { + mBinding.hint.setText(R.string.search_hot); + mWordAdapter.addAll(Hot.get(Setting.getHot())); + OkHttp.newCall("https://api.web.360kan.com/v1/rank?cat=1", Headers.of(HttpHeaders.REFERER, "https://www.360kan.com/rank/general")).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + List items = Hot.get(response.body().string()); + if (mWordAdapter.getItemCount() > 0) return; + App.post(() -> mWordAdapter.addAll(items)); + } + }); + } + + private void getSuggest(String text) { + mBinding.hint.setText(R.string.search_suggest); + OkHttp.newCall("https://suggest.video.iqiyi.com/?if=mobile&key=" + URLEncoder.encode(ZhuToPin.get(text))).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (mBinding.keyword.getText().toString().trim().isEmpty()) return; + List items = Suggest.get(response.body().string()); + App.post(() -> mWordAdapter.addAll(items)); + } + }); + } + + @Override + public void onItemClick(String text) { + mBinding.keyword.setText(text); + onSearch(); + } + + @Override + public void onDataChanged(int size) { + mBinding.recordLayout.setVisibility(size == 0 ? View.GONE : View.VISIBLE); + } + + @Override + public void onSearch() { + String keyword = mBinding.keyword.getText().toString().trim(); + mBinding.keyword.setSelection(mBinding.keyword.length()); + Util.hideKeyboard(mBinding.keyword); + if (TextUtils.isEmpty(keyword)) return; + CollectActivity.start(this, keyword); + App.post(() -> mRecordAdapter.add(keyword), 250); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (KeyUtil.isMenuKey(event)) showDialog(); + return super.dispatchKeyEvent(event); + } + + @Override + public void showDialog() { + SiteDialog.create(this).search().show(); + } + + @Override + public void onRemote() { + PushActivity.start(this, 1); + } + + @Override + public void setSite(Site item) { + } + + @Override + public void onChanged() { + } + + @Override + protected void onResume() { + super.onResume(); + mBinding.keyword.requestFocus(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java new file mode 100644 index 00000000..25425953 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingActivity.java @@ -0,0 +1,401 @@ +package com.fongmi.android.tv.ui.activity; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.view.View; + +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.BuildConfig; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.Updater; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.ActivitySettingBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.ConfigCallback; +import com.fongmi.android.tv.impl.DohCallback; +import com.fongmi.android.tv.impl.LiveCallback; +import com.fongmi.android.tv.impl.ProxyCallback; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.dialog.ConfigDialog; +import com.fongmi.android.tv.ui.dialog.DohDialog; +import com.fongmi.android.tv.ui.dialog.HistoryDialog; +import com.fongmi.android.tv.ui.dialog.LiveDialog; +import com.fongmi.android.tv.ui.dialog.ProxyDialog; +import com.fongmi.android.tv.ui.dialog.RestoreDialog; +import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.bean.Doh; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Path; +import com.permissionx.guolindev.PermissionX; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.Nullable; + +public class SettingActivity extends BaseActivity implements ConfigCallback, SiteCallback, LiveCallback, DohCallback, ProxyCallback { + + private ActivitySettingBinding mBinding; + private String[] quality; + private String[] size; + private int type; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, SettingActivity.class)); + } + + private String getSwitch(boolean value) { + return getString(value ? R.string.setting_on : R.string.setting_off); + } + + private String getProxy(String proxy) { + return proxy.isEmpty() ? getString(R.string.none) : UrlUtil.scheme(proxy); + } + + private int getDohIndex() { + return Math.max(0, VodConfig.get().getDoh().indexOf(Doh.objectFrom(Setting.getDoh()))); + } + + private String[] getDohList() { + List list = new ArrayList<>(); + for (Doh item : VodConfig.get().getDoh()) list.add(item.getName()); + return list.toArray(new String[0]); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivitySettingBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + mBinding.vod.requestFocus(); + mBinding.vodUrl.setText(VodConfig.getDesc()); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + mBinding.wallUrl.setText(WallConfig.getDesc()); + mBinding.versionText.setText(BuildConfig.VERSION_NAME); + setCacheText(); + setOtherText(); + } + + private void setOtherText() { + mBinding.dohText.setText(getDohList()[getDohIndex()]); + mBinding.proxyText.setText(getProxy(Setting.getProxy())); + mBinding.incognitoText.setText(getSwitch(Setting.isIncognito())); + mBinding.sizeText.setText((size = ResUtil.getStringArray(R.array.select_size))[Setting.getSize()]); + mBinding.qualityText.setText((quality = ResUtil.getStringArray(R.array.select_quality))[Setting.getQuality()]); + } + + private void setCacheText() { + FileUtil.getCacheSize(new Callback() { + @Override + public void success(String result) { + mBinding.cacheText.setText(result); + } + }); + } + + @Override + protected void initEvent() { + mBinding.vod.setOnClickListener(this::onVod); + mBinding.live.setOnClickListener(this::onLive); + mBinding.wall.setOnClickListener(this::onWall); + mBinding.proxy.setOnClickListener(this::onProxy); + mBinding.cache.setOnClickListener(this::onCache); + mBinding.backup.setOnClickListener(this::onBackup); + mBinding.player.setOnClickListener(this::onPlayer); + mBinding.restore.setOnClickListener(this::onRestore); + mBinding.version.setOnClickListener(this::onVersion); + mBinding.vod.setOnLongClickListener(this::onVodEdit); + mBinding.vodHome.setOnClickListener(this::onVodHome); + mBinding.live.setOnLongClickListener(this::onLiveEdit); + mBinding.liveHome.setOnClickListener(this::onLiveHome); + mBinding.wall.setOnLongClickListener(this::onWallEdit); + mBinding.vodHistory.setOnClickListener(this::onVodHistory); + mBinding.version.setOnLongClickListener(this::onVersionDev); + mBinding.liveHistory.setOnClickListener(this::onLiveHistory); + mBinding.wallDefault.setOnClickListener(this::setWallDefault); + mBinding.wallRefresh.setOnClickListener(this::setWallRefresh); + mBinding.incognito.setOnClickListener(this::setIncognito); + mBinding.quality.setOnClickListener(this::setQuality); + mBinding.size.setOnClickListener(this::setSize); + mBinding.doh.setOnClickListener(this::setDoh); + } + + @Override + public void setConfig(Config config) { + if (config.getUrl().startsWith("file") && !PermissionX.isGranted(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> load(config)); + } else { + load(config); + } + } + + private void load(Config config) { + switch (config.getType()) { + case 0: + Notify.progress(this); + VodConfig.load(config, getCallback(0)); + mBinding.vodUrl.setText(config.getDesc()); + break; + case 1: + Notify.progress(this); + LiveConfig.load(config, getCallback(1)); + mBinding.liveUrl.setText(config.getDesc()); + break; + case 2: + Notify.progress(this); + WallConfig.load(config, getCallback(2)); + mBinding.wallUrl.setText(config.getDesc()); + break; + } + } + + private Callback getCallback(int type) { + return new Callback() { + @Override + public void success(String result) { + Notify.show(result); + } + + @Override + public void success() { + setConfig(type); + } + + @Override + public void error(String msg) { + Notify.show(msg); + setConfig(type); + } + }; + } + + private void setConfig(int type) { + switch (type) { + case 0: + setCacheText(); + Notify.dismiss(); + RefreshEvent.video(); + RefreshEvent.config(); + RefreshEvent.history(); + mBinding.vodUrl.setText(VodConfig.getDesc()); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + mBinding.wallUrl.setText(WallConfig.getDesc()); + break; + case 1: + setCacheText(); + Notify.dismiss(); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + break; + case 2: + setCacheText(); + Notify.dismiss(); + mBinding.wallUrl.setText(WallConfig.getDesc()); + break; + } + } + + @Override + public void setSite(Site item) { + VodConfig.get().setHome(item); + RefreshEvent.video(); + } + + @Override + public void onChanged() { + } + + @Override + public void setLive(Live item) { + LiveConfig.get().setHome(item); + } + + private void onVod(View view) { + ConfigDialog.create(this).type(type = 0).show(); + } + + private void onLive(View view) { + ConfigDialog.create(this).type(type = 1).show(); + } + + private void onWall(View view) { + ConfigDialog.create(this).type(type = 2).show(); + } + + private boolean onVodEdit(View view) { + ConfigDialog.create(this).type(type = 0).edit().show(); + return true; + } + + private boolean onLiveEdit(View view) { + ConfigDialog.create(this).type(type = 1).edit().show(); + return true; + } + + private boolean onWallEdit(View view) { + ConfigDialog.create(this).type(type = 2).edit().show(); + return true; + } + + private void onVodHome(View view) { + SiteDialog.create(this).action().show(); + } + + private void onLiveHome(View view) { + LiveDialog.create(this).action().show(); + } + + private void onVodHistory(View view) { + HistoryDialog.create(this).type(type = 0).show(); + } + + private void onLiveHistory(View view) { + HistoryDialog.create(this).type(type = 1).show(); + } + + private void onPlayer(View view) { + SettingPlayerActivity.start(this); + } + + private void onVersion(View view) { + Updater.create().force().release().start(this); + } + + private boolean onVersionDev(View view) { + Updater.create().force().dev().start(this); + return true; + } + + private void setWallDefault(View view) { + WallConfig.refresh(Setting.getWall() == 4 ? 1 : Setting.getWall() + 1); + } + + private void setWallRefresh(View view) { + Notify.progress(this); + WallConfig.get().load(new Callback() { + @Override + public void success() { + Notify.dismiss(); + setCacheText(); + } + }); + } + + private void setIncognito(View view) { + Setting.putIncognito(!Setting.isIncognito()); + mBinding.incognitoText.setText(getSwitch(Setting.isIncognito())); + } + + private void setQuality(View view) { + int index = Setting.getQuality(); + Setting.putQuality(index = index == quality.length - 1 ? 0 : ++index); + mBinding.qualityText.setText(quality[index]); + RefreshEvent.image(); + } + + private void setSize(View view) { + int index = Setting.getSize(); + Setting.putSize(index = index == size.length - 1 ? 0 : ++index); + mBinding.sizeText.setText(size[index]); + RefreshEvent.size(); + } + + private void setDoh(View view) { + DohDialog.create(this).index(getDohIndex()).show(); + } + + @Override + public void setDoh(Doh doh) { + Source.get().stop(); + OkHttp.get().setDoh(doh); + Notify.progress(getActivity()); + Setting.putDoh(doh.toString()); + mBinding.dohText.setText(doh.getName()); + VodConfig.load(Config.vod(), getCallback(0)); + } + + private void onProxy(View view) { + ProxyDialog.create(this).show(); + } + + @Override + public void setProxy(String proxy) { + Source.get().stop(); + Setting.putProxy(proxy); + OkHttp.get().setProxy(proxy); + Notify.progress(getActivity()); + mBinding.proxyText.setText(getProxy(proxy)); + VodConfig.load(Config.vod(), getCallback(0)); + } + + private void onCache(View view) { + FileUtil.clearCache(new Callback() { + @Override + public void success() { + setCacheText(); + } + }); + } + + private void onBackup(View view) { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> AppDatabase.backup(new Callback() { + @Override + public void success() { + Notify.show(R.string.backup_success); + } + + @Override + public void error() { + Notify.show(R.string.backup_fail); + } + })); + } + + private void onRestore(View view) { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> RestoreDialog.create(this).show(new Callback() { + @Override + public void success() { + Notify.show(R.string.restore_success); + Notify.progress(getActivity()); + setOtherText(); + initConfig(); + } + + @Override + public void error() { + Notify.show(R.string.restore_fail); + } + })); + } + + private void initConfig() { + WallConfig.get().init(); + LiveConfig.get().init().load(); + VodConfig.get().init().load(getCallback(0)); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != Activity.RESULT_OK || requestCode != FileChooser.REQUEST_PICK_FILE) return; + setConfig(Config.find("file:/" + FileChooser.getPathFromUri(this, data.getData()).replace(Path.rootPath(), ""), type)); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingPlayerActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingPlayerActivity.java new file mode 100644 index 00000000..8e8d7648 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/SettingPlayerActivity.java @@ -0,0 +1,162 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.provider.Settings; +import android.view.View; + +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.ActivitySettingPlayerBinding; +import com.fongmi.android.tv.impl.BufferCallback; +import com.fongmi.android.tv.impl.SpeedCallback; +import com.fongmi.android.tv.impl.UaCallback; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.dialog.BufferDialog; +import com.fongmi.android.tv.ui.dialog.SpeedDialog; +import com.fongmi.android.tv.ui.dialog.UaDialog; +import com.fongmi.android.tv.utils.ResUtil; + +import java.text.DecimalFormat; + +public class SettingPlayerActivity extends BaseActivity implements UaCallback, BufferCallback, SpeedCallback { + + private ActivitySettingPlayerBinding mBinding; + private DecimalFormat format; + private String[] caption; + private String[] render; + private String[] scale; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, SettingPlayerActivity.class)); + } + + private String getSwitch(boolean value) { + return getString(value ? R.string.setting_on : R.string.setting_off); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivitySettingPlayerBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + setVisible(); + format = new DecimalFormat("0.#"); + mBinding.render.requestFocus(); + mBinding.uaText.setText(Setting.getUa()); + mBinding.aacText.setText(getSwitch(Setting.isPreferAAC())); + mBinding.tunnelText.setText(getSwitch(Setting.isTunnel())); + mBinding.speedText.setText(format.format(Setting.getSpeed())); + mBinding.bufferText.setText(String.valueOf(Setting.getBuffer())); + mBinding.backgroundText.setText(getSwitch(Setting.isBackgroundOn())); + mBinding.audioDecodeText.setText(getSwitch(Setting.isAudioPrefer())); + mBinding.danmakuLoadText.setText(getSwitch(Setting.isDanmakuLoad())); + mBinding.scaleText.setText((scale = ResUtil.getStringArray(R.array.select_scale))[Setting.getScale()]); + mBinding.renderText.setText((render = ResUtil.getStringArray(R.array.select_render))[Setting.getRender()]); + mBinding.captionText.setText((caption = ResUtil.getStringArray(R.array.select_caption))[Setting.isCaption() ? 1 : 0]); + } + + @Override + protected void initEvent() { + mBinding.ua.setOnClickListener(this::onUa); + mBinding.aac.setOnClickListener(this::setAAC); + mBinding.scale.setOnClickListener(this::setScale); + mBinding.speed.setOnClickListener(this::onSpeed); + mBinding.buffer.setOnClickListener(this::onBuffer); + mBinding.render.setOnClickListener(this::setRender); + mBinding.tunnel.setOnClickListener(this::setTunnel); + mBinding.caption.setOnClickListener(this::setCaption); + mBinding.caption.setOnLongClickListener(this::onCaption); + mBinding.background.setOnClickListener(this::onBackground); + mBinding.audioDecode.setOnClickListener(this::setAudioDecode); + mBinding.danmakuLoad.setOnClickListener(this::setDanmakuLoad); + } + + private void setVisible() { + if (Setting.getBackground() == 2) Setting.putBackground(1); + mBinding.caption.setVisibility(Setting.hasCaption() ? View.VISIBLE : View.GONE); + } + + private void onUa(View view) { + UaDialog.create(this).show(); + } + + @Override + public void setUa(String ua) { + mBinding.uaText.setText(ua); + Setting.putUa(ua); + } + + private void setAAC(View view) { + Setting.putPreferAAC(!Setting.isPreferAAC()); + mBinding.aacText.setText(getSwitch(Setting.isPreferAAC())); + } + + private void setScale(View view) { + int index = Setting.getScale(); + Setting.putScale(index = index == scale.length - 1 ? 0 : ++index); + mBinding.scaleText.setText(scale[index]); + } + + private void onSpeed(View view) { + SpeedDialog.create(this).show(); + } + + @Override + public void setSpeed(float speed) { + mBinding.speedText.setText(format.format(speed)); + Setting.putSpeed(speed); + } + + private void onBuffer(View view) { + BufferDialog.create(this).show(); + } + + @Override + public void setBuffer(int times) { + mBinding.bufferText.setText(String.valueOf(times)); + Setting.putBuffer(times); + } + + private void setRender(View view) { + int index = Setting.getRender(); + Setting.putRender(index = index == render.length - 1 ? 0 : ++index); + mBinding.renderText.setText(render[index]); + if (Setting.isTunnel() && Setting.getRender() == 1) setTunnel(view); + } + + private void setTunnel(View view) { + Setting.putTunnel(!Setting.isTunnel()); + mBinding.tunnelText.setText(getSwitch(Setting.isTunnel())); + if (Setting.isTunnel() && Setting.getRender() == 1) setRender(view); + } + + private void setCaption(View view) { + Setting.putCaption(!Setting.isCaption()); + mBinding.captionText.setText(caption[Setting.isCaption() ? 1 : 0]); + } + + private boolean onCaption(View view) { + if (Setting.isCaption()) startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); + return Setting.isCaption(); + } + + private void setAudioDecode(View view) { + Setting.putAudioPrefer(!Setting.isAudioPrefer()); + mBinding.audioDecodeText.setText(getSwitch(Setting.isAudioPrefer())); + } + + private void setDanmakuLoad(View view) { + Setting.putDanmakuLoad(!Setting.isDanmakuLoad()); + mBinding.danmakuLoadText.setText(getSwitch(Setting.isDanmakuLoad())); + } + + private void onBackground(View view) { + Setting.putBackground(Setting.isBackgroundOn() ? 0 : 1); + mBinding.backgroundText.setText(getSwitch(Setting.isBackgroundOn())); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VideoActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VideoActivity.java new file mode 100644 index 00000000..1ba236cd --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VideoActivity.java @@ -0,0 +1,1435 @@ +package com.fongmi.android.tv.ui.activity; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.text.Html; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentActivity; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.OnChildViewHolderSelectedListener; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.C; +import androidx.media3.common.Player; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Danmaku; +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.bean.Flag; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.bean.Parse; +import com.fongmi.android.tv.bean.Part; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Sub; +import com.fongmi.android.tv.bean.Track; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.ActivityVideoBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.ActionEvent; +import com.fongmi.android.tv.event.ErrorEvent; +import com.fongmi.android.tv.event.PlayerEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.player.Players; +import com.fongmi.android.tv.player.exo.ExoUtil; +import com.fongmi.android.tv.service.PlaybackService; +import com.fongmi.android.tv.ui.adapter.QualityAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.CustomKeyDownVod; +import com.fongmi.android.tv.ui.custom.CustomMovement; +import com.fongmi.android.tv.ui.dialog.DanmakuDialog; +import com.fongmi.android.tv.ui.dialog.DescDialog; +import com.fongmi.android.tv.ui.dialog.SubtitleDialog; +import com.fongmi.android.tv.ui.dialog.TrackDialog; +import com.fongmi.android.tv.ui.presenter.ArrayPresenter; +import com.fongmi.android.tv.ui.presenter.EpisodePresenter; +import com.fongmi.android.tv.ui.presenter.FlagPresenter; +import com.fongmi.android.tv.ui.presenter.ParsePresenter; +import com.fongmi.android.tv.ui.presenter.PartPresenter; +import com.fongmi.android.tv.ui.presenter.QuickPresenter; +import com.fongmi.android.tv.utils.Clock; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Sniffer; +import com.fongmi.android.tv.utils.Traffic; +import com.github.bassaer.library.MDColor; +import com.github.catvod.utils.Trans; +import com.permissionx.guolindev.PermissionX; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; + +public class VideoActivity extends BaseActivity implements CustomKeyDownVod.Listener, TrackDialog.Listener, ArrayPresenter.OnClickListener, Clock.Callback { + + private ActivityVideoBinding mBinding; + private ViewGroup.LayoutParams mFrameParams; + private EpisodePresenter mEpisodePresenter; + private ArrayObjectAdapter mEpisodeAdapter; + private ArrayObjectAdapter mArrayAdapter; + private ArrayObjectAdapter mParseAdapter; + private ArrayObjectAdapter mQuickAdapter; + private ArrayObjectAdapter mFlagAdapter; + private ArrayObjectAdapter mPartAdapter; + private Observer mObserveDetail; + private Observer mObservePlayer; + private Observer mObserveSearch; + private QualityAdapter mQualityAdapter; + private FlagPresenter mFlagPresenter; + private PartPresenter mPartPresenter; + private CustomKeyDownVod mKeyDown; + private ExecutorService mExecutor; + private SiteViewModel mViewModel; + private List mBroken; + private History mHistory; + private Players mPlayers; + private boolean fullscreen; + private boolean initAuto; + private boolean autoMode; + private boolean useParse; + private boolean redirect; + private Runnable mR1; + private Runnable mR2; + private Runnable mR3; + private Runnable mR4; + private Clock mClock; + private View mFocus1; + private View mFocus2; + private String tag; + + public static void push(FragmentActivity activity, String text) { + if (FileChooser.isValid(activity, Uri.parse(text))) file(activity, FileChooser.getPathFromUri(activity, Uri.parse(text))); + else start(activity, Sniffer.getUrl(text)); + } + + public static void file(FragmentActivity activity, String path) { + if (TextUtils.isEmpty(path)) return; + String name = new File(path).getName(); + PermissionX.init(activity).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> start(activity, "push_agent", "file://" + path, name)); + } + + public static void cast(Activity activity, History history) { + start(activity, history.getSiteKey(), history.getVodId(), history.getVodName(), history.getVodPic(), null, false, true); + } + + public static void collect(Activity activity, String key, String id, String name, String pic) { + start(activity, key, id, name, pic, null, true, false); + } + + public static void start(Activity activity, String url) { + start(activity, "push_agent", url, url); + } + + public static void start(Activity activity, String key, String id, String name) { + start(activity, key, id, name, null); + } + + public static void start(Activity activity, String key, String id, String name, String pic) { + start(activity, key, id, name, pic, null); + } + + public static void start(Activity activity, String key, String id, String name, String pic, String mark) { + start(activity, key, id, name, pic, mark, false, false); + } + + public static void start(Activity activity, String key, String id, String name, String pic, String mark, boolean collect, boolean cast) { + Intent intent = new Intent(activity, VideoActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("collect", collect); + intent.putExtra("cast", cast); + intent.putExtra("mark", mark); + intent.putExtra("name", name); + intent.putExtra("pic", pic); + intent.putExtra("key", key); + intent.putExtra("id", id); + activity.startActivity(intent); + } + + private boolean isCast() { + return getIntent().getBooleanExtra("cast", false); + } + + private String getName() { + return Objects.toString(getIntent().getStringExtra("name"), ""); + } + + private String getPic() { + return Objects.toString(getIntent().getStringExtra("pic"), ""); + } + + private String getMark() { + return Objects.toString(getIntent().getStringExtra("mark"), ""); + } + + private String getKey() { + return Objects.toString(getIntent().getStringExtra("key"), ""); + } + + private String getId() { + return Objects.toString(getIntent().getStringExtra("id"), ""); + } + + private String getHistoryKey() { + return getKey().concat(AppDatabase.SYMBOL).concat(getId()).concat(AppDatabase.SYMBOL) + VodConfig.getCid(); + } + + private Site getSite() { + return VodConfig.get().getSite(getKey()); + } + + private Flag getFlag() { + return (Flag) mFlagAdapter.get(getFlagPosition()); + } + + private Episode getEpisode() { + return (Episode) mEpisodeAdapter.get(getEpisodePosition()); + } + + private int getFlagPosition() { + for (int i = 0; i < mFlagAdapter.size(); i++) if (((Flag) mFlagAdapter.get(i)).isActivated()) return i; + return 0; + } + + private int getEpisodePosition() { + for (int i = 0; i < mEpisodeAdapter.size(); i++) if (((Episode) mEpisodeAdapter.get(i)).isActivated()) return i; + return 0; + } + + private int getParsePosition() { + for (int i = 0; i < mParseAdapter.size(); i++) if (((Parse) mParseAdapter.get(i)).isActivated()) return i; + return 0; + } + + private int getScale() { + return mHistory != null && mHistory.getScale() != -1 ? mHistory.getScale() : Setting.getScale(); + } + + private boolean isReplay() { + return Setting.getReset() == 1; + } + + private boolean isFromCollect() { + return getIntent().getBooleanExtra("collect", false); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityVideoBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + String id = Objects.toString(intent.getStringExtra("id"), ""); + if (TextUtils.isEmpty(id) || id.equals(getId())) return; + getIntent().putExtras(intent); + stopSearch(); + checkId(); + } + + @Override + protected void initView() { + mFrameParams = mBinding.video.getLayoutParams(); + mClock = Clock.create(mBinding.widget.clock); + mKeyDown = CustomKeyDownVod.create(this); + mObserveDetail = this::setDetail; + mObservePlayer = this::setPlayer; + mObserveSearch = this::setSearch; + mPlayers = Players.create(this); + mBroken = new ArrayList<>(); + mR1 = this::hideControl; + mR2 = this::updateFocus; + mR3 = this::setTraffic; + mR4 = this::showEmpty; + setRecyclerView(); + setVideoView(); + setViewModel(); + checkCast(); + checkId(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + protected void initEvent() { + mBinding.control.seek.setListener(mPlayers); + mBinding.desc.setOnClickListener(view -> onDesc()); + mBinding.keep.setOnClickListener(view -> onKeep()); + mBinding.video.setOnClickListener(view -> onVideo()); + mBinding.change1.setOnClickListener(view -> onChange()); + mBinding.control.text.setOnClickListener(this::onTrack); + mBinding.control.audio.setOnClickListener(this::onTrack); + mBinding.control.video.setOnClickListener(this::onTrack); + mBinding.control.speed.setUpListener(this::onSpeedAdd); + mBinding.control.speed.setDownListener(this::onSpeedSub); + mBinding.control.ending.setUpListener(this::onEndingAdd); + mBinding.control.ending.setDownListener(this::onEndingSub); + mBinding.control.opening.setUpListener(this::onOpeningAdd); + mBinding.control.opening.setDownListener(this::onOpeningSub); + mBinding.control.text.setUpListener(this::onSubtitleClick); + mBinding.control.text.setDownListener(this::onSubtitleClick); + mBinding.control.loop.setOnClickListener(view -> onLoop()); + mBinding.control.next.setOnClickListener(view -> checkNext()); + mBinding.control.prev.setOnClickListener(view -> checkPrev()); + mBinding.control.scale.setOnClickListener(view -> onScale()); + mBinding.control.speed.setOnClickListener(view -> onSpeed()); + mBinding.control.reset.setOnClickListener(view -> onReset()); + mBinding.control.player.setOnClickListener(view -> onChoose()); + mBinding.control.decode.setOnClickListener(view -> onDecode()); + mBinding.control.ending.setOnClickListener(view -> onEnding()); + mBinding.control.change2.setOnClickListener(view -> onChange()); + mBinding.control.danmaku.setOnClickListener(view -> onDanmaku()); + mBinding.control.opening.setOnClickListener(view -> onOpening()); + mBinding.control.speed.setOnLongClickListener(view -> onSpeedLong()); + mBinding.control.reset.setOnLongClickListener(view -> onResetToggle()); + mBinding.control.ending.setOnLongClickListener(view -> onEndingReset()); + mBinding.control.opening.setOnLongClickListener(view -> onOpeningReset()); + mBinding.video.setOnTouchListener((view, event) -> mKeyDown.onTouchEvent(event)); + mBinding.flag.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + if (mFlagAdapter.size() > 0) setFlagActivated((Flag) mFlagAdapter.get(position)); + } + }); + mBinding.episode.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + if (child != null && mBinding.video != mFocus1) mFocus1 = child.itemView; + } + }); + mBinding.array.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + if (mEpisodeAdapter.size() > 20 && position > 1) mBinding.episode.setSelectedPosition((position - 2) * 20); + } + }); + } + + private void setRecyclerView() { + mBinding.flag.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.flag.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.flag.setAdapter(new ItemBridgeAdapter(mFlagAdapter = new ArrayObjectAdapter(mFlagPresenter = new FlagPresenter(this::setFlagActivated)))); + mBinding.episode.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.episode.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.episode.setAdapter(new ItemBridgeAdapter(mEpisodeAdapter = new ArrayObjectAdapter(mEpisodePresenter = new EpisodePresenter(this::setEpisodeActivated)))); + mBinding.quality.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.quality.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.quality.setAdapter(mQualityAdapter = new QualityAdapter(this::setQualityActivated)); + mBinding.array.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.array.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.array.setAdapter(new ItemBridgeAdapter(mArrayAdapter = new ArrayObjectAdapter(new ArrayPresenter(this)))); + mBinding.part.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.part.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.part.setAdapter(new ItemBridgeAdapter(mPartAdapter = new ArrayObjectAdapter(mPartPresenter = new PartPresenter(item -> initSearch(item, false))))); + mBinding.quick.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.quick.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.quick.setAdapter(new ItemBridgeAdapter(mQuickAdapter = new ArrayObjectAdapter(new QuickPresenter(this::setSearch)))); + mBinding.control.parse.setHorizontalSpacing(ResUtil.dp2px(8)); + mBinding.control.parse.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.control.parse.setAdapter(new ItemBridgeAdapter(mParseAdapter = new ArrayObjectAdapter(new ParsePresenter(this::setParseActivated)))); + mParseAdapter.setItems(VodConfig.get().getParses(), null); + } + + private void setVideoView() { + mPlayers.init(mBinding.exo); + PlaybackService.start(mPlayers); + ExoUtil.setSubtitleView(mBinding.exo); + mPlayers.setDanmakuView(mBinding.danmaku); + mPlayers.setTag(tag = UUID.randomUUID().toString()); + mBinding.control.decode.setText(mPlayers.getDecodeText()); + mBinding.control.danmaku.setVisibility(Setting.isDanmakuLoad() ? View.VISIBLE : View.GONE); + mBinding.control.reset.setText(ResUtil.getStringArray(R.array.select_reset)[Setting.getReset()]); + } + + private void setDecode() { + mBinding.control.decode.setText(mPlayers.getDecodeText()); + } + + private void setScale(int scale) { + mBinding.exo.setResizeMode(scale); + mBinding.control.scale.setText(ResUtil.getStringArray(R.array.select_scale)[scale]); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.result.observeForever(mObserveDetail); + mViewModel.player.observeForever(mObservePlayer); + mViewModel.search.observeForever(mObserveSearch); + } + + private void checkCast() { + if (isCast()) onVideo(); + else mBinding.progressLayout.showProgress(); + } + + private void checkId() { + if (getId().startsWith("push://")) getIntent().putExtra("key", "push_agent").putExtra("id", getId().substring(7)); + if (getId().isEmpty() || getId().startsWith("msearch:")) setEmpty(false); + else getDetail(); + } + + private void getDetail() { + mViewModel.detailContent(getKey(), getId()); + } + + private void getDetail(Vod item) { + getIntent().putExtra("key", item.getSiteKey()); + getIntent().putExtra("pic", item.getVodPic()); + getIntent().putExtra("id", item.getVodId()); + mBinding.scroll.scrollTo(0, 0); + mClock.setCallback(null); + mPlayers.reset(); + mPlayers.stop(); + getDetail(); + } + + private void setDetail(Result result) { + if (result.getList().isEmpty()) setEmpty(result.hasMsg()); + else setDetail(result.getList().get(0)); + Notify.show(result.getMsg()); + } + + private void setEmpty(boolean finish) { + if (isFromCollect() || finish) { + finish(); + } else if (getName().isEmpty()) { + showEmpty(); + } else { + mBinding.name.setText(getName()); + App.post(mR4, 10000); + checkSearch(false); + } + } + + private void showEmpty() { + mBinding.progressLayout.showEmpty(); + stopSearch(); + } + + private void setDetail(Vod item) { + mBinding.progressLayout.showContent(); + mBinding.video.setTag(item.getVodPic(getPic())); + mBinding.name.setText(item.getVodName(getName())); + setText(mBinding.remark, 0, item.getVodRemarks()); + setText(mBinding.year, R.string.detail_year, item.getVodYear()); + setText(mBinding.area, R.string.detail_area, item.getVodArea()); + setText(mBinding.type, R.string.detail_type, item.getTypeName()); + setText(mBinding.site, R.string.detail_site, getSite().getName()); + setText(mBinding.actor, R.string.detail_actor, Html.fromHtml(item.getVodActor()).toString()); + setText(mBinding.content, R.string.detail_content, Html.fromHtml(item.getVodContent()).toString()); + setText(mBinding.director, R.string.detail_director, Html.fromHtml(item.getVodDirector()).toString()); + mFlagAdapter.setItems(item.getVodFlags(), null); + setPartAdapter(Part.get(item.getVodName())); + mBinding.content.setMaxLines(getMaxLines()); + mBinding.video.requestFocus(); + setArtwork(item.getVodPic()); + App.removeCallbacks(mR4); + checkHistory(item); + checkFlag(item); + checkKeep(); + } + + private int getMaxLines() { + int lines = 1; + if (isGone(mBinding.actor)) ++lines; + if (isGone(mBinding.remark)) ++lines; + if (isGone(mBinding.director)) ++lines; + return lines; + } + + private void setText(TextView view, int resId, String text) { + view.setText(getSpan(resId, text), TextView.BufferType.SPANNABLE); + view.setVisibility(text.isEmpty() ? View.GONE : View.VISIBLE); + view.setLinkTextColor(MDColor.YELLOW_500); + CustomMovement.bind(view); + view.setTag(text); + } + + private SpannableStringBuilder getSpan(int resId, String text) { + if (resId > 0) text = getString(resId, text); + Map map = new HashMap<>(); + Matcher m = Sniffer.CLICKER.matcher(text); + while (m.find()) { + String key = Trans.s2t(m.group(2)).trim(); + text = text.replace(m.group(), key); + map.put(key, m.group(1)); + } + SpannableStringBuilder span = SpannableStringBuilder.valueOf(text); + for (String s : map.keySet()) { + int index = text.indexOf(s); + Result result = Result.type(map.get(s)); + span.setSpan(getClickSpan(result), index, index + s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return span; + } + + private ClickableSpan getClickSpan(Result result) { + return new ClickableSpan() { + @Override + public void onClick(@NonNull View view) { + VodActivity.start(getActivity(), getKey(), result); + setRedirect(true); + } + }; + } + + private void getPlayer(Flag flag, Episode episode, boolean replay) { + mBinding.widget.title.setText(getString(R.string.detail_title, mBinding.name.getText(), episode.getName())); + mViewModel.playerContent(getKey(), flag.getFlag(), episode.getUrl()); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mBinding.widget.title.setSelected(true); + updateHistory(episode, replay); + showProgress(); + setMetadata(); + hideCenter(); + } + + private void setPlayer(Result result) { + result.getUrl().set(mQualityAdapter.getPosition()); + if (!result.getDesc().isEmpty()) setText(mBinding.content, R.string.detail_content, Html.fromHtml(result.getDesc()).toString()); + setUseParse(VodConfig.hasParse() && ((result.getPlayUrl().isEmpty() && VodConfig.get().getFlags().contains(result.getFlag())) || result.getJx() == 1)); + mPlayers.start(result, isUseParse(), getSite().isChangeable() ? getSite().getTimeout() : -1); + mBinding.control.parse.setVisibility(isUseParse() ? View.VISIBLE : View.GONE); + setQualityVisible(result.getUrl().isMulti()); + mQualityAdapter.addAll(result); + } + + private void setFlagActivated(Flag item) { + if (mFlagAdapter.size() == 0 || item.isActivated()) return; + if (mFlagAdapter.indexOf(item) == -1) item.setFlag(((Flag) mFlagAdapter.get(0)).getFlag()); + for (int i = 0; i < mFlagAdapter.size(); i++) ((Flag) mFlagAdapter.get(i)).setActivated(item); + mBinding.flag.setSelectedPosition(mFlagAdapter.indexOf(item)); + notifyItemChanged(mBinding.flag, mFlagAdapter); + setEpisodeAdapter(item.getEpisodes()); + setQualityVisible(false); + seamless(item); + } + + private void setEpisodeAdapter(List items) { + mBinding.episode.setVisibility(items.isEmpty() ? View.GONE : View.VISIBLE); + mEpisodeAdapter.setItems(items, null); + setArrayAdapter(items.size()); + setR2Callback(); + } + + private void seamless(Flag flag) { + Episode episode = flag.find(mHistory.getVodRemarks(), getMark().isEmpty()); + setQualityVisible(episode != null && episode.isActivated() && mQualityAdapter.getItemCount() > 1); + if (episode == null || episode.isActivated()) return; + mHistory.setVodRemarks(episode.getName()); + setEpisodeActivated(episode); + } + + private void setEpisodeActivated(Episode item) { + int flagPosition = getFlagPosition(); + if (shouldEnterFullscreen(item)) return; + if (isFullscreen()) Notify.show(getString(R.string.play_ready, item.getName())); + for (int i = 0; i < mFlagAdapter.size(); i++) ((Flag) mFlagAdapter.get(i)).toggle(flagPosition == i, item); + mBinding.episode.setSelectedPosition(getEpisodePosition()); + notifyItemChanged(mBinding.episode, mEpisodeAdapter); + onRefresh(); + } + + private void setQualityVisible(boolean visible) { + mBinding.quality.setVisibility(visible ? View.VISIBLE : View.GONE); + setR2Callback(); + } + + private void setQualityActivated(Result result) { + try { + mPlayers.start(result, isUseParse(), getSite().isChangeable() ? getSite().getTimeout() : -1); + } catch (Exception e) { + ErrorEvent.extract(tag, e.getMessage()); + e.printStackTrace(); + } + } + + private void reverseEpisode(boolean scroll) { + for (int i = 0; i < mFlagAdapter.size(); i++) Collections.reverse(((Flag) mFlagAdapter.get(i)).getEpisodes()); + setEpisodeAdapter(getFlag().getEpisodes()); + if (scroll) mBinding.episode.setSelectedPosition(getEpisodePosition()); + } + + private void setParseActivated(Parse item) { + VodConfig.get().setParse(item); + notifyItemChanged(mBinding.control.parse, mParseAdapter); + onRefresh(); + } + + private void setArrayAdapter(int size) { + List items = new ArrayList<>(); + items.add(getString(R.string.play_reverse)); + items.add(getString(mHistory.getRevPlayText())); + mBinding.array.setVisibility(size > 1 ? View.VISIBLE : View.GONE); + if (mHistory.isRevSort()) for (int i = size; i > 0; i -= 20) items.add(i + "-" + Math.max(i - 19, 1)); + else for (int i = 0; i < size; i += 20) items.add((i + 1) + "-" + Math.min(i + 20, size)); + mArrayAdapter.setItems(items, null); + } + + private int findFocusDown(int index) { + List orders = Arrays.asList(R.id.flag, R.id.quality, R.id.episode, R.id.array, R.id.part, R.id.quick); + for (int i = 0; i < orders.size(); i++) if (i > index) if (isVisible(findViewById(orders.get(i)))) return orders.get(i); + return 0; + } + + private int findFocusUp(int index) { + List orders = Arrays.asList(R.id.flag, R.id.quality, R.id.episode, R.id.array, R.id.part, R.id.quick); + for (int i = orders.size() - 1; i >= 0; i--) if (i < index) if (isVisible(findViewById(orders.get(i)))) return orders.get(i); + return 0; + } + + private void updateFocus() { + mPartPresenter.setNextFocusUp(findFocusUp(4)); + mEpisodePresenter.setNextFocusUp(findFocusUp(2)); + mFlagPresenter.setNextFocusDown(findFocusDown(0)); + mEpisodePresenter.setNextFocusDown(findFocusDown(2)); + notifyItemChanged(mBinding.episode, mEpisodeAdapter); + notifyItemChanged(mBinding.part, mPartAdapter); + notifyItemChanged(mBinding.flag, mFlagAdapter); + } + + @Override + public void onRevSort() { + mHistory.setRevSort(!mHistory.isRevSort()); + reverseEpisode(false); + } + + @Override + public void onRevPlay(TextView view) { + mHistory.setRevPlay(!mHistory.isRevPlay()); + view.setText(mHistory.getRevPlayText()); + Notify.show(mHistory.getRevPlayHint()); + } + + private boolean shouldEnterFullscreen(Episode item) { + boolean enter = !isFullscreen() && item.isActivated(); + if (enter) enterFullscreen(); + return enter; + } + + private void enterFullscreen() { + mFocus1 = getCurrentFocus(); + mBinding.video.requestFocus(); + mBinding.video.setForeground(null); + mBinding.video.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); + mBinding.flag.setSelectedPosition(getFlagPosition()); + mPlayers.setDanmakuSize(1.2f); + mKeyDown.setFull(true); + setFullscreen(true); + mFocus2 = null; + } + + private void exitFullscreen() { + mBinding.video.setForeground(ResUtil.getDrawable(R.drawable.selector_video)); + mBinding.video.setLayoutParams(mFrameParams); + mPlayers.setDanmakuSize(0.8f); + getFocus1().requestFocus(); + mKeyDown.setFull(false); + setFullscreen(false); + mFocus2 = null; + hideInfo(); + } + + private void onDesc() { + CharSequence desc = mBinding.content.getText(); + if (desc.length() > 3) DescDialog.show(this, desc.subSequence(3, desc.length())); + } + + private void onKeep() { + Keep keep = Keep.find(getHistoryKey()); + Notify.show(keep != null ? R.string.keep_del : R.string.keep_add); + if (keep != null) keep.delete(); + else createKeep(); + RefreshEvent.keep(); + checkKeep(); + } + + private void onVideo() { + if (!isFullscreen()) enterFullscreen(); + } + + private void onChange() { + checkSearch(true); + } + + private void onLoop() { + mBinding.control.loop.setActivated(!mBinding.control.loop.isActivated()); + } + + private void checkNext() { + checkNext(true); + } + + private void checkNext(boolean notify) { + if (mHistory.isRevPlay()) onPrev(notify); + else onNext(notify); + } + + private void checkPrev() { + if (mHistory.isRevPlay()) onNext(true); + else onPrev(true); + } + + private void onNext(boolean notify) { + int current = getEpisodePosition(); + int max = mEpisodeAdapter.size() - 1; + current = ++current > max ? max : current; + Episode item = (Episode) mEpisodeAdapter.get(current); + if (!item.isActivated()) setEpisodeActivated(item); + else if (notify) Notify.show(mHistory.isRevPlay() ? R.string.error_play_prev : R.string.error_play_next); + } + + private void onPrev(boolean notify) { + int current = getEpisodePosition(); + current = --current < 0 ? 0 : current; + Episode item = (Episode) mEpisodeAdapter.get(current); + if (!item.isActivated()) setEpisodeActivated(item); + else if (notify) Notify.show(mHistory.isRevPlay() ? R.string.error_play_next : R.string.error_play_prev); + } + + private void onScale() { + int index = getScale(); + String[] array = ResUtil.getStringArray(R.array.select_scale); + mHistory.setScale(index = index == array.length - 1 ? 0 : ++index); + setScale(index); + } + + private void onSpeed() { + mBinding.control.speed.setText(mPlayers.addSpeed()); + mHistory.setSpeed(mPlayers.getSpeed()); + } + + private void onSpeedAdd() { + mBinding.control.speed.setText(mPlayers.addSpeed(0.25f)); + mHistory.setSpeed(mPlayers.getSpeed()); + } + + private void onSpeedSub() { + mBinding.control.speed.setText(mPlayers.subSpeed(0.25f)); + mHistory.setSpeed(mPlayers.getSpeed()); + } + + private boolean onSpeedLong() { + mBinding.control.speed.setText(mPlayers.toggleSpeed()); + mHistory.setSpeed(mPlayers.getSpeed()); + return true; + } + + private void onRefresh() { + onReset(false); + } + + private void onReset() { + onReset(isReplay()); + } + + private void onReset(boolean replay) { + mPlayers.stop(); + mPlayers.clear(); + mClock.setCallback(null); + if (mFlagAdapter.size() == 0) return; + if (mEpisodeAdapter.size() == 0) return; + getPlayer(getFlag(), getEpisode(), replay); + } + + private boolean onResetToggle() { + Setting.putReset(Math.abs(Setting.getReset() - 1)); + mBinding.control.reset.setText(ResUtil.getStringArray(R.array.select_reset)[Setting.getReset()]); + return true; + } + + private void onOpening() { + long current = mPlayers.getPosition(); + long duration = mPlayers.getDuration(); + if (current < 0 || duration < 0) return; + if (current > Constant.OPED_LIMIT) return; + setOpening(current); + } + + private void onOpeningAdd() { + setOpening(Math.max(0, Math.max(0, mHistory.getOpening()) + 1000)); + } + + private void onOpeningSub() { + setOpening(Math.max(0, Math.max(0, mHistory.getOpening()) - 1000)); + } + + private boolean onOpeningReset() { + setOpening(0); + return true; + } + + private void setOpening(long opening) { + mHistory.setOpening(opening); + mBinding.control.opening.setText(opening <= 0 ? getString(R.string.play_op) : mPlayers.stringToTime(mHistory.getOpening())); + } + + private void onEnding() { + long current = mPlayers.getPosition(); + long duration = mPlayers.getDuration(); + if (current < 0 || duration < 0) return; + if (duration - current > Constant.OPED_LIMIT) return; + setEnding(duration - current); + } + + private void onEndingAdd() { + setEnding(Math.max(0, Math.max(0, mHistory.getEnding()) + 1000)); + } + + private void onEndingSub() { + setEnding(Math.max(0, Math.max(0, mHistory.getEnding()) - 1000)); + } + + private boolean onEndingReset() { + setEnding(0); + return true; + } + + private void setEnding(long ending) { + mHistory.setEnding(ending); + mBinding.control.ending.setText(ending <= 0 ? getString(R.string.play_ed) : mPlayers.stringToTime(mHistory.getEnding())); + } + + private void onChoose() { + mPlayers.choose(this, mBinding.widget.title.getText()); + setRedirect(true); + } + + private void onDecode() { + mPlayers.toggleDecode(); + setDecode(); + } + + private void onTrack(View view) { + TrackDialog.create().player(mPlayers).type(Integer.parseInt(view.getTag().toString())).show(this); + hideControl(); + } + + private void onDanmaku() { + DanmakuDialog.create().player(mPlayers).show(this); + hideControl(); + } + + private void onToggle() { + if (isVisible(mBinding.control.getRoot())) hideControl(); + else showControl(getFocus2()); + } + + private void showProgress() { + mBinding.widget.progress.setVisibility(View.VISIBLE); + App.post(mR3, 0); + hideError(); + } + + private void hideProgress() { + mBinding.widget.progress.setVisibility(View.GONE); + App.removeCallbacks(mR3); + Traffic.reset(); + } + + private void showError(String text) { + mBinding.widget.error.setVisibility(View.VISIBLE); + mBinding.widget.text.setText(text); + hideProgress(); + } + + private void hideError() { + mBinding.widget.error.setVisibility(View.GONE); + mBinding.widget.text.setText(""); + } + + private void showInfo() { + mBinding.widget.top.setVisibility(View.VISIBLE); + mBinding.widget.center.setVisibility(View.VISIBLE); + mBinding.widget.exoDuration.setText(mPlayers.getDurationTime()); + mBinding.widget.exoPosition.setText(mPlayers.getPositionTime(0)); + } + + private void hideInfo() { + mBinding.widget.top.setVisibility(View.GONE); + mBinding.widget.center.setVisibility(View.GONE); + } + + private void showControl(View view) { + mBinding.control.getRoot().setVisibility(View.VISIBLE); + view.requestFocus(); + setR1Callback(); + } + + private void hideControl() { + mBinding.control.text.setText(R.string.play_track_text); + mBinding.control.getRoot().setVisibility(View.GONE); + App.removeCallbacks(mR1); + } + + private void hideCenter() { + mBinding.widget.action.setImageResource(R.drawable.ic_widget_play); + hideInfo(); + } + + private void setTraffic() { + Traffic.setSpeed(mBinding.widget.traffic); + App.post(mR3, Constant.INTERVAL_TRAFFIC); + } + + private void setR1Callback() { + App.post(mR1, Constant.INTERVAL_HIDE); + } + + private void setR2Callback() { + App.post(mR2, 500); + } + + private void setArtwork(String url) { + ImgUtil.load(url, R.drawable.radio, new CustomTarget<>(ResUtil.getScreenWidth(), ResUtil.getScreenHeight()) { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + mBinding.exo.setDefaultArtwork(resource); + } + + @Override + public void onLoadFailed(@Nullable Drawable error) { + mBinding.exo.setDefaultArtwork(error); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + } + }); + } + + private void setPartAdapter(List items) { + mBinding.part.setVisibility(View.VISIBLE); + mPartAdapter.setItems(items, null); + setR2Callback(); + } + + private void checkFlag(Vod item) { + boolean empty = item.getVodFlags().isEmpty(); + mBinding.flag.setVisibility(empty ? View.GONE : View.VISIBLE); + if (empty) { + ErrorEvent.flag(tag); + } else { + setFlagActivated(mHistory.getFlag()); + if (mHistory.isRevSort()) reverseEpisode(true); + } + } + + private void checkHistory(Vod item) { + mHistory = History.find(getHistoryKey()); + mHistory = mHistory == null ? createHistory(item) : mHistory; + if (!TextUtils.isEmpty(getMark())) mHistory.setVodRemarks(getMark()); + if (Setting.isIncognito() && mHistory.getKey().equals(getHistoryKey())) mHistory.delete(); + mBinding.control.opening.setText(mHistory.getOpening() <= 0 ? getString(R.string.play_op) : mPlayers.stringToTime(mHistory.getOpening())); + mBinding.control.ending.setText(mHistory.getEnding() <= 0 ? getString(R.string.play_ed) : mPlayers.stringToTime(mHistory.getEnding())); + mBinding.control.speed.setText(mPlayers.setSpeed(mHistory.getSpeed())); + mHistory.setVodPic(item.getVodPic()); + setScale(getScale()); + } + + private History createHistory(Vod item) { + History history = new History(); + history.setKey(getHistoryKey()); + history.setCid(VodConfig.getCid()); + history.setVodName(item.getVodName()); + history.findEpisode(item.getVodFlags()); + return history; + } + + private void updateHistory(Episode item, boolean replay) { + replay = replay || !item.equals(mHistory.getEpisode()); + mHistory.setEpisodeUrl(item.getUrl()); + mHistory.setVodRemarks(item.getName()); + mHistory.setVodFlag(getFlag().getFlag()); + mHistory.setCreateTime(System.currentTimeMillis()); + mHistory.setPosition(replay ? C.TIME_UNSET : mHistory.getPosition()); + } + + private void checkPlayImg() { + ActionEvent.update(); + } + + private void checkKeep() { + mBinding.keep.setCompoundDrawablesWithIntrinsicBounds(Keep.find(getHistoryKey()) == null ? R.drawable.ic_detail_keep_off : R.drawable.ic_detail_keep_on, 0, 0, 0); + } + + private void createKeep() { + Keep keep = new Keep(); + keep.setKey(getHistoryKey()); + keep.setCid(VodConfig.getCid()); + keep.setSiteName(getSite().getName()); + keep.setVodPic(mBinding.video.getTag().toString()); + keep.setVodName(mBinding.name.getText().toString()); + keep.setCreateTime(System.currentTimeMillis()); + keep.save(); + } + + @Override + public void onSubtitleClick() { + App.post(this::hideControl, 200); + App.post(() -> SubtitleDialog.create().view(mBinding.exo.getSubtitleView()).full(isFullscreen()).show(this), 200); + } + + @Override + public void onTimeChanged() { + long position, duration; + mHistory.setPosition(position = mPlayers.getPosition()); + mHistory.setDuration(duration = mPlayers.getDuration()); + if (position >= 0 && duration > 0 && !Setting.isIncognito()) App.execute(() -> mHistory.update()); + if (mHistory.getEnding() > 0 && duration > 0 && mHistory.getEnding() + position >= duration) { + checkEnded(false); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onActionEvent(ActionEvent event) { + if (isRedirect()) return; + if (ActionEvent.PLAY.equals(event.getAction()) || ActionEvent.PAUSE.equals(event.getAction())) { + onKeyCenter(); + } else if (ActionEvent.NEXT.equals(event.getAction())) { + mBinding.control.next.performClick(); + } else if (ActionEvent.PREV.equals(event.getAction())) { + mBinding.control.prev.performClick(); + } else if (ActionEvent.STOP.equals(event.getAction())) { + finish(); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (isRedirect()) return; + if (event.getType() == RefreshEvent.Type.DETAIL) getDetail(); + else if (event.getType() == RefreshEvent.Type.PLAYER) onRefresh(); + else if (event.getType() == RefreshEvent.Type.SUBTITLE) mPlayers.setSub(Sub.from(event.getPath())); + else if (event.getType() == RefreshEvent.Type.DANMAKU) mPlayers.setDanmaku(Danmaku.from(event.getPath())); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerEvent(PlayerEvent event) { + if (!event.getTag().equals(tag)) return; + switch (event.getState()) { + case PlayerEvent.PREPARE: + setDecode(); + setPosition(); + break; + case Player.STATE_BUFFERING: + showProgress(); + break; + case Player.STATE_READY: + hideProgress(); + checkPlayImg(); + mPlayers.reset(); + break; + case Player.STATE_ENDED: + checkEnded(true); + break; + case PlayerEvent.TRACK: + setMetadata(); + setTrackVisible(); + mClock.setCallback(this); + break; + case PlayerEvent.SIZE: + mBinding.widget.size.setText(mPlayers.getSizeText()); + break; + } + } + + private void setPosition() { + if (mHistory != null) mPlayers.seekTo(Math.max(mHistory.getOpening(), mHistory.getPosition())); + } + + private void checkEnded(boolean notify) { + if (mBinding.control.loop.isActivated()) { + onReset(true); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + checkNext(notify); + checkPlayImg(); + } + } + + private void setTrackVisible() { + mBinding.control.text.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_TEXT) || mPlayers.isVod() ? View.VISIBLE : View.GONE); + mBinding.control.audio.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_AUDIO) ? View.VISIBLE : View.GONE); + mBinding.control.video.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_VIDEO) ? View.VISIBLE : View.GONE); + } + + private void setMetadata() { + String title = mHistory.getVodName(); + String episode = getEpisode().getName(); + String artist = title.equals(episode) ? "" : getString(R.string.play_now, episode); + mPlayers.setMetadata(title, artist, mHistory.getVodPic(), mBinding.exo.getDefaultArtwork()); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onErrorEvent(ErrorEvent event) { + if (!event.getTag().equals(tag)) return; + if (mPlayers.retried()) onError(event); + else onRefresh(); + } + + private void onError(ErrorEvent event) { + Track.delete(mPlayers.getUrl()); + showError(event.getMsg()); + mClock.setCallback(null); + mPlayers.resetTrack(); + mPlayers.reset(); + mPlayers.stop(); + startFlow(); + } + + private void startFlow() { + if (!getSite().isChangeable()) return; + if (isUseParse()) checkParse(); + else checkFlag(); + } + + private void checkParse() { + int position = getParsePosition(); + boolean last = position == mParseAdapter.size() - 1; + boolean pass = position == 0 || last; + if (last) initParse(); + if (pass) checkFlag(); + else nextParse(position); + } + + private void initParse() { + if (mParseAdapter.size() == 0) return; + VodConfig.get().setParse((Parse) mParseAdapter.get(0)); + notifyItemChanged(mBinding.control.parse, mParseAdapter); + } + + private void checkFlag() { + int position = isGone(mBinding.flag) ? -1 : getFlagPosition(); + if (position == mFlagAdapter.size() - 1) checkSearch(false); + else nextFlag(position); + } + + private void checkSearch(boolean force) { + if (mQuickAdapter.size() == 0) initSearch(mBinding.name.getText().toString(), true); + else if (isAutoMode() || force) nextSite(); + } + + private void initSearch(String keyword, boolean auto) { + stopSearch(); + setAutoMode(auto); + setInitAuto(auto); + startSearch(keyword); + mBinding.part.setTag(keyword); + } + + private boolean isPass(Site item) { + if (isAutoMode() && !item.isChangeable()) return false; + return item.isSearchable(); + } + + private void startSearch(String keyword) { + mQuickAdapter.clear(); + List sites = new ArrayList<>(); + mExecutor = Executors.newFixedThreadPool(10); + for (Site site : VodConfig.get().getSites()) if (isPass(site)) sites.add(site); + for (Site site : sites) mExecutor.execute(() -> search(site, keyword)); + } + + private void stopSearch() { + if (mExecutor == null) return; + mExecutor.shutdownNow(); + mExecutor = null; + } + + private void search(Site site, String keyword) { + try { + mViewModel.searchContent(site, keyword, true); + } catch (Throwable ignored) { + } + } + + private void setSearch(Result result) { + List items = result.getList(); + Iterator iterator = items.iterator(); + while (iterator.hasNext()) if (mismatch(iterator.next())) iterator.remove(); + mQuickAdapter.addAll(mQuickAdapter.size(), items); + mBinding.quick.setVisibility(View.VISIBLE); + if (isInitAuto()) nextSite(); + if (items.isEmpty()) return; + App.removeCallbacks(mR4); + } + + private void setSearch(Vod item) { + setAutoMode(false); + getDetail(item); + } + + private boolean mismatch(Vod item) { + if (getId().equals(item.getVodId())) return true; + if (mBroken.contains(item.getVodId())) return true; + String keyword = Objects.toString(mBinding.part.getTag(), ""); + if (isAutoMode()) return !item.getVodName().equals(keyword); + else return !item.getVodName().contains(keyword); + } + + private void nextParse(int position) { + Parse parse = (Parse) mParseAdapter.get(position + 1); + Notify.show(getString(R.string.play_switch_parse, parse.getName())); + setParseActivated(parse); + } + + private void nextFlag(int position) { + Flag flag = (Flag) mFlagAdapter.get(position + 1); + Notify.show(getString(R.string.play_switch_flag, flag.getFlag())); + setFlagActivated(flag); + } + + private void nextSite() { + if (mQuickAdapter.size() == 0) return; + Vod item = (Vod) mQuickAdapter.get(0); + Notify.show(getString(R.string.play_switch_site, item.getSiteName())); + mQuickAdapter.removeItems(0, 1); + mBroken.add(getId()); + setInitAuto(false); + getDetail(item); + } + + private void onPaused() { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (isFullscreen()) showInfo(); + else hideInfo(); + mPlayers.pause(); + checkPlayImg(); + } + + private void onPlay() { + if (mHistory != null && mPlayers.isEnded()) mPlayers.seekTo(mHistory.getOpening()); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (!mPlayers.isEmpty() && mPlayers.isIdle()) mPlayers.prepare(); + mPlayers.play(); + checkPlayImg(); + hideCenter(); + } + + private boolean isFullscreen() { + return fullscreen; + } + + private void setFullscreen(boolean fullscreen) { + this.fullscreen = fullscreen; + } + + private boolean isInitAuto() { + return initAuto; + } + + private void setInitAuto(boolean initAuto) { + this.initAuto = initAuto; + } + + private boolean isAutoMode() { + return autoMode; + } + + private void setAutoMode(boolean autoMode) { + this.autoMode = autoMode; + } + + public boolean isUseParse() { + return useParse; + } + + public void setUseParse(boolean useParse) { + this.useParse = useParse; + } + + public boolean isRedirect() { + return redirect; + } + + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + + private View getFocus1() { + return mFocus1 == null ? mBinding.video : mFocus1; + } + + private View getFocus2() { + return mFocus2 == null || mFocus2 == mBinding.control.opening || mFocus2 == mBinding.control.ending ? mBinding.control.next : mFocus2; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (isFullscreen() && KeyUtil.isMenuKey(event)) onToggle(); + if (isVisible(mBinding.control.getRoot())) setR1Callback(); + if (isVisible(mBinding.control.getRoot())) mFocus2 = getCurrentFocus(); + if (isFullscreen() && isGone(mBinding.control.getRoot()) && mKeyDown.hasEvent(event)) return mKeyDown.onKeyDown(event); + return super.dispatchKeyEvent(event); + } + + @Override + public void onSeeking(long time) { + mBinding.widget.center.setVisibility(View.VISIBLE); + mBinding.widget.exoDuration.setText(mPlayers.getDurationTime()); + mBinding.widget.exoPosition.setText(mPlayers.getPositionTime(time)); + mBinding.widget.action.setImageResource(time > 0 ? R.drawable.ic_widget_forward : R.drawable.ic_widget_rewind); + hideProgress(); + } + + @Override + public void onSeekTo(long time) { + mKeyDown.resetTime(); + mPlayers.seek(time); + showProgress(); + onPlay(); + } + + @Override + public void onSpeedUp() { + if (!mPlayers.isPlaying()) return; + mBinding.control.speed.setText(mPlayers.setSpeed(Setting.getSpeed())); + mBinding.widget.speed.startAnimation(ResUtil.getAnim(R.anim.forward)); + mBinding.widget.speed.setVisibility(View.VISIBLE); + } + + @Override + public void onSpeedEnd() { + mBinding.control.speed.setText(mPlayers.setSpeed(mHistory.getSpeed())); + mBinding.widget.speed.setVisibility(View.GONE); + mBinding.widget.speed.clearAnimation(); + } + + @Override + public void onKeyUp() { + long current = mPlayers.getPosition(); + long duration = mPlayers.getDuration(); + if (duration - current < Constant.OPED_LIMIT) { + showControl(mBinding.control.ending); + } else if (current < Constant.OPED_LIMIT) { + showControl(mBinding.control.opening); + } else { + showControl(getFocus2()); + } + } + + @Override + public void onKeyDown() { + showControl(getFocus2()); + } + + @Override + public void onKeyCenter() { + if (mPlayers.isPlaying()) onPaused(); + else onPlay(); + hideControl(); + } + + @Override + public void onSingleTap() { + if (isFullscreen()) onToggle(); + } + + @Override + public void onDoubleTap() { + if (isFullscreen()) onKeyCenter(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK && requestCode == 1001) mPlayers.checkData(data); + } + + @Override + protected void onStart() { + super.onStart(); + mClock.stop().start(); + onPlay(); + } + + @Override + protected void onResume() { + super.onResume(); + if (isRedirect()) onPlay(); + setRedirect(false); + } + + @Override + protected void onPause() { + super.onPause(); + if (isRedirect()) onPaused(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Setting.isBackgroundOff()) onPaused(); + if (Setting.isBackgroundOff()) mClock.stop(); + } + + @Override + public void onBackPressed() { + if (isVisible(mBinding.control.getRoot())) { + hideControl(); + } else if (isVisible(mBinding.widget.center)) { + hideCenter(); + } else if (isFullscreen()) { + exitFullscreen(); + } else { + stopSearch(); + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + stopSearch(); + mClock.release(); + mPlayers.release(); + RefreshEvent.history(); + PlaybackService.stop(); + App.removeCallbacks(mR1, mR2, mR3, mR4); + mViewModel.result.removeObserver(mObserveDetail); + mViewModel.player.removeObserver(mObservePlayer); + mViewModel.search.removeObserver(mObserveSearch); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java new file mode 100644 index 00000000..cc083b2c --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java @@ -0,0 +1,204 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.OnChildViewHolderSelectedListener; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; +import androidx.viewpager.widget.ViewPager; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Class; +import com.fongmi.android.tv.bean.Filter; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.ActivityVodBinding; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.fragment.VodFragment; +import com.fongmi.android.tv.ui.presenter.TypePresenter; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.utils.Prefers; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +public class VodActivity extends BaseActivity implements TypePresenter.OnClickListener { + + private ActivityVodBinding mBinding; + private ArrayObjectAdapter mAdapter; + private PageAdapter mPageAdapter; + private boolean coolDown; + private View mOldView; + + public static void start(Activity activity, Result result) { + start(activity, VodConfig.get().getHome().getKey(), result); + } + + public static void start(Activity activity, String key, Result result) { + if (result == null || result.getTypes().isEmpty()) return; + Intent intent = new Intent(activity, VodActivity.class); + intent.putExtra("key", key); + intent.putExtra("result", result); + for (Map.Entry> entry : result.getFilters().entrySet()) Prefers.put("filter_" + key + "_" + entry.getKey(), App.gson().toJson(entry.getValue())); + activity.startActivity(intent); + } + + private String getKey() { + return getIntent().getStringExtra("key"); + } + + private Result getResult() { + return getIntent().getParcelableExtra("result"); + } + + private List getFilter(String typeId) { + return Filter.arrayFrom(Prefers.getString("filter_" + getKey() + "_" + typeId)); + } + + private Site getSite() { + return VodConfig.get().getSite(getKey()); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityVodBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView() { + setRecyclerView(); + setTypes(); + setPager(); + } + + @Override + protected void initEvent() { + mBinding.pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + mBinding.recycler.setSelectedPosition(position); + } + }); + mBinding.recycler.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable RecyclerView.ViewHolder child, int position, int subposition) { + onChildSelected(child); + } + }); + } + + private void setRecyclerView() { + mBinding.recycler.setHorizontalSpacing(ResUtil.dp2px(16)); + mBinding.recycler.setRowHeight(ViewGroup.LayoutParams.WRAP_CONTENT); + mBinding.recycler.setAdapter(new ItemBridgeAdapter(mAdapter = new ArrayObjectAdapter(new TypePresenter(this)))); + } + + private List getTypes(Result result) { + List items = new ArrayList<>(); + for (String cate : getSite().getCategories()) for (Class item : result.getTypes()) if (cate.equals(item.getTypeName())) items.add(item); + return items; + } + + private void setTypes() { + Result result = getResult(); + result.setTypes(getTypes(result)); + for (Class item : result.getTypes()) item.setFilters(getFilter(item.getTypeId())); + mAdapter.setItems(result.getTypes(), null); + } + + private void setPager() { + mBinding.pager.setAdapter(mPageAdapter = new PageAdapter(getSupportFragmentManager())); + } + + private void onChildSelected(@Nullable RecyclerView.ViewHolder child) { + if (mOldView != null) mOldView.setActivated(false); + if (child == null) return; + mOldView = child.itemView; + mOldView.setActivated(true); + App.post(mRunnable, 100); + } + + private final Runnable mRunnable = new Runnable() { + @Override + public void run() { + mBinding.pager.setCurrentItem(mBinding.recycler.getSelectedPosition()); + } + }; + + private void updateFilter(Class item) { + if (item.getFilter() == null) return; + getFragment().toggleFilter(item.toggleFilter()); + mAdapter.notifyArrayItemRangeChanged(0, mAdapter.size()); + } + + private VodFragment getFragment() { + return (VodFragment) mPageAdapter.instantiateItem(mBinding.pager, mBinding.pager.getCurrentItem()); + } + + private void setCoolDown() { + App.post(() -> coolDown = false, 2000); + coolDown = true; + } + + @Override + public void onItemClick(Class item) { + updateFilter(item); + } + + @Override + public void onRefresh(Class item) { + getFragment().onRefresh(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (KeyUtil.isMenuKey(event)) updateFilter((Class) mAdapter.get(mBinding.pager.getCurrentItem())); + if (KeyUtil.isBackKey(event) && event.isLongPress() && getFragment().goRoot()) setCoolDown(); + return super.dispatchKeyEvent(event); + } + + @Override + public void onBackPressed() { + Class item = (Class) mAdapter.get(mBinding.pager.getCurrentItem()); + if (item.getFilter() != null && item.getFilter()) updateFilter(item); + else if (getFragment().canBack()) getFragment().goBack(); + else if (!coolDown) super.onBackPressed(); + } + + class PageAdapter extends FragmentStatePagerAdapter { + + public PageAdapter(@NonNull FragmentManager fm) { + super(fm); + } + + @NonNull + @Override + public Fragment getItem(int position) { + Class type = (Class) mAdapter.get(position); + return VodFragment.newInstance(getKey(), type.getTypeId(), type.getStyle(), type.getExtend(false), "1".equals(type.getTypeFlag())); + } + + @Override + public int getCount() { + return mAdapter.size(); + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java new file mode 100644 index 00000000..9370f735 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java @@ -0,0 +1,75 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.AdapterConfigBinding; + +import java.util.List; + +public class ConfigAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private List mItems; + + public ConfigAdapter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + + void onTextClick(Config item); + + void onDeleteClick(Config item); + } + + public ConfigAdapter addAll(int type) { + mItems = Config.getAll(type); + mItems.remove(type == 0 ? VodConfig.get().getConfig() : LiveConfig.get().getConfig()); + return this; + } + + public int remove(Config item) { + int position = mItems.indexOf(item); + if (position == -1) return -1; + item.delete(); + mItems.remove(position); + notifyItemRemoved(position); + return getItemCount(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterConfigBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Config item = mItems.get(position); + holder.binding.text.setText(item.getDesc()); + holder.binding.text.setOnClickListener(v -> mListener.onTextClick(item)); + holder.binding.delete.setOnClickListener(v -> mListener.onDeleteClick(item)); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterConfigBinding binding; + + public ViewHolder(@NonNull AdapterConfigBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DohAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DohAdapter.java new file mode 100644 index 00000000..0481166f --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/DohAdapter.java @@ -0,0 +1,67 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.databinding.AdapterDohBinding; +import com.github.catvod.bean.Doh; + +import java.util.List; + +public class DohAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private int select; + + public DohAdapter(OnClickListener listener) { + this.mItems = VodConfig.get().getDoh(); + this.mListener = listener; + } + + public void setSelect(int select) { + this.select = select; + } + + public int getSelect() { + return select; + } + + public interface OnClickListener { + + void onItemClick(Doh item); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterDohBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Doh item = mItems.get(position); + holder.binding.text.setText(item.getName()); + holder.binding.text.setActivated(select == position); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(item)); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterDohBinding binding; + + public ViewHolder(@NonNull AdapterDohBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/KeepAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/KeepAdapter.java new file mode 100644 index 00000000..326b646e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/KeepAdapter.java @@ -0,0 +1,118 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.databinding.AdapterVodBinding; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.ResUtil; + +import java.util.ArrayList; +import java.util.List; + +public class KeepAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private int width, height; + private boolean delete; + + public KeepAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + setLayoutSize(); + } + + private void setLayoutSize() { + int space = ResUtil.dp2px(48) + ResUtil.dp2px(16 * (Product.getColumn() - 1)); + int base = ResUtil.getScreenWidth() - space; + width = base / Product.getColumn(); + height = (int) (width / 0.75f); + } + + public interface OnClickListener { + + void onItemClick(Keep item); + + void onItemDelete(Keep item); + + boolean onLongClick(); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void delete(Keep item) { + int index = mItems.indexOf(item); + if (index == -1) return; + mItems.remove(index); + notifyItemRemoved(index); + } + + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + notifyItemRangeChanged(0, mItems.size()); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ViewHolder holder = new ViewHolder(AdapterVodBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + holder.binding.getRoot().getLayoutParams().width = width; + holder.binding.getRoot().getLayoutParams().height = height; + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Keep item = mItems.get(position); + setFocusListener(holder.binding); + setClickListener(holder.itemView, item); + holder.binding.name.setText(item.getVodName()); + holder.binding.remark.setVisibility(View.GONE); + holder.binding.site.setVisibility(View.VISIBLE); + holder.binding.site.setText(item.getSiteName()); + holder.binding.delete.setVisibility(!delete ? View.GONE : View.VISIBLE); + ImgUtil.loadVod(item.getVodName(), item.getVodPic(), holder.binding.image); + } + + private void setFocusListener(AdapterVodBinding binding) { + binding.getRoot().setOnFocusChangeListener((v, hasFocus) -> binding.name.setSelected(hasFocus)); + } + + private void setClickListener(View root, Keep item) { + root.setOnLongClickListener(view -> mListener.onLongClick()); + root.setOnClickListener(view -> { + if (isDelete()) mListener.onItemDelete(item); + else mListener.onItemClick(item); + }); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterVodBinding binding; + + public ViewHolder(@NonNull AdapterVodBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/KeyboardAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/KeyboardAdapter.java new file mode 100644 index 00000000..ffb064cb --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/KeyboardAdapter.java @@ -0,0 +1,120 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.AdapterKeyboardIconBinding; +import com.fongmi.android.tv.databinding.AdapterKeyboardTextBinding; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + + +public class KeyboardAdapter extends RecyclerView.Adapter { + + private final List icons = Arrays.asList(R.drawable.ic_keyboard_remote, R.drawable.ic_keyboard_left, R.drawable.ic_keyboard_right, R.drawable.ic_keyboard_back, R.drawable.ic_keyboard_search, R.drawable.ic_keyboard, R.drawable.ic_setting_home); + private final List enList = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + private final List twList = Arrays.asList("↩", "ㄅ", "ㄆ", "ㄇ", "ㄈ", "ㄉ", "ㄊ", "ㄋ", "ㄌ", "ㄍ", "ㄎ", "ㄏ", "ㄐ", "ㄑ", "ㄒ", "ㄓ", "ㄔ", "ㄕ", "ㄖ", "ㄗ", "ㄘ", "ㄙ", "ㄧ", "ㄨ", "ㄩ", "ㄚ", "ㄛ", "ㄜ", "ㄝ", "ㄞ", "ㄟ", "ㄠ", "ㄡ", "ㄢ", "ㄣ", "ㄤ", "ㄥ", "ㄦ", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"); + private final OnClickListener mListener; + private final List mItems; + + public KeyboardAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + this.mItems.addAll(icons); + this.mItems.addAll(Setting.isZhuyin() ? twList : enList); + } + + public interface OnClickListener { + + void onTextClick(String text); + + void onIconClick(int resId); + + boolean onLongClick(int resId); + } + + public void toggle() { + Setting.putZhuyin(!Setting.isZhuyin()); + mItems.removeAll(Setting.isZhuyin() ? enList : twList); + mItems.addAll(icons.size(), Setting.isZhuyin() ? twList : enList); + notifyItemRangeRemoved(icons.size(), Setting.isZhuyin() ? enList.size() : twList.size()); + notifyItemRangeInserted(icons.size(), Setting.isZhuyin() ? twList.size() : enList.size()); + } + + @Override + public int getItemViewType(int position) { + return mItems.get(position) instanceof String ? 0 : 1; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == 0) return new TextHolder(AdapterKeyboardTextBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + else return new IconHolder(AdapterKeyboardIconBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + switch (getItemViewType(position)) { + case 0: + TextHolder text = (TextHolder) holder; + text.binding.text.setText(mItems.get(position).toString()); + break; + case 1: + IconHolder icon = (IconHolder) holder; + icon.binding.icon.setImageResource((int) mItems.get(position)); + break; + } + } + + class TextHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private final AdapterKeyboardTextBinding binding; + + TextHolder(@NonNull AdapterKeyboardTextBinding binding) { + super(binding.getRoot()); + this.binding = binding; + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + mListener.onTextClick(mItems.get(getLayoutPosition()).toString()); + } + } + + class IconHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + + private final AdapterKeyboardIconBinding binding; + + IconHolder(@NonNull AdapterKeyboardIconBinding binding) { + super(binding.getRoot()); + this.binding = binding; + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } + + @Override + public void onClick(View view) { + mListener.onIconClick((int) mItems.get(getLayoutPosition())); + } + + @Override + public boolean onLongClick(View view) { + return mListener.onLongClick((int) mItems.get(getLayoutPosition())); + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/LiveAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/LiveAdapter.java new file mode 100644 index 00000000..15908326 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/LiveAdapter.java @@ -0,0 +1,81 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.databinding.AdapterLiveBinding; + +import java.util.List; + +public class LiveAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private boolean action; + + public LiveAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = LiveConfig.get().getLives(); + } + + public void setAction(boolean action) { + this.action = action; + } + + public interface OnClickListener { + + void onItemClick(Live item); + + void onBootClick(int position, Live item); + + void onPassClick(int position, Live item); + + boolean onBootLongClick(Live item); + + boolean onPassLongClick(Live item); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterLiveBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Live item = mItems.get(position); + holder.binding.text.setText(item.getName()); + holder.binding.text.setSelected(item.isActivated()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.boot.setImageResource(item.getBootIcon()); + holder.binding.pass.setImageResource(item.getPassIcon()); + holder.binding.boot.setVisibility(action ? View.VISIBLE : View.GONE); + holder.binding.pass.setVisibility(action ? View.VISIBLE : View.GONE); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(item)); + holder.binding.boot.setOnClickListener(v -> mListener.onBootClick(position, item)); + holder.binding.pass.setOnClickListener(v -> mListener.onPassClick(position, item)); + holder.binding.boot.setOnLongClickListener(v -> mListener.onBootLongClick(item)); + holder.binding.pass.setOnLongClickListener(v -> mListener.onPassLongClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterLiveBinding binding; + + public ViewHolder(@NonNull AdapterLiveBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/QualityAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/QualityAdapter.java new file mode 100644 index 00000000..9baa3936 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/QualityAdapter.java @@ -0,0 +1,71 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.databinding.AdapterQualityBinding; + +public class QualityAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private Result mResult; + private int position; + + public QualityAdapter(OnClickListener listener) { + this.mListener = listener; + this.mResult = Result.empty(); + } + + public interface OnClickListener { + + void onItemClick(Result result); + } + + public int getPosition() { + return position; + } + + public void addAll(Result result) { + mResult = result; + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mResult.getUrl().getValues().size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterQualityBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.binding.text.setText(mResult.getUrl().n(position)); + holder.binding.text.setOnClickListener(v -> onItemClick(position)); + holder.binding.text.setActivated(mResult.getUrl().getPosition() == position); + } + + private void onItemClick(int position) { + this.position = position; + mResult.getUrl().set(position); + mListener.onItemClick(mResult); + notifyItemRangeChanged(0, getItemCount()); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterQualityBinding binding; + + ViewHolder(@NonNull AdapterQualityBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/RecordAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/RecordAdapter.java new file mode 100644 index 00000000..0edf694d --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/RecordAdapter.java @@ -0,0 +1,94 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.AdapterSearchRecordBinding; +import com.google.gson.reflect.TypeToken; + +import java.util.ArrayList; +import java.util.List; + +public class RecordAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public RecordAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = getItems(); + this.mListener.onDataChanged(mItems.size()); + } + + public interface OnClickListener { + + void onItemClick(String text); + + void onDataChanged(int size); + } + + private List getItems() { + if (Setting.getKeyword().isEmpty()) return new ArrayList<>(); + return App.gson().fromJson(Setting.getKeyword(), new TypeToken>() {}.getType()); + } + + private void checkToAdd(String item) { + mItems.remove(item); + mItems.add(0, item); + if (mItems.size() > 8) mItems.remove(8); + } + + public void add(String item) { + checkToAdd(item); + notifyDataSetChanged(); + mListener.onDataChanged(getItemCount()); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterSearchRecordBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.binding.record.setText(mItems.get(position)); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener { + + private final AdapterSearchRecordBinding binding; + + public ViewHolder(@NonNull AdapterSearchRecordBinding binding) { + super(binding.getRoot()); + this.binding = binding; + itemView.setOnClickListener(this); + itemView.setOnLongClickListener(this); + } + + @Override + public void onClick(View view) { + mListener.onItemClick(mItems.get(getLayoutPosition())); + } + + @Override + public boolean onLongClick(View v) { + mItems.remove(getLayoutPosition()); + notifyItemRemoved(getLayoutPosition()); + mListener.onDataChanged(getItemCount()); + Setting.putKeyword(App.gson().toJson(mItems)); + return true; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java new file mode 100644 index 00000000..c04c2e21 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java @@ -0,0 +1,80 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.databinding.AdapterRestoreBinding; +import com.github.catvod.utils.Path; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RestoreAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public RestoreAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + this.addAll(); + } + + public interface OnClickListener { + + void onItemClick(File item); + + void onDeleteClick(File item); + } + + public void addAll() { + File[] files = Path.tv().listFiles(); + if (files == null) files = new File[0]; + for (File file : files) if (file.getName().startsWith("tv") && file.getName().endsWith(".bk.gz")) mItems.add(file); + if (!mItems.isEmpty()) Collections.sort(mItems, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified())); + notifyDataSetChanged(); + } + + public int remove(File item) { + int position = mItems.indexOf(item); + if (position == -1) return -1; + Path.clear(item); + mItems.remove(position); + notifyItemRemoved(position); + return getItemCount(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterRestoreBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + File item = mItems.get(position); + holder.binding.text.setText(item.getName()); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(item)); + holder.binding.delete.setOnClickListener(v -> mListener.onDeleteClick(item)); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterRestoreBinding binding; + + public ViewHolder(@NonNull AdapterRestoreBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/SiteAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/SiteAdapter.java new file mode 100644 index 00000000..30aa3b0d --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/SiteAdapter.java @@ -0,0 +1,115 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.AdapterSiteBinding; + +import java.util.ArrayList; +import java.util.List; + +public class SiteAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private int type; + + public SiteAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + this.addAll(); + } + + public interface OnClickListener { + + void onItemClick(Site item); + } + + public void setType(int type) { + this.type = type; + notifyDataSetChanged(); + } + + public void selectAll() { + setEnable(type != 3); + } + + public void cancelAll() { + setEnable(type == 3); + } + + private void addAll() { + for (Site site : VodConfig.get().getSites()) if (!site.isHide()) mItems.add(site); + } + + public List getItems() { + return mItems; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterSiteBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Site item = mItems.get(position); + holder.binding.text.setText(item.getName()); + holder.binding.check.setChecked(getChecked(item)); + holder.binding.text.setSelected(item.isActivated()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.check.setVisibility(type == 0 ? View.GONE : View.VISIBLE); + holder.binding.getRoot().setOnLongClickListener(v -> setLongListener(item)); + holder.binding.getRoot().setOnClickListener(v -> setListener(item, position)); + holder.binding.text.setGravity(Setting.getSiteMode() == 0 ? Gravity.CENTER : Gravity.START); + } + + private boolean getChecked(Site item) { + if (type == 1) return item.isSearchable(); + if (type == 2) return item.isChangeable(); + return false; + } + + private void setListener(Site item, int position) { + if (type == 0) mListener.onItemClick(item); + if (type == 1) item.setSearchable(!item.isSearchable()).save(); + if (type == 2) item.setChangeable(!item.isChangeable()).save(); + if (type != 0) notifyItemChanged(position); + } + + private boolean setLongListener(Site item) { + if (type == 1) setEnable(!item.isSearchable()); + if (type == 2) setEnable(!item.isChangeable()); + return true; + } + + private void setEnable(boolean enable) { + if (type == 1) for (Site site : mItems) site.setSearchable(enable).save(); + if (type == 2) for (Site site : mItems) site.setChangeable(enable).save(); + notifyItemRangeChanged(0, getItemCount()); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterSiteBinding binding; + + ViewHolder(@NonNull AdapterSiteBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/WordAdapter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/WordAdapter.java new file mode 100644 index 00000000..9a237c43 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/adapter/WordAdapter.java @@ -0,0 +1,67 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.databinding.AdapterSearchWordBinding; + +import java.util.ArrayList; +import java.util.List; + +public class WordAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public WordAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(String text); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterSearchWordBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.binding.word.setText(mItems.get(position)); + } + + public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { + + private final AdapterSearchWordBinding binding; + + public ViewHolder(@NonNull AdapterSearchWordBinding binding) { + super(binding.getRoot()); + this.binding = binding; + itemView.setOnClickListener(this); + } + + @Override + public void onClick(View view) { + mListener.onItemClick(mItems.get(getLayoutPosition())); + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java new file mode 100644 index 00000000..fe99f288 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java @@ -0,0 +1,142 @@ +package com.fongmi.android.tv.ui.base; + +import android.app.Activity; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.View; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AppCompatActivity; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Util; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; + +import me.jessyan.autosize.AutoSizeCompat; + +public abstract class BaseActivity extends AppCompatActivity { + + protected abstract ViewBinding getBinding(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(getBinding().getRoot()); + EventBus.getDefault().register(this); + Util.hideSystemUI(this); + setBackCallback(); + initView(); + initEvent(); + } + + @Override + public void setContentView(View view) { + super.setContentView(view); + refreshWall(); + } + + protected Activity getActivity() { + return this; + } + + protected boolean customWall() { + return true; + } + + protected boolean handleBack() { + return false; + } + + protected void initView() { + } + + protected void initEvent() { + } + + protected void onBackPress() { + } + + protected boolean isVisible(View view) { + return view.getVisibility() == View.VISIBLE; + } + + protected boolean isGone(View view) { + return view.getVisibility() == View.GONE; + } + + protected void notifyItemChanged(RecyclerView view, ArrayObjectAdapter adapter) { + if (!view.isComputingLayout()) adapter.notifyArrayItemRangeChanged(0, adapter.size()); + } + + private void setBackCallback() { + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(handleBack()) { + @Override + public void handleOnBackPressed() { + onBackPress(); + } + }); + } + + private void refreshWall() { + try { + if (!customWall()) return; + File file = FileUtil.getWall(Setting.getWall()); + if (file.exists() && file.length() > 0) getWindow().setBackgroundDrawable(Drawable.createFromPath(file.getAbsolutePath())); + else getWindow().setBackgroundDrawableResource(ResUtil.getDrawable(file.getName())); + } catch (Exception e) { + getWindow().setBackgroundDrawableResource(R.drawable.wallpaper_4); + } + } + + private Resources hackResources(Resources resources) { + try { + AutoSizeCompat.autoConvertDensityOfGlobal(resources); + return resources; + } catch (Exception ignored) { + return resources; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType() == RefreshEvent.Type.WALL) refreshWall(); + } + + @Override + public Resources getResources() { + return hackResources(super.getResources()); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Util.hideSystemUI(this); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) Util.hideSystemUI(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseFragment.java b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseFragment.java new file mode 100644 index 00000000..f00529fe --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseFragment.java @@ -0,0 +1,53 @@ +package com.fongmi.android.tv.ui.base; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.viewbinding.ViewBinding; + +public abstract class BaseFragment extends Fragment { + + protected abstract ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container); + + private boolean init; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return getBinding(inflater, container).getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + initView(); + } + + protected void initView() { + } + + protected void initData() { + } + + private void onVisible() { + if (init) return; + initData(); + init = true; + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (isVisibleToUser) if (isResumed()) onVisible(); + } + + @Override + public void onResume() { + super.onResume(); + if (getUserVisibleHint()) onVisible(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseVodHolder.java b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseVodHolder.java new file mode 100644 index 00000000..c5d720bb --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseVodHolder.java @@ -0,0 +1,16 @@ +package com.fongmi.android.tv.ui.base; + +import android.view.View; + +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Vod; + +public abstract class BaseVodHolder extends Presenter.ViewHolder { + + public BaseVodHolder(View view) { + super(view); + } + + public abstract void initView(Vod item); +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/base/ViewType.java b/app/src/leanback/java/com/fongmi/android/tv/ui/base/ViewType.java new file mode 100644 index 00000000..0f691e2d --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/base/ViewType.java @@ -0,0 +1,8 @@ +package com.fongmi.android.tv.ui.base; + +public class ViewType { + + public static final int RECT = 0; + public static final int OVAL = 1; + public static final int LIST = 2; +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomEditText.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomEditText.java new file mode 100644 index 00000000..12665fbb --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomEditText.java @@ -0,0 +1,38 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatEditText; + +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomEditText extends AppCompatEditText { + + public CustomEditText(@NonNull Context context) { + super(context); + } + + public CustomEditText(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + private View focusSearch(KeyEvent event) { + if (KeyUtil.isUpKey(event)) return getParent().focusSearch(this, FOCUS_UP); + if (KeyUtil.isDownKey(event)) return getParent().focusSearch(this, FOCUS_DOWN); + if (KeyUtil.isLeftKey(event) && getSelectionStart() == 0) return getParent().focusSearch(this, FOCUS_LEFT); + if (KeyUtil.isRightKey(event) && getSelectionStart() == getText().length()) return getParent().focusSearch(this, FOCUS_RIGHT); + return null; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + View v = focusSearch(event); + if (v != null) return v.requestFocus(); + return super.onKeyDown(keyCode, event); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomHorizontalGridView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomHorizontalGridView.java new file mode 100644 index 00000000..747a228d --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomHorizontalGridView.java @@ -0,0 +1,100 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.FocusFinder; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.animation.Animation; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.HorizontalGridView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; + +public class CustomHorizontalGridView extends HorizontalGridView { + + private Animation shake; + + public CustomHorizontalGridView(@NonNull Context context) { + super(context); + } + + public CustomHorizontalGridView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public CustomHorizontalGridView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void initAttributes(@NonNull Context context, @Nullable AttributeSet attrs) { + super.initAttributes(context, attrs); + this.shake = isInEditMode() ? null : ResUtil.getAnim(R.anim.shake); + } + + @Override + public View focusSearch(View focused, int direction) { + if (focused != null) { + View found = FocusFinder.getInstance().findNextFocus(this, focused, direction); + if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) { + if ((found == null || found.getId() != R.id.text) && getScrollState() == SCROLL_STATE_IDLE) { + focused.clearAnimation(); + focused.startAnimation(shake); + return null; + } + } + } + return super.focusSearch(focused, direction); + } + + @Override + public boolean dispatchKeyEvent(@NonNull KeyEvent event) { + return super.dispatchKeyEvent(event) || executeKeyEvent(event); + } + + public boolean executeKeyEvent(@NonNull KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) return arrowScroll(FOCUS_LEFT); + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) return arrowScroll(FOCUS_RIGHT); + return false; + } + + public boolean arrowScroll(int direction) { + View currentFocused = findFocus(); + if (currentFocused == this) { + currentFocused = null; + } else if (currentFocused != null) { + boolean isChild = false; + for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; parent = parent.getParent()) { + if (parent == this) { + isChild = true; + break; + } + } + if (!isChild) { + currentFocused = null; + } + } + boolean handled = false; + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); + if (nextFocused == null || nextFocused == currentFocused) { + if (direction == FOCUS_LEFT || direction == FOCUS_RIGHT) { + shake(currentFocused); + handled = true; + } + } + return handled; + } + + private void shake(View currentFocused) { + if (currentFocused != null && getScrollState() == SCROLL_STATE_IDLE) { + currentFocused.clearAnimation(); + currentFocused.startAnimation(shake); + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownCast.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownCast.java new file mode 100644 index 00000000..f4514298 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownCast.java @@ -0,0 +1,113 @@ +package com.fongmi.android.tv.ui.custom; + +import android.app.Activity; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomKeyDownCast extends GestureDetector.SimpleOnGestureListener { + + private final GestureDetector detector; + private final Listener listener; + private boolean changeSpeed; + private long holdTime; + + public static CustomKeyDownCast create(Activity activity) { + return new CustomKeyDownCast(activity); + } + + private CustomKeyDownCast(Activity activity) { + this.detector = new GestureDetector(activity, this); + this.listener = (Listener) activity; + } + + public boolean onTouchEvent(MotionEvent e) { + return detector.onTouchEvent(e); + } + + public boolean hasEvent(KeyEvent event) { + return KeyUtil.isEnterKey(event) || KeyUtil.isUpKey(event) || KeyUtil.isDownKey(event) || KeyUtil.isLeftKey(event) || KeyUtil.isRightKey(event); + } + + public boolean onKeyDown(KeyEvent event) { + check(event); + return true; + } + + private void check(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isLeftKey(event)) { + listener.onSeeking(subTime()); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isRightKey(event)) { + listener.onSeeking(addTime()); + } else if (event.getAction() == KeyEvent.ACTION_UP && (KeyUtil.isLeftKey(event) || KeyUtil.isRightKey(event))) { + App.post(() -> listener.onSeekTo(holdTime), 250); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isUpKey(event)) { + if (changeSpeed) listener.onSpeedEnd(); + else listener.onKeyUp(); + changeSpeed = false; + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isDownKey(event)) { + listener.onKeyDown(); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isEnterKey(event)) { + listener.onKeyCenter(); + } else if (event.isLongPress() && KeyUtil.isUpKey(event)) { + listener.onSpeedUp(); + changeSpeed = true; + } + } + + @Override + public boolean onDown(@NonNull MotionEvent e) { + return true; + } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + listener.onDoubleTap(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(@NonNull MotionEvent e) { + listener.onSingleTap(); + return true; + } + + private long addTime() { + return holdTime = holdTime + Constant.INTERVAL_SEEK; + } + + private long subTime() { + return holdTime = holdTime - Constant.INTERVAL_SEEK; + } + + public void resetTime() { + holdTime = 0; + } + + public interface Listener { + + void onSeeking(long time); + + void onSeekTo(long time); + + void onSpeedUp(); + + void onSpeedEnd(); + + void onKeyUp(); + + void onKeyDown(); + + void onKeyCenter(); + + void onSingleTap(); + + void onDoubleTap(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownLive.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownLive.java new file mode 100644 index 00000000..1caab46c --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownLive.java @@ -0,0 +1,138 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.ResUtil; + +public class CustomKeyDownLive extends GestureDetector.SimpleOnGestureListener { + + private final GestureDetector detector; + private final StringBuilder text; + private final Listener listener; + private long holdTime; + + private final Runnable runnable = new Runnable() { + @Override + public void run() { + listener.onFind(text.toString()); + text.setLength(0); + } + }; + + public static CustomKeyDownLive create(Context context) { + return new CustomKeyDownLive(context); + } + + private CustomKeyDownLive(Context context) { + this.text = new StringBuilder(); + this.listener = (Listener) context; + this.detector = new GestureDetector(context, this); + } + + public boolean onTouchEvent(MotionEvent e) { + return detector.onTouchEvent(e); + } + + public boolean hasEvent(KeyEvent event) { + return KeyUtil.isEnterKey(event) || KeyUtil.isUpKey(event) || KeyUtil.isDownKey(event) || KeyUtil.isLeftKey(event) || KeyUtil.isRightKey(event) || KeyUtil.isDigitKey(event) || KeyUtil.isMenuKey(event) || event.isLongPress(); + } + + public void onKeyDown(KeyEvent event) { + if (listener.dispatch(true)) check(event); + } + + private void check(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isLeftKey(event)) { + listener.onSeeking(subTime()); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isRightKey(event)) { + listener.onSeeking(addTime()); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isUpKey(event)) { + listener.onKeyUp(); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isDownKey(event)) { + listener.onKeyDown(); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isLeftKey(event)) { + listener.onKeyLeft(holdTime); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isRightKey(event)) { + listener.onKeyRight(holdTime); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isDigitKey(event)) { + onKeyDown(event.getKeyCode()); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isEnterKey(event)) { + listener.onKeyCenter(); + } else if (KeyUtil.isMenuKey(event) || event.isLongPress() && KeyUtil.isEnterKey(event)) { + listener.onMenu(); + } + } + + private void onKeyDown(int keyCode) { + if (text.length() >= 4) return; + text.append(getNumber(keyCode)); + listener.onShow(text.toString()); + App.post(runnable, 2000); + } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + if (listener.dispatch(false)) listener.onDoubleTap(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(@NonNull MotionEvent e) { + if (!listener.dispatch(false)) return true; + int half = ResUtil.getScreenWidth() / 2; + if (e.getX() > half) listener.onDoubleTap(); + else listener.onSingleTap(); + return true; + } + + private int getNumber(int keyCode) { + return keyCode >= 144 ? keyCode - 144 : keyCode - 7; + } + + private long addTime() { + return holdTime = holdTime + Constant.INTERVAL_SEEK; + } + + private long subTime() { + return holdTime = holdTime - Constant.INTERVAL_SEEK; + } + + public void resetTime() { + holdTime = 0; + } + + public interface Listener { + + boolean dispatch(boolean check); + + void onShow(String number); + + void onFind(String number); + + void onSeeking(long time); + + void onKeyUp(); + + void onKeyDown(); + + void onKeyLeft(long time); + + void onKeyRight(long time); + + void onKeyCenter(); + + void onMenu(); + + void onSingleTap(); + + void onDoubleTap(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownVod.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownVod.java new file mode 100644 index 00000000..fa53df34 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyDownVod.java @@ -0,0 +1,114 @@ +package com.fongmi.android.tv.ui.custom; + +import android.app.Activity; +import android.view.GestureDetector; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomKeyDownVod extends GestureDetector.SimpleOnGestureListener { + + private final GestureDetector detector; + private final Listener listener; + private boolean changeSpeed; + private boolean full; + private long holdTime; + + public static CustomKeyDownVod create(Activity activity) { + return new CustomKeyDownVod(activity); + } + + private CustomKeyDownVod(Activity activity) { + this.detector = new GestureDetector(activity, this); + this.listener = (Listener) activity; + } + + public boolean onTouchEvent(MotionEvent e) { + if (!full) return false; + return detector.onTouchEvent(e); + } + + public void setFull(boolean full) { + this.full = full; + } + + public boolean hasEvent(KeyEvent event) { + return KeyUtil.isEnterKey(event) || KeyUtil.isUpKey(event) || KeyUtil.isDownKey(event) || KeyUtil.isLeftKey(event) || KeyUtil.isRightKey(event); + } + + public boolean onKeyDown(KeyEvent event) { + check(event); + return true; + } + + private void check(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isLeftKey(event)) { + listener.onSeeking(subTime()); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isRightKey(event)) { + listener.onSeeking(addTime()); + } else if (event.getAction() == KeyEvent.ACTION_UP && (KeyUtil.isLeftKey(event) || KeyUtil.isRightKey(event))) { + App.post(() -> listener.onSeekTo(holdTime), 250); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isUpKey(event)) { + if (changeSpeed) listener.onSpeedEnd(); + else listener.onKeyUp(); + changeSpeed = false; + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isDownKey(event)) { + listener.onKeyDown(); + } else if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isEnterKey(event)) { + listener.onKeyCenter(); + } else if (event.isLongPress() && KeyUtil.isUpKey(event)) { + listener.onSpeedUp(); + changeSpeed = true; + } + } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + listener.onDoubleTap(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(@NonNull MotionEvent e) { + listener.onSingleTap(); + return true; + } + + private long addTime() { + return holdTime = holdTime + Constant.INTERVAL_SEEK; + } + + private long subTime() { + return holdTime = holdTime - Constant.INTERVAL_SEEK; + } + + public void resetTime() { + holdTime = 0; + } + + public interface Listener { + + void onSeeking(long time); + + void onSeekTo(long time); + + void onSpeedUp(); + + void onSpeedEnd(); + + void onKeyUp(); + + void onKeyDown(); + + void onKeyCenter(); + + void onSingleTap(); + + void onDoubleTap(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyboard.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyboard.java new file mode 100644 index 00000000..81089e9a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomKeyboard.java @@ -0,0 +1,89 @@ +package com.fongmi.android.tv.ui.custom; + +import android.annotation.SuppressLint; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.ActivitySearchBinding; +import com.fongmi.android.tv.ui.adapter.KeyboardAdapter; + +public class CustomKeyboard implements KeyboardAdapter.OnClickListener { + + private final ActivitySearchBinding binding; + private final Callback callback; + private KeyboardAdapter adapter; + + public static void init(Callback callback, ActivitySearchBinding binding) { + new CustomKeyboard(callback, binding).initView(); + } + + public CustomKeyboard(Callback callback, ActivitySearchBinding binding) { + this.callback = callback; + this.binding = binding; + } + + private void initView() { + binding.keyboard.setItemAnimator(null); + binding.keyboard.setHasFixedSize(false); + binding.keyboard.addItemDecoration(new SpaceItemDecoration(7, 8)); + binding.keyboard.setAdapter(adapter = new KeyboardAdapter(this)); + } + + @Override + public void onTextClick(String text) { + StringBuilder sb = new StringBuilder(binding.keyword.getText().toString()); + int cursor = binding.keyword.getSelectionStart(); + if (binding.keyword.length() > 19) return; + sb.insert(cursor, text); + binding.keyword.setText(sb.toString()); + binding.keyword.setSelection(cursor + 1); + } + + @Override + @SuppressLint("NonConstantResourceId") + public void onIconClick(int resId) { + StringBuilder sb = new StringBuilder(binding.keyword.getText().toString()); + int cursor = binding.keyword.getSelectionStart(); + switch (resId) { + case R.drawable.ic_setting_home: + callback.showDialog(); + break; + case R.drawable.ic_keyboard_remote: + callback.onRemote(); + break; + case R.drawable.ic_keyboard_search: + callback.onSearch(); + break; + case R.drawable.ic_keyboard_left: + binding.keyword.setSelection(--cursor < 0 ? 0 : cursor); + break; + case R.drawable.ic_keyboard_right: + binding.keyword.setSelection(++cursor > binding.keyword.length() ? binding.keyword.length() : cursor); + break; + case R.drawable.ic_keyboard_back: + if (cursor == 0) return; + sb.deleteCharAt(cursor - 1); + binding.keyword.setText(sb.toString()); + binding.keyword.setSelection(cursor - 1); + break; + case R.drawable.ic_keyboard: + adapter.toggle(); + break; + } + } + + @Override + public boolean onLongClick(int resId) { + if (resId != R.drawable.ic_keyboard_back) return false; + binding.keyword.setText(""); + return true; + } + + public interface Callback { + + void showDialog(); + + void onRemote(); + + void onSearch(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomLeftRightLayout.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomLeftRightLayout.java new file mode 100644 index 00000000..d3d05589 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomLeftRightLayout.java @@ -0,0 +1,59 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomLeftRightLayout extends LinearLayout { + + private LeftListener leftListener; + private RightListener rightListener; + + public CustomLeftRightLayout(@NonNull Context context) { + super(context); + } + + public CustomLeftRightLayout(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public void setLeftListener(LeftListener leftListener) { + this.leftListener = leftListener; + } + + public void setRightListener(RightListener rightListener) { + this.rightListener = rightListener; + } + + private boolean hasEvent(KeyEvent event) { + return event.getAction() == KeyEvent.ACTION_DOWN && ((leftListener != null && KeyUtil.isLeftKey(event)) || (rightListener != null && KeyUtil.isRightKey(event))); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (hasEvent(event)) return onKeyDown(event); + else return super.dispatchKeyEvent(event); + } + + private boolean onKeyDown(KeyEvent event) { + if (leftListener != null && KeyUtil.isLeftKey(event)) leftListener.onLeft(); + if (rightListener != null && KeyUtil.isRightKey(event)) rightListener.onRight(); + return true; + } + + public interface LeftListener { + + void onLeft(); + } + + public interface RightListener { + + void onRight(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomLiveListView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomLiveListView.java new file mode 100644 index 00000000..d270b951 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomLiveListView.java @@ -0,0 +1,66 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.VerticalGridView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomLiveListView extends VerticalGridView { + + private Callback listener; + + public CustomLiveListView(@NonNull Context context) { + super(context); + } + + public CustomLiveListView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public CustomLiveListView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setListener(Callback listener) { + this.listener = listener; + } + + private boolean onKeyDown() { + if (getSelectedPosition() != getAdapter().getItemCount() - 1) return false; + if (getId() == R.id.channel || getId() == R.id.epgData) setSelectedPosition(0); + else if (listener != null) listener.nextGroup(false); + return true; + } + + private boolean onKeyUp() { + if (getSelectedPosition() != 0) return false; + if (getId() == R.id.channel || getId() == R.id.epgData) setSelectedPosition(getAdapter().getItemCount()); + else if (listener != null) listener.prevGroup(false); + return true; + } + + @Override + public boolean dispatchKeyEvent(@NonNull KeyEvent event) { + if (getVisibility() == View.GONE || event.getAction() != KeyEvent.ACTION_DOWN) return super.dispatchKeyEvent(event); + if (getVisibility() == View.VISIBLE && listener != null) listener.setUITimer(); + if (KeyUtil.isDownKey(event)) return onKeyDown(); + if (KeyUtil.isUpKey(event)) return onKeyUp(); + return super.dispatchKeyEvent(event); + } + + public interface Callback { + + void setUITimer(); + + boolean nextGroup(boolean skip); + + boolean prevGroup(boolean skip); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomMic.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomMic.java new file mode 100644 index 00000000..3ffd1eeb --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomMic.java @@ -0,0 +1,107 @@ +package com.fongmi.android.tv.ui.custom; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.speech.RecognizerIntent; +import android.speech.SpeechRecognizer; +import android.util.AttributeSet; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatImageView; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.bassaer.library.MDColor; +import com.permissionx.guolindev.PermissionX; + +public class CustomMic extends AppCompatImageView { + + private SpeechRecognizer recognizer; + private FragmentActivity activity; + private boolean listen; + + public CustomMic(@NonNull Context context) { + super(context); + } + + public CustomMic(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + private boolean isListen() { + return listen; + } + + private void setListen(boolean listen) { + this.listen = listen; + } + + public void setListener(FragmentActivity activity, CustomTextListener listener) { + if (recognizer == null) recognizer = SpeechRecognizer.createSpeechRecognizer(activity); + this.recognizer.setRecognitionListener(listener); + this.activity = activity; + } + + private void checkPermission() { + if (PermissionX.isGranted(activity, Manifest.permission.RECORD_AUDIO)) { + start(); + } else { + PermissionX.init(activity).permissions(Manifest.permission.RECORD_AUDIO).request((allGranted, grantedList, deniedList) -> { + if (allGranted) start(); + }); + } + } + + private void startListening() { + try { + Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM); + recognizer.startListening(intent); + } catch (Exception ignored) { + } + } + + private void start() { + startListening(); + requestFocus(); + updateUI(true); + } + + public boolean stop() { + recognizer.stopListening(); + updateUI(false); + return true; + } + + private void updateUI(boolean listening) { + if (listening) { + setListen(true); + startAnimation(ResUtil.getAnim(R.anim.flicker)); + setColorFilter(MDColor.RED_500, PorterDuff.Mode.SRC_IN); + } else { + setListen(false); + clearAnimation(); + setColorFilter(MDColor.WHITE, PorterDuff.Mode.SRC_IN); + } + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + if (gainFocus) checkPermission(); + else stop(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (isListen() && KeyUtil.isBackKey(event)) return stop(); + else return super.dispatchKeyEvent(event); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomRowPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomRowPresenter.java new file mode 100644 index 00000000..9ef4b591 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomRowPresenter.java @@ -0,0 +1,43 @@ +package com.fongmi.android.tv.ui.custom; + +import android.annotation.SuppressLint; + +import androidx.leanback.widget.FocusHighlight; +import androidx.leanback.widget.HorizontalGridView; +import androidx.leanback.widget.ListRowPresenter; +import androidx.leanback.widget.RowPresenter; + +import com.fongmi.android.tv.utils.ResUtil; + +public class CustomRowPresenter extends ListRowPresenter { + + private final int spacing; + private final int strategy; + + public CustomRowPresenter(int spacing) { + this(spacing, FocusHighlight.ZOOM_FACTOR_SMALL); + } + + @SuppressLint("RestrictedApi") + public CustomRowPresenter(int spacing, int focusZoomFactor) { + this(spacing, focusZoomFactor, HorizontalGridView.FOCUS_SCROLL_ITEM); + } + + public CustomRowPresenter(int spacing, int focusZoomFactor, int strategy) { + super(focusZoomFactor); + this.spacing = spacing; + this.strategy = strategy; + setShadowEnabled(false); + setSelectEffectEnabled(false); + setKeepChildForeground(false); + } + + @Override + @SuppressLint("RestrictedApi") + protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) { + super.initializeRowViewHolder(holder); + ViewHolder vh = (ViewHolder) holder; + vh.getGridView().setFocusScrollStrategy(strategy); + vh.getGridView().setHorizontalSpacing(ResUtil.dp2px(spacing)); + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomScroller.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomScroller.java new file mode 100644 index 00000000..267be180 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomScroller.java @@ -0,0 +1,73 @@ +package com.fongmi.android.tv.ui.custom; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Result; + +public class CustomScroller extends RecyclerView.OnScrollListener { + + private final Callback callback; + private boolean loading; + private boolean enable; + private int page; + + public CustomScroller(Callback callback) { + this.callback = callback; + this.enable = true; + this.page = 1; + } + + @Override + public void onScrollStateChanged(@NonNull RecyclerView view, int newState) { + if (isDisable() || isLoading() || newState != RecyclerView.SCROLL_STATE_IDLE) return; + if (isBottom(view)) callback.onLoadMore(String.valueOf(++page)); + } + + private boolean isBottom(RecyclerView view) { + if (view == null || view.getLayoutManager() == null || view.getLayoutManager().getItemCount() == 0) return false; + View lastChild = view.getLayoutManager().getChildAt(view.getLayoutManager().getChildCount() - 1); + int lastPosition = view.getLayoutManager().getPosition(lastChild); + return lastPosition == view.getLayoutManager().getItemCount() - 1; + } + + public void reset() { + page = 1; + } + + public int addPage() { + return ++page; + } + + public boolean first() { + return page == 1; + } + + public boolean isLoading() { + return loading; + } + + public void setLoading(boolean loading) { + this.loading = loading; + } + + public boolean isDisable() { + return !enable; + } + + public void setEnable(int pageCount) { + this.enable = page < pageCount || pageCount == 0; + } + + public void endLoading(Result result) { + if (result.getList().isEmpty()) page--; + setEnable(result.getPageCount()); + setLoading(false); + } + + public interface Callback { + void onLoadMore(String page); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomSearchView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomSearchView.java new file mode 100644 index 00000000..4d9a79f1 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomSearchView.java @@ -0,0 +1,38 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.animation.Animation; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; + +public class CustomSearchView extends CustomEditText { + + private Animation flicker; + + public CustomSearchView(@NonNull Context context) { + super(context); + } + + public CustomSearchView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + flicker = ResUtil.getAnim(R.anim.flicker); + } + + @Override + public boolean isFocused() { + return true; + } + + @Override + protected void onFocusChanged(boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) { + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); + if (gainFocus) startAnimation(flicker); + else clearAnimation(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomSelector.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomSelector.java new file mode 100644 index 00000000..36ab0bb8 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomSelector.java @@ -0,0 +1,66 @@ +package com.fongmi.android.tv.ui.custom; + +import androidx.collection.ArrayMap; +import androidx.leanback.widget.ListRow; +import androidx.leanback.widget.Presenter; +import androidx.leanback.widget.PresenterSelector; + +import java.util.ArrayList; +import java.util.List; + +public class CustomSelector extends PresenterSelector { + + private final List mPresenters; + private final ArrayMap, Presenter> mSingleMap; + private final ArrayMap, ArrayMap, Presenter>> mClassMap; + + public CustomSelector() { + super(); + mPresenters = new ArrayList<>(); + mSingleMap = new ArrayMap<>(); + mClassMap = new ArrayMap<>(); + } + + public void addPresenter(Class cls, Presenter presenter) { + mSingleMap.put(cls, presenter); + if (!mPresenters.contains(presenter)) { + mPresenters.add(presenter); + } + } + + public void addPresenter(Class cls, Presenter presenter, Class childType) { + ArrayMap, Presenter> classPresenterArrayMap = mClassMap.get(cls); + if (classPresenterArrayMap == null) classPresenterArrayMap = new ArrayMap<>(); + classPresenterArrayMap.put(childType, presenter); + mClassMap.put(cls, classPresenterArrayMap); + if (!mPresenters.contains(presenter)) { + mPresenters.add(presenter); + } + } + + @Override + public Presenter getPresenter(Object item) { + Class cls = item.getClass(); + Presenter presenter; + presenter = mSingleMap.get(cls); + if (presenter != null) return presenter; + ArrayMap, Presenter> presenters = mClassMap.get(cls); + assert presenters != null; + if (presenters.size() == 1) return presenters.valueAt(0); + if (item instanceof ListRow) { + ListRow listRow = (ListRow) item; + Presenter childPresenter = listRow.getAdapter().getPresenter(listRow); + Class childCls = childPresenter.getClass(); + do { + presenter = presenters.get(childCls); + childCls = childCls.getSuperclass(); + } while (presenter == null && childCls != null); + } + return presenter; + } + + @Override + public Presenter[] getPresenters() { + return mPresenters.toArray(new Presenter[]{}); + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTextListener.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTextListener.java new file mode 100644 index 00000000..b9bf83dc --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTextListener.java @@ -0,0 +1,69 @@ +package com.fongmi.android.tv.ui.custom; + +import android.os.Bundle; +import android.speech.RecognitionListener; +import android.speech.SpeechRecognizer; +import android.text.Editable; +import android.text.TextWatcher; + +import java.util.List; + +public abstract class CustomTextListener implements TextWatcher, RecognitionListener { + + @Override + public void onReadyForSpeech(Bundle params) { + } + + @Override + public void onBeginningOfSpeech() { + } + + @Override + public void onRmsChanged(float rmsdB) { + } + + @Override + public void onBufferReceived(byte[] buffer) { + } + + @Override + public void onEndOfSpeech() { + } + + @Override + public void onError(int error) { + } + + @Override + public void onResults(Bundle results) { + if (results == null) return; + StringBuilder sb = new StringBuilder(); + List texts = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); + for (String text : texts) sb.append(text).append("\n"); + String result = sb.toString().trim(); + if (result.length() > 0) onResults(result); + } + + @Override + public void onPartialResults(Bundle partialResults) { + } + + @Override + public void onEvent(int eventType, Bundle params) { + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + } + + public void onResults(String result) { + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTitleView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTitleView.java new file mode 100644 index 00000000..95cffae2 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTitleView.java @@ -0,0 +1,100 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.animation.Animation; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.utils.KeyUtil; +import com.fongmi.android.tv.utils.ResUtil; + +import java.util.ArrayList; +import java.util.List; + +public class CustomTitleView extends AppCompatTextView { + + private Listener listener; + private Animation flicker; + private boolean coolDown; + + public CustomTitleView(@NonNull Context context) { + super(context); + } + + public CustomTitleView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + flicker = ResUtil.getAnim(R.anim.flicker); + } + + public void setListener(Listener listener) { + this.listener = listener; + setOnClickListener(v -> listener.showDialog()); + } + + private boolean hasEvent(KeyEvent event) { + return !getSites().isEmpty() && (KeyUtil.isEnterKey(event) || KeyUtil.isLeftKey(event) || KeyUtil.isRightKey(event) || (KeyUtil.isUpKey(event) && !coolDown)); + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + if (focused) startAnimation(flicker); + else clearAnimation(); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (hasEvent(event)) return onKeyDown(event); + else return super.dispatchKeyEvent(event); + } + + private boolean onKeyDown(KeyEvent event) { + if (event.getAction() == KeyEvent.ACTION_UP && KeyUtil.isEnterKey(event)) { + listener.showDialog(); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isLeftKey(event)) { + listener.setSite(getSite(true)); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isRightKey(event)) { + listener.setSite(getSite(false)); + } else if (event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isUpKey(event)) { + onKeyUp(); + } + return true; + } + + private void onKeyUp() { + App.post(() -> coolDown = false, 3000); + listener.onRefresh(); + coolDown = true; + } + + private Site getSite(boolean next) { + List items = getSites(); + int position = VodConfig.getHomeIndex(); + if (next) position = position > 0 ? --position : items.size() - 1; + else position = position < items.size() - 1 ? ++position : 0; + return items.get(position); + } + + private List getSites() { + List items = new ArrayList<>(); + for (Site site : VodConfig.get().getSites()) if (!site.isHide()) items.add(site); + return items; + } + + public interface Listener extends SiteCallback { + + void showDialog(); + + void onRefresh(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTypeView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTypeView.java new file mode 100644 index 00000000..9d04b8fb --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomTypeView.java @@ -0,0 +1,60 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomTypeView extends AppCompatTextView { + + private Listener listener; + private boolean coolDown; + + public CustomTypeView(@NonNull Context context) { + super(context); + } + + public CustomTypeView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public void setListener(Listener listener) { + this.listener = listener; + } + + private boolean hasEvent(KeyEvent event) { + return !coolDown && event.getAction() == KeyEvent.ACTION_DOWN && KeyUtil.isUpKey(event); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (hasEvent(event)) return onKeyDown(); + else return super.dispatchKeyEvent(event); + } + + private boolean onKeyDown() { + App.post(() -> coolDown = false, 3000); + listener.onRefresh(); + coolDown = true; + return true; + } + + @Override + protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) { + super.onFocusChanged(focused, direction, previouslyFocusedRect); + App.post(() -> coolDown = false, 500); + if (focused) coolDown = true; + } + + public interface Listener { + + void onRefresh(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomUpDownView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomUpDownView.java new file mode 100644 index 00000000..522f717e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomUpDownView.java @@ -0,0 +1,59 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.AppCompatTextView; + +import com.fongmi.android.tv.utils.KeyUtil; + +public class CustomUpDownView extends AppCompatTextView { + + private UpListener upListener; + private DownListener downListener; + + public CustomUpDownView(@NonNull Context context) { + super(context); + } + + public CustomUpDownView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public void setUpListener(UpListener upListener) { + this.upListener = upListener; + } + + public void setDownListener(DownListener downListener) { + this.downListener = downListener; + } + + private boolean hasEvent(KeyEvent event) { + return event.getAction() == KeyEvent.ACTION_DOWN && ((upListener != null && KeyUtil.isUpKey(event)) || (downListener != null && KeyUtil.isDownKey(event))); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (hasEvent(event)) return onKeyDown(event); + else return super.dispatchKeyEvent(event); + } + + private boolean onKeyDown(KeyEvent event) { + if (upListener != null && KeyUtil.isUpKey(event)) upListener.onUp(); + if (downListener != null && KeyUtil.isDownKey(event)) downListener.onDown(); + return true; + } + + public interface UpListener { + + void onUp(); + } + + public interface DownListener { + + void onDown(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomVerticalGridView.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomVerticalGridView.java new file mode 100644 index 00000000..577ab0c1 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomVerticalGridView.java @@ -0,0 +1,83 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.KeyEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.OnChildViewHolderSelectedListener; +import androidx.leanback.widget.VerticalGridView; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; + +import java.util.Arrays; +import java.util.List; + +public class CustomVerticalGridView extends VerticalGridView { + + private List views; + private boolean pressDown; + private boolean pressUp; + private boolean moveTop; + + public CustomVerticalGridView(@NonNull Context context) { + this(context, null); + } + + public CustomVerticalGridView(@NonNull Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public CustomVerticalGridView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + setMoveTop(true); + } + + @Override + protected void initAttributes(@NonNull Context context, @Nullable AttributeSet attrs) { + super.initAttributes(context, attrs); + setOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { + @Override + public void onChildViewHolderSelected(@NonNull RecyclerView parent, @Nullable ViewHolder child, int position, int subposition) { + if (pressDown && position == 1) hideHeader(); + if (pressUp && position == 0) showHeader(); + } + }); + } + + public void setHeader(View... views) { + this.views = Arrays.asList(views); + } + + public void setMoveTop(boolean moveTop) { + this.moveTop = moveTop; + } + + public void hideHeader() { + if (views != null) for (View view : views) view.setVisibility(View.GONE); + } + + public void showHeader() { + if (views != null) for (View view : views) view.setVisibility(View.VISIBLE); + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getAction() != KeyEvent.ACTION_DOWN) return super.dispatchKeyEvent(event); + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) return moveTop && moveToTop(); + pressUp = event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP; + pressDown = event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN; + return super.dispatchKeyEvent(event); + } + + public boolean moveToTop() { + if (views == null || getSelectedPosition() == 0 || getAdapter() == null || getAdapter().getItemCount() == 0) return false; + for (View view : views) if (view.getId() == R.id.recycler) view.requestFocus(); + scrollToPosition(0); + showHeader(); + return true; + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomViewPager.java b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomViewPager.java new file mode 100644 index 00000000..d14b586c --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/custom/CustomViewPager.java @@ -0,0 +1,158 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.FocusFinder; +import android.view.KeyEvent; +import android.view.SoundEffectConstants; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.animation.Animation; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewpager.widget.ViewPager; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; + +public class CustomViewPager extends ViewPager { + + private Animation shake; + private Rect rect; + + public CustomViewPager(@NonNull Context context) { + super(context); + } + + public CustomViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + private void init() { + this.rect = new Rect(); + this.shake = ResUtil.getAnim(R.anim.shake); + } + + @Override + public void setCurrentItem(int item) { + super.setCurrentItem(item, false); + } + + @Override + public boolean executeKeyEvent(@NonNull KeyEvent event) { + if (findFocus() instanceof TextView) return false; + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) return arrowScroll(FOCUS_LEFT); + if (event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) return arrowScroll(FOCUS_RIGHT); + return false; + } + + @Override + public boolean arrowScroll(int direction) { + boolean handled = false; + View currentFocused = findFocus(); + if (currentFocused == this) { + currentFocused = null; + } else if (currentFocused != null) { + boolean isChild = false; + for (ViewParent parent = currentFocused.getParent(); parent instanceof ViewGroup; parent = parent.getParent()) { + if (parent == this) { + isChild = true; + break; + } + } + if (!isChild) { + currentFocused = null; + } + } + View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); + if (nextFocused != null && nextFocused != currentFocused) { + if (direction == View.FOCUS_LEFT) { + int nextLeft = getChildRectInPagerCoordinates(rect, nextFocused).left; + int currLeft = getChildRectInPagerCoordinates(rect, currentFocused).left; + if (currentFocused != null && nextLeft >= currLeft) { + handled = pageLeft(); + } else { + handled = nextFocused.requestFocus(); + } + } else if (direction == View.FOCUS_RIGHT) { + int nextLeft = getChildRectInPagerCoordinates(rect, nextFocused).left; + int currLeft = getChildRectInPagerCoordinates(rect, currentFocused).left; + if (currentFocused != null && nextLeft <= currLeft) { + handled = pageRight(); + } else { + handled = nextFocused.requestFocus(); + } + } + } else if (direction == FOCUS_LEFT) { + if (getCurrentItem() == 0) { + shake(currentFocused); + handled = true; + } else { + handled = pageLeft(); + } + } else if (direction == FOCUS_RIGHT) { + if (getAdapter() != null && getCurrentItem() == getAdapter().getCount() - 1) { + shake(currentFocused); + handled = true; + } else { + handled = pageRight(); + } + } + if (handled) { + playSoundEffect(SoundEffectConstants.getContantForFocusDirection(direction)); + } + return handled; + } + + private Rect getChildRectInPagerCoordinates(Rect outRect, View child) { + if (outRect == null) { + outRect = new Rect(); + } + if (child == null) { + outRect.set(0, 0, 0, 0); + return outRect; + } + outRect.left = child.getLeft(); + outRect.right = child.getRight(); + outRect.top = child.getTop(); + outRect.bottom = child.getBottom(); + ViewParent parent = child.getParent(); + while (parent instanceof ViewGroup && parent != this) { + ViewGroup group = (ViewGroup) parent; + outRect.left += group.getLeft(); + outRect.right += group.getRight(); + outRect.top += group.getTop(); + outRect.bottom += group.getBottom(); + parent = group.getParent(); + } + return outRect; + } + + private boolean pageLeft() { + if (getCurrentItem() > 0) { + setCurrentItem(getCurrentItem() - 1, false); + return true; + } + return false; + } + + private boolean pageRight() { + if (getAdapter() != null && getCurrentItem() < getAdapter().getCount() - 1) { + setCurrentItem(getCurrentItem() + 1, false); + return true; + } + return false; + } + + private void shake(View currentFocused) { + if (currentFocused != null) { + currentFocused.clearAnimation(); + currentFocused.startAnimation(shake); + } + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/BufferDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/BufferDialog.java new file mode 100644 index 00000000..4bcd162a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/BufferDialog.java @@ -0,0 +1,53 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogBufferBinding; +import com.fongmi.android.tv.impl.BufferCallback; +import com.fongmi.android.tv.utils.KeyUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class BufferDialog { + + private final DialogBufferBinding binding; + private final BufferCallback callback; + private final AlertDialog dialog; + + public static BufferDialog create(FragmentActivity activity) { + return new BufferDialog(activity); + } + + public BufferDialog(FragmentActivity activity) { + this.callback = (BufferCallback) activity; + this.binding = DialogBufferBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); + dialog.show(); + } + + private void initView() { + binding.slider.setValue(Setting.getBuffer()); + } + + private void initEvent() { + binding.slider.addOnChangeListener((slider, value, fromUser) -> callback.setBuffer((int) value)); + binding.slider.setOnKeyListener((view, keyCode, event) -> { + boolean enter = KeyUtil.isEnterKey(event); + if (enter) dialog.dismiss(); + return enter; + }); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java new file mode 100644 index 00000000..fb45da27 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java @@ -0,0 +1,166 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.DialogConfigBinding; +import com.fongmi.android.tv.event.ServerEvent; +import com.fongmi.android.tv.impl.ConfigCallback; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.QRCode; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class ConfigDialog implements DialogInterface.OnDismissListener { + + private final DialogConfigBinding binding; + private final FragmentActivity activity; + private final ConfigCallback callback; + private final AlertDialog dialog; + private boolean append; + private boolean edit; + private String url; + private int type; + + public static ConfigDialog create(FragmentActivity activity) { + return new ConfigDialog(activity); + } + + public ConfigDialog type(int type) { + this.type = type; + return this; + } + + public ConfigDialog edit() { + this.edit = true; + return this; + } + + public ConfigDialog(FragmentActivity activity) { + this.activity = activity; + this.callback = (ConfigCallback) activity; + this.binding = DialogConfigBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.append = true; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.55f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.setOnDismissListener(this); + dialog.show(); + } + + private void initView() { + binding.text.setText(url = getUrl()); + binding.text.setSelection(TextUtils.isEmpty(url) ? 0 : url.length()); + binding.positive.setText(edit ? R.string.dialog_edit : R.string.dialog_positive); + binding.code.setImageBitmap(QRCode.getBitmap(Server.get().getAddress(3), 200, 0)); + binding.info.setText(ResUtil.getString(R.string.push_info, Server.get().getAddress()).replace(",", "\n")); + } + + private void initEvent() { + EventBus.getDefault().register(this); + binding.choose.setOnClickListener(this::onChoose); + binding.positive.setOnClickListener(this::onPositive); + binding.negative.setOnClickListener(this::onNegative); + binding.text.addTextChangedListener(new CustomTextListener() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + detect(s.toString()); + } + }); + binding.text.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) binding.positive.performClick(); + return true; + }); + } + + private String getUrl() { + switch (type) { + case 0: + return VodConfig.getUrl(); + case 1: + return LiveConfig.getUrl(); + case 2: + return WallConfig.getUrl(); + default: + return ""; + } + } + + private void onChoose(View view) { + FileChooser.from(activity).show(); + dialog.dismiss(); + } + + private void detect(String s) { + if (append && "h".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ttp://"); + } else if (append && "f".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ile://"); + } else if (append && "a".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ssets://"); + } else if (s.length() > 1) { + append = false; + } else if (s.length() == 0) { + append = true; + } + } + + private void onPositive(View view) { + String name = binding.name.getText().toString().trim(); + String text = binding.text.getText().toString().trim(); + if (edit) Config.find(url, type).url(text).update(); + if (text.isEmpty()) Config.delete(url, type); + if (name.isEmpty()) callback.setConfig(Config.find(text, type)); + else callback.setConfig(Config.find(text, name, type)); + dialog.dismiss(); + } + + private void onNegative(View view) { + dialog.dismiss(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onServerEvent(ServerEvent event) { + if (event.getType() != ServerEvent.Type.SETTING) return; + binding.name.setText(event.getName()); + binding.text.setText(event.getText()); + binding.text.setSelection(binding.text.getText().length()); + } + + @Override + public void onDismiss(DialogInterface dialogInterface) { + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/DescDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/DescDialog.java new file mode 100644 index 00000000..d357f27a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/DescDialog.java @@ -0,0 +1,33 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.databinding.DialogDescBinding; +import com.fongmi.android.tv.ui.custom.CustomMovement; +import com.github.bassaer.library.MDColor; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class DescDialog { + + public static void show(Activity activity, CharSequence desc) { + new DescDialog().create(activity, desc); + } + + public void create(Activity activity, CharSequence desc) { + DialogDescBinding binding = DialogDescBinding.inflate(LayoutInflater.from(activity)); + AlertDialog dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + dialog.getWindow().setDimAmount(0); + initView(binding.text, desc); + dialog.show(); + } + + private void initView(TextView view, CharSequence desc) { + view.setText(desc, TextView.BufferType.SPANNABLE); + view.setLinkTextColor(MDColor.BLUE_500); + CustomMovement.bind(view); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/DohDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/DohDialog.java new file mode 100644 index 00000000..c8d7b5a5 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/DohDialog.java @@ -0,0 +1,66 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.databinding.DialogDohBinding; +import com.fongmi.android.tv.impl.DohCallback; +import com.fongmi.android.tv.ui.adapter.DohAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.bean.Doh; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class DohDialog implements DohAdapter.OnClickListener { + + private final DialogDohBinding binding; + private final DohCallback callback; + private final AlertDialog dialog; + private final DohAdapter adapter; + + public static DohDialog create(Activity activity) { + return new DohDialog(activity); + } + + public DohDialog index(int index) { + adapter.setSelect(index); + return this; + } + + public DohDialog(Activity activity) { + this.callback = (DohCallback) activity; + this.binding = DialogDohBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.adapter = new DohAdapter(this); + } + + public void show() { + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setAdapter(adapter); + binding.recycler.setHasFixedSize(true); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + binding.recycler.post(() -> binding.recycler.scrollToPosition(adapter.getSelect())); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.4f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onItemClick(Doh item) { + callback.setDoh(item); + dialog.dismiss(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java new file mode 100644 index 00000000..6be3d5b5 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java @@ -0,0 +1,72 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.DialogHistoryBinding; +import com.fongmi.android.tv.impl.ConfigCallback; +import com.fongmi.android.tv.ui.adapter.ConfigAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class HistoryDialog implements ConfigAdapter.OnClickListener { + + private final DialogHistoryBinding binding; + private final ConfigCallback callback; + private final ConfigAdapter adapter; + private final AlertDialog dialog; + private int type; + + public static HistoryDialog create(Activity activity) { + return new HistoryDialog(activity); + } + + public HistoryDialog type(int type) { + this.type = type; + return this; + } + + public HistoryDialog(Activity activity) { + this.callback = (ConfigCallback) activity; + this.binding = DialogHistoryBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.adapter = new ConfigAdapter(this); + } + + public void show() { + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setItemAnimator(null); + binding.recycler.setHasFixedSize(false); + binding.recycler.setAdapter(adapter.addAll(type)); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.4f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onTextClick(Config item) { + callback.setConfig(item); + dialog.dismiss(); + } + + @Override + public void onDeleteClick(Config item) { + if (adapter.remove(item) == 0) dialog.dismiss(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/LiveDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/LiveDialog.java new file mode 100644 index 00000000..506795df --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/LiveDialog.java @@ -0,0 +1,96 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.databinding.DialogLiveBinding; +import com.fongmi.android.tv.impl.LiveCallback; +import com.fongmi.android.tv.ui.adapter.LiveAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class LiveDialog implements LiveAdapter.OnClickListener { + + private final DialogLiveBinding binding; + private final LiveCallback callback; + private final LiveAdapter adapter; + private final AlertDialog dialog; + + public static LiveDialog create(Activity activity) { + return new LiveDialog(activity); + } + + private LiveDialog(Activity activity) { + this.adapter = new LiveAdapter(this); + this.callback = (LiveCallback) activity; + this.binding = DialogLiveBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + } + + public LiveDialog action() { + adapter.setAction(true); + return this; + } + + public void show() { + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setAdapter(adapter); + binding.recycler.setHasFixedSize(true); + binding.recycler.setItemAnimator(null); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + binding.recycler.post(() -> binding.recycler.scrollToPosition(LiveConfig.getHomeIndex())); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.4f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onItemClick(Live item) { + callback.setLive(item); + dialog.dismiss(); + } + + @Override + public void onBootClick(int position, Live item) { + item.boot(!item.isBoot()).save(); + adapter.notifyItemChanged(position); + } + + @Override + public void onPassClick(int position, Live item) { + item.pass(!item.isPass()).save(); + adapter.notifyItemChanged(position); + } + + @Override + public boolean onBootLongClick(Live item) { + boolean result = !item.isBoot(); + for (Live live : LiveConfig.get().getLives()) live.boot(result).save(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + return true; + } + + @Override + public boolean onPassLongClick(Live item) { + boolean result = !item.isPass(); + for (Live live : LiveConfig.get().getLives()) live.pass(result).save(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + return true; + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/PassDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/PassDialog.java new file mode 100644 index 00000000..0c7f8fb2 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/PassDialog.java @@ -0,0 +1,63 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.DialogPassBinding; +import com.fongmi.android.tv.impl.PassCallback; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +public class PassDialog extends BaseDialog { + + private DialogPassBinding binding; + private PassCallback callback; + + public static PassDialog create() { + return new PassDialog(); + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + this.callback = (PassCallback) activity; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogPassBinding.inflate(inflater, container, false); + } + + @Override + protected void initEvent() { + binding.positive.setOnClickListener(this::onPass); + binding.pass.setOnEditorActionListener(this::onDone); + } + + private void onPass(View view) { + String pass = binding.pass.getText().toString().trim(); + if (pass.length() > 0) callback.setPass(pass); + dismiss(); + } + + private boolean onDone(TextView view, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) binding.positive.performClick(); + return true; + } + + @Override + public void onResume() { + super.onResume(); + getDialog().getWindow().setLayout(ResUtil.dp2px(250), -1); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/ProxyDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/ProxyDialog.java new file mode 100644 index 00000000..3299a706 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/ProxyDialog.java @@ -0,0 +1,122 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogProxyBinding; +import com.fongmi.android.tv.event.ServerEvent; +import com.fongmi.android.tv.impl.ProxyCallback; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.fongmi.android.tv.utils.QRCode; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class ProxyDialog implements DialogInterface.OnDismissListener { + + private final DialogProxyBinding binding; + private final ProxyCallback callback; + private final AlertDialog dialog; + private boolean append; + + public static ProxyDialog create(FragmentActivity activity) { + return new ProxyDialog(activity); + } + + public ProxyDialog(FragmentActivity activity) { + this.callback = (ProxyCallback) activity; + this.binding = DialogProxyBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.append = true; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.55f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.setOnDismissListener(this); + dialog.show(); + } + + private void initView() { + String text = Setting.getProxy(); + binding.text.setText(text); + binding.text.setSelection(TextUtils.isEmpty(text) ? 0 : text.length()); + binding.code.setImageBitmap(QRCode.getBitmap(Server.get().getAddress(3), 200, 0)); + binding.info.setText(ResUtil.getString(R.string.push_info, Server.get().getAddress()).replace(",", "\n")); + } + + private void initEvent() { + EventBus.getDefault().register(this); + binding.positive.setOnClickListener(this::onPositive); + binding.negative.setOnClickListener(this::onNegative); + binding.text.addTextChangedListener(new CustomTextListener() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + detect(s.toString()); + } + }); + binding.text.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) binding.positive.performClick(); + return true; + }); + } + + private void detect(String s) { + if (append && "h".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ttp://"); + } else if (append && "s".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ocks5://"); + } else if (append && s.length() == 1) { + append = false; + binding.text.getText().insert(0, "socks5://"); + } else if (s.length() > 1) { + append = false; + } else if (s.length() == 0) { + append = true; + } + } + + private void onPositive(View view) { + callback.setProxy(binding.text.getText().toString().trim()); + dialog.dismiss(); + } + + private void onNegative(View view) { + dialog.dismiss(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onServerEvent(ServerEvent event) { + if (event.getType() != ServerEvent.Type.SETTING) return; + binding.text.setText(event.getText()); + binding.positive.performClick(); + } + + @Override + public void onDismiss(DialogInterface dialogInterface) { + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/RestoreDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/RestoreDialog.java new file mode 100644 index 00000000..3f73775a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/RestoreDialog.java @@ -0,0 +1,68 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.databinding.DialogRestoreBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.adapter.RestoreAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import java.io.File; + +public class RestoreDialog implements RestoreAdapter.OnClickListener { + + private final DialogRestoreBinding binding; + private final RestoreAdapter adapter; + private final AlertDialog dialog; + private Callback callback; + + public static RestoreDialog create(Activity activity) { + return new RestoreDialog(activity); + } + + public RestoreDialog(Activity activity) { + this.binding = DialogRestoreBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.adapter = new RestoreAdapter(this); + } + + public void show(Callback callback) { + this.callback = callback; + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setAdapter(adapter); + binding.recycler.setItemAnimator(null); + binding.recycler.setHasFixedSize(false); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.4f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onItemClick(File item) { + AppDatabase.restore(item, callback); + dialog.dismiss(); + } + + @Override + public void onDeleteClick(File item) { + if (adapter.remove(item) == 0) dialog.dismiss(); + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java new file mode 100644 index 00000000..45a38866 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java @@ -0,0 +1,133 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.DialogSiteBinding; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.ui.adapter.SiteAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class SiteDialog implements SiteAdapter.OnClickListener { + + private RecyclerView.ItemDecoration decoration; + private final DialogSiteBinding binding; + private final SiteCallback callback; + private final SiteAdapter adapter; + private final AlertDialog dialog; + private final int GRID_COUNT = 10; + private int type; + + public static SiteDialog create(Activity activity) { + return new SiteDialog(activity); + } + + public SiteDialog(Activity activity) { + this.adapter = new SiteAdapter(this); + this.callback = (SiteCallback) activity; + this.binding = DialogSiteBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + } + + public SiteDialog search() { + type = 1; + return this; + } + + public SiteDialog action() { + binding.action.setVisibility(View.VISIBLE); + return this; + } + + public void show() { + setType(type); + initView(); + initEvent(); + } + + private boolean list() { + return Setting.getSiteMode() == 0 || adapter.getItemCount() < GRID_COUNT; + } + + private int getCount() { + return list() ? 1 : Math.max(2, Math.min((int) Math.ceil((double) adapter.getItemCount() / GRID_COUNT), 3)); + } + + private int getIcon() { + return list() ? R.drawable.ic_site_grid : R.drawable.ic_site_list; + } + + private float getWidth() { + return 0.4f + (getCount() - 1) * 0.2f; + } + + private void initView() { + setRecyclerView(); + setDialog(); + setMode(); + } + + private void initEvent() { + binding.mode.setOnClickListener(this::setMode); + binding.select.setOnClickListener(v -> adapter.selectAll()); + binding.cancel.setOnClickListener(v -> adapter.cancelAll()); + binding.search.setOnClickListener(v -> setType(v.isActivated() ? 0 : 1)); + binding.change.setOnClickListener(v -> setType(v.isActivated() ? 0 : 2)); + } + + private void setRecyclerView() { + binding.recycler.setAdapter(adapter); + binding.recycler.setHasFixedSize(true); + binding.recycler.setItemAnimator(null); + if (decoration != null) binding.recycler.removeItemDecoration(decoration); + binding.recycler.addItemDecoration(decoration = new SpaceItemDecoration(getCount(), 16)); + binding.recycler.setLayoutManager(new GridLayoutManager(dialog.getContext(), getCount())); + if (!binding.mode.hasFocus()) binding.recycler.post(() -> binding.recycler.scrollToPosition(VodConfig.getHomeIndex())); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * getWidth()); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void setMode() { + if (adapter.getItemCount() < GRID_COUNT) Setting.putSiteMode(0); + binding.mode.setEnabled(adapter.getItemCount() >= GRID_COUNT); + binding.mode.setImageResource(getIcon()); + } + + private void setType(int type) { + binding.search.setActivated(type == 1); + binding.change.setActivated(type == 2); + binding.select.setClickable(type > 0); + binding.cancel.setClickable(type > 0); + adapter.setType(this.type = type); + } + + private void setMode(View view) { + Setting.putSiteMode(Math.abs(Setting.getSiteMode() - 1)); + initView(); + } + + @Override + public void onItemClick(Site item) { + callback.setSite(item); + dialog.dismiss(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SpeedDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SpeedDialog.java new file mode 100644 index 00000000..fe83d208 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/SpeedDialog.java @@ -0,0 +1,53 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogSpeedBinding; +import com.fongmi.android.tv.impl.SpeedCallback; +import com.fongmi.android.tv.utils.KeyUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class SpeedDialog { + + private final DialogSpeedBinding binding; + private final SpeedCallback callback; + private final AlertDialog dialog; + + public static SpeedDialog create(FragmentActivity activity) { + return new SpeedDialog(activity); + } + + public SpeedDialog(FragmentActivity activity) { + this.callback = (SpeedCallback) activity; + this.binding = DialogSpeedBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog.getWindow().setBackgroundDrawableResource(android.R.color.transparent); + dialog.show(); + } + + private void initView() { + binding.slider.setValue(Setting.getSpeed()); + } + + private void initEvent() { + binding.slider.addOnChangeListener((slider, value, fromUser) -> callback.setSpeed(value)); + binding.slider.setOnKeyListener((view, keyCode, event) -> { + boolean enter = KeyUtil.isEnterKey(event); + if (enter) dialog.dismiss(); + return enter; + }); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/UaDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/UaDialog.java new file mode 100644 index 00000000..b25bf0a9 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/UaDialog.java @@ -0,0 +1,120 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogUaBinding; +import com.fongmi.android.tv.event.ServerEvent; +import com.fongmi.android.tv.impl.UaCallback; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.fongmi.android.tv.utils.QRCode; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.utils.Util; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class UaDialog implements DialogInterface.OnDismissListener { + + private final DialogUaBinding binding; + private final UaCallback callback; + private final AlertDialog dialog; + private boolean append; + + public static UaDialog create(FragmentActivity activity) { + return new UaDialog(activity); + } + + public UaDialog(FragmentActivity activity) { + this.callback = (UaCallback) activity; + this.binding = DialogUaBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.append = true; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.width = (int) (ResUtil.getScreenWidth() * 0.55f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.setOnDismissListener(this); + dialog.show(); + } + + private void initView() { + String text = Setting.getUa(); + binding.text.setText(text); + binding.text.setSelection(TextUtils.isEmpty(text) ? 0 : text.length()); + binding.code.setImageBitmap(QRCode.getBitmap(Server.get().getAddress(3), 200, 0)); + binding.info.setText(ResUtil.getString(R.string.push_info, Server.get().getAddress()).replace(",", "\n")); + } + + private void initEvent() { + EventBus.getDefault().register(this); + binding.positive.setOnClickListener(this::onPositive); + binding.negative.setOnClickListener(this::onNegative); + binding.text.addTextChangedListener(new CustomTextListener() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + detect(s.toString()); + } + }); + binding.text.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) binding.positive.performClick(); + return true; + }); + } + + private void detect(String s) { + if (append && "c".equalsIgnoreCase(s)) { + append = false; + binding.text.setText(Util.CHROME); + } else if (append && "o".equalsIgnoreCase(s)) { + append = false; + binding.text.setText(Util.OKHTTP); + } else if (s.length() > 1) { + append = false; + } else if (s.length() == 0) { + append = true; + } + } + + private void onPositive(View view) { + callback.setUa(binding.text.getText().toString().trim()); + dialog.dismiss(); + } + + private void onNegative(View view) { + dialog.dismiss(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onServerEvent(ServerEvent event) { + if (event.getType() != ServerEvent.Type.SETTING) return; + binding.text.setText(event.getText()); + binding.positive.performClick(); + } + + @Override + public void onDismiss(DialogInterface dialogInterface) { + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/WebDialog.java b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/WebDialog.java new file mode 100644 index 00000000..d5e9748a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/dialog/WebDialog.java @@ -0,0 +1,44 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.view.View; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class WebDialog { + + private final AlertDialog dialog; + + public static WebDialog create(View view) { + return new WebDialog(view); + } + + public WebDialog(View view) { + this.dialog = new MaterialAlertDialogBuilder(App.activity()).setView(view).create(); + this.dialog.setOnDismissListener((DialogInterface.OnDismissListener) view); + } + + public WebDialog show() { + initDialog(); + return this; + } + + public void dismiss() { + dialog.setOnDismissListener(null); + dialog.dismiss(); + } + + private void initDialog() { + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + params.height = (int) (ResUtil.getScreenHeight() * 0.8f); + params.width = (int) (ResUtil.getScreenWidth() * 0.8f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/fragment/CollectFragment.java b/app/src/leanback/java/com/fongmi/android/tv/ui/fragment/CollectFragment.java new file mode 100644 index 00000000..82852528 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/fragment/CollectFragment.java @@ -0,0 +1,141 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.app.Activity; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.ListRow; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Collect; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.FragmentVodBinding; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.activity.VideoActivity; +import com.fongmi.android.tv.ui.activity.VodActivity; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.custom.CustomRowPresenter; +import com.fongmi.android.tv.ui.custom.CustomScroller; +import com.fongmi.android.tv.ui.custom.CustomSelector; +import com.fongmi.android.tv.ui.presenter.VodPresenter; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.common.collect.Lists; + +import java.util.ArrayList; +import java.util.List; + +public class CollectFragment extends BaseFragment implements CustomScroller.Callback, VodPresenter.OnClickListener { + + private FragmentVodBinding mBinding; + private ArrayObjectAdapter mAdapter; + private ArrayObjectAdapter mLast; + private CustomScroller mScroller; + private SiteViewModel mViewModel; + private Collect mCollect; + private String mKeyword; + + public static CollectFragment newInstance(String keyword, Collect collect) { + Bundle args = new Bundle(); + args.putString("keyword", keyword); + CollectFragment fragment = new CollectFragment().setCollect(collect); + fragment.setArguments(args); + return fragment; + } + + private String getKeyword() { + return mKeyword = mKeyword == null ? getArguments().getString("keyword") : mKeyword; + } + + private CollectFragment setCollect(Collect collect) { + this.mCollect = collect; + return this; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentVodBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + setRecyclerView(); + setViewModel(); + } + + private void setRecyclerView() { + CustomSelector selector = new CustomSelector(); + selector.addPresenter(ListRow.class, new CustomRowPresenter(16), VodPresenter.class); + mBinding.recycler.setAdapter(new ItemBridgeAdapter(mAdapter = new ArrayObjectAdapter(selector))); + mBinding.recycler.setHeader(getActivity().findViewById(R.id.result), getActivity().findViewById(R.id.recycler)); + mBinding.recycler.addOnScrollListener(mScroller = new CustomScroller(this)); + mBinding.recycler.setVerticalSpacing(ResUtil.dp2px(16)); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.result.observe(this, result -> { + mScroller.endLoading(result); + addVideo(result.getList()); + }); + } + + @Override + protected void initData() { + if (mCollect != null) addVideo(mCollect.getList()); + } + + private boolean checkLastSize(List items) { + if (mLast == null || items.size() == 0) return false; + int size = Product.getColumn() - mLast.size(); + if (size == 0) return false; + size = Math.min(size, items.size()); + mLast.addAll(mLast.size(), new ArrayList<>(items.subList(0, size))); + addVideo(new ArrayList<>(items.subList(size, items.size()))); + return true; + } + + public void addVideo(List items) { + if (checkLastSize(items) || getActivity() == null || getActivity().isFinishing()) return; + List rows = new ArrayList<>(); + for (List part : Lists.partition(items, Product.getColumn())) { + mLast = new ArrayObjectAdapter(new VodPresenter(this)); + mLast.setItems(part, null); + rows.add(new ListRow(mLast)); + } + mAdapter.addAll(mAdapter.size(), rows); + } + + @Override + public void onItemClick(Vod item) { + getActivity().setResult(Activity.RESULT_OK); + if (item.isFolder()) VodActivity.start(getActivity(), item.getSiteKey(), Result.folder(item)); + else VideoActivity.collect(getActivity(), item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public boolean onLongClick(Vod item) { + return false; + } + + @Override + public void onLoadMore(String page) { + if (mCollect == null || "all".equals(mCollect.getSite().getKey())) return; + mViewModel.searchContent(mCollect.getSite(), getKeyword(), page); + mScroller.setLoading(true); + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (mBinding != null && !isVisibleToUser) mBinding.recycler.moveToTop(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/fragment/VodFragment.java b/app/src/leanback/java/com/fongmi/android/tv/ui/fragment/VodFragment.java new file mode 100644 index 00000000..ad1b647a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/fragment/VodFragment.java @@ -0,0 +1,311 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.leanback.widget.ArrayObjectAdapter; +import androidx.leanback.widget.FocusHighlight; +import androidx.leanback.widget.HorizontalGridView; +import androidx.leanback.widget.ItemBridgeAdapter; +import androidx.leanback.widget.ListRow; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Filter; +import com.fongmi.android.tv.bean.Page; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.bean.Value; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.FragmentVodBinding; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.activity.CollectActivity; +import com.fongmi.android.tv.ui.activity.VideoActivity; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.custom.CustomRowPresenter; +import com.fongmi.android.tv.ui.custom.CustomScroller; +import com.fongmi.android.tv.ui.custom.CustomSelector; +import com.fongmi.android.tv.ui.presenter.FilterPresenter; +import com.fongmi.android.tv.ui.presenter.VodPresenter; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.utils.Prefers; +import com.google.common.collect.Lists; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class VodFragment extends BaseFragment implements CustomScroller.Callback, VodPresenter.OnClickListener { + + private HashMap mExtends; + private FragmentVodBinding mBinding; + private ArrayObjectAdapter mAdapter; + private ArrayObjectAdapter mLast; + private CustomScroller mScroller; + private SiteViewModel mViewModel; + private List mFilters; + private List mPages; + private boolean mOpen; + private Page mPage; + + public static VodFragment newInstance(String key, String typeId, Style style, HashMap extend, boolean folder) { + Bundle args = new Bundle(); + args.putString("key", key); + args.putString("typeId", typeId); + args.putBoolean("folder", folder); + args.putParcelable("style", style); + args.putSerializable("extend", extend); + VodFragment fragment = new VodFragment(); + fragment.setArguments(args); + return fragment; + } + + private String getKey() { + return getArguments().getString("key"); + } + + private String getTypeId() { + return mPages.isEmpty() ? getArguments().getString("typeId") : getLastPage().getVodId(); + } + + private List getFilter() { + return Filter.arrayFrom(Prefers.getString("filter_" + getKey() + "_" + getTypeId())); + } + + private HashMap getExtend() { + Serializable extend = getArguments().getSerializable("extend"); + return extend == null ? new HashMap<>() : (HashMap) extend; + } + + private boolean isFolder() { + return getArguments().getBoolean("folder"); + } + + private Site getSite() { + return VodConfig.get().getSite(getKey()); + } + + private Page getLastPage() { + return mPages.get(mPages.size() - 1); + } + + private Style getStyle() { + return isFolder() ? Style.list() : getSite().getStyle(mPages.isEmpty() ? getArguments().getParcelable("style") : getLastPage().getStyle()); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentVodBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + mPages = new ArrayList<>(); + mExtends = getExtend(); + mFilters = getFilter(); + setRecyclerView(); + setViewModel(); + setFilters(); + } + + @Override + protected void initData() { + getVideo(); + } + + @SuppressLint("RestrictedApi") + private void setRecyclerView() { + CustomSelector selector = new CustomSelector(); + selector.addPresenter(Vod.class, new VodPresenter(this, Style.list())); + selector.addPresenter(ListRow.class, new CustomRowPresenter(16), VodPresenter.class); + selector.addPresenter(ListRow.class, new CustomRowPresenter(8, FocusHighlight.ZOOM_FACTOR_NONE, HorizontalGridView.FOCUS_SCROLL_ALIGNED), FilterPresenter.class); + mBinding.recycler.addOnScrollListener(mScroller = new CustomScroller(this)); + mBinding.recycler.setAdapter(new ItemBridgeAdapter(mAdapter = new ArrayObjectAdapter(selector))); + mBinding.recycler.setHeader(getActivity().findViewById(R.id.recycler)); + mBinding.recycler.setVerticalSpacing(ResUtil.dp2px(16)); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.action.observe(getViewLifecycleOwner(), result -> Notify.show(result.getMsg())); + mViewModel.result.observe(getViewLifecycleOwner(), result -> { + boolean first = mScroller.first(); + int size = result.getList().size(); + if (size > 0) addVideo(result); + mScroller.endLoading(result); + checkPosition(first); + checkMore(size); + hideProgress(); + }); + } + + private void setFilters() { + for (Filter filter : mFilters) { + if (mExtends.containsKey(filter.getKey())) { + filter.setActivated(mExtends.get(filter.getKey())); + } + } + } + + private void setClick(ArrayObjectAdapter adapter, String key, Value item) { + for (int i = 0; i < adapter.size(); i++) ((Value) adapter.get(i)).setActivated(item); + adapter.notifyArrayItemRangeChanged(0, adapter.size()); + if (item.isActivated()) mExtends.put(key, item.getV()); + else mExtends.remove(key); + onRefresh(); + } + + private void getVideo() { + mScroller.reset(); + getVideo(getTypeId(), "1"); + } + + private void getVideo(String typeId, String page) { + boolean first = "1".equals(page); + if (first) mLast = null; + if (first) showProgress(); + int filterSize = mOpen ? mFilters.size() : 0; + boolean clear = first && mAdapter.size() > filterSize; + if (clear) mAdapter.removeItems(filterSize, mAdapter.size() - filterSize); + mViewModel.categoryContent(getKey(), typeId, page, true, mExtends); + } + + private void addVideo(Result result) { + Style style = result.getStyle(getStyle()); + if (style.isList()) mAdapter.addAll(mAdapter.size(), result.getList()); + else addGrid(result.getList(), style); + } + + private void checkPosition(boolean first) { + if (mPage != null && mPage.getPosition() > 0) mBinding.recycler.hideHeader(); + if (mPage != null && mPage.getPosition() < 1) mBinding.recycler.showHeader(); + if (mPage != null) mBinding.recycler.setSelectedPosition(mPage.getPosition()); + else if (first && !mOpen) mBinding.recycler.moveToTop(); + mPage = null; + } + + private void checkMore(int count) { + if (mScroller.isDisable() || count == 0 || mAdapter.size() >= 5) return; + getVideo(getTypeId(), String.valueOf(mScroller.addPage())); + } + + private boolean checkLastSize(List items, Style style) { + if (mLast == null || items.size() == 0) return false; + int size = Product.getColumn(style) - mLast.size(); + if (size == 0) return false; + size = Math.min(size, items.size()); + mLast.addAll(mLast.size(), new ArrayList<>(items.subList(0, size))); + addGrid(new ArrayList<>(items.subList(size, items.size())), style); + return true; + } + + private void addGrid(List items, Style style) { + if (checkLastSize(items, style)) return; + List rows = new ArrayList<>(); + for (List part : Lists.partition(items, Product.getColumn(style))) { + mLast = new ArrayObjectAdapter(new VodPresenter(this, style)); + mLast.setItems(part, null); + rows.add(new ListRow(mLast)); + } + mAdapter.addAll(mAdapter.size(), rows); + } + + private ListRow getRow(Filter filter) { + FilterPresenter presenter = new FilterPresenter(filter.getKey()); + ArrayObjectAdapter adapter = new ArrayObjectAdapter(presenter); + presenter.setOnClickListener((key, item) -> setClick(adapter, key, item)); + adapter.setItems(filter.getValue(), null); + return new ListRow(adapter); + } + + private void showProgress() { + if (!mOpen) mBinding.progress.getRoot().setVisibility(View.VISIBLE); + } + + private void hideProgress() { + mBinding.progress.getRoot().setVisibility(View.GONE); + } + + private void showFilter() { + List rows = new ArrayList<>(); + for (Filter filter : mFilters) rows.add(getRow(filter)); + App.post(() -> mBinding.recycler.scrollToPosition(0), 48); + mAdapter.addAll(0, rows); + hideProgress(); + } + + private void hideFilter() { + mAdapter.removeItems(0, mFilters.size()); + } + + public void toggleFilter(boolean open) { + if (open) showFilter(); + else hideFilter(); + mOpen = open; + } + + public void onRefresh() { + getVideo(); + } + + public boolean canBack() { + return !mPages.isEmpty(); + } + + public void goBack() { + if (mPages.size() == 1) mBinding.recycler.setMoveTop(true); + mPages.remove(mPage = getLastPage()); + onRefresh(); + } + + public boolean goRoot() { + if (mPages.isEmpty()) return false; + mPages.clear(); + getVideo(); + return true; + } + + @Override + public void onItemClick(Vod item) { + if (item.isAction()) { + mViewModel.action(getKey(), item.getAction()); + } else if (item.isFolder()) { + mPages.add(Page.get(item, mBinding.recycler.getSelectedPosition())); + mBinding.recycler.setMoveTop(false); + getVideo(item.getVodId(), "1"); + } else { + if (getSite().isIndex()) CollectActivity.start(getActivity(), item.getVodName()); + else VideoActivity.start(getActivity(), getKey(), item.getVodId(), item.getVodName(), item.getVodPic(), isFolder() ? item.getVodName() : null); + } + } + + @Override + public boolean onLongClick(Vod item) { + CollectActivity.start(getActivity(), item.getVodName()); + return true; + } + + @Override + public void onLoadMore(String page) { + mScroller.setLoading(true); + getVideo(getTypeId(), page); + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (mBinding != null && !isVisibleToUser) mBinding.recycler.moveToTop(); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodListHolder.java b/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodListHolder.java new file mode 100644 index 00000000..24bc353d --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodListHolder.java @@ -0,0 +1,34 @@ +package com.fongmi.android.tv.ui.holder; + +import android.widget.ImageView; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodListBinding; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.ui.presenter.VodPresenter; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodListHolder extends BaseVodHolder { + + private final VodPresenter.OnClickListener listener; + private final AdapterVodListBinding binding; + + public VodListHolder(@NonNull AdapterVodListBinding binding, VodPresenter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.remark.setText(item.getVodRemarks()); + binding.name.setVisibility(item.getNameVisible()); + binding.remark.setVisibility(item.getRemarkVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.load(item.getVodName(), item.getVodPic(), binding.image, ImageView.ScaleType.FIT_CENTER, true); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodOvalHolder.java b/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodOvalHolder.java new file mode 100644 index 00000000..525edaff --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodOvalHolder.java @@ -0,0 +1,36 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodOvalBinding; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.ui.presenter.VodPresenter; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodOvalHolder extends BaseVodHolder { + + private final VodPresenter.OnClickListener listener; + private final AdapterVodOvalBinding binding; + + public VodOvalHolder(@NonNull AdapterVodOvalBinding binding, VodPresenter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + public VodOvalHolder size(int[] size) { + binding.image.getLayoutParams().width = size[0]; + binding.image.getLayoutParams().height = size[1]; + return this; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.name.setVisibility(item.getNameVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.oval(item.getVodName(), item.getVodPic(), binding.image); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodRectHolder.java b/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodRectHolder.java new file mode 100644 index 00000000..26748f95 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/holder/VodRectHolder.java @@ -0,0 +1,42 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodRectBinding; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.ui.presenter.VodPresenter; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodRectHolder extends BaseVodHolder { + + private final VodPresenter.OnClickListener listener; + private final AdapterVodRectBinding binding; + + public VodRectHolder(@NonNull AdapterVodRectBinding binding, VodPresenter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + public VodRectHolder size(int[] size) { + binding.getRoot().getLayoutParams().width = size[0]; + binding.getRoot().getLayoutParams().height = size[1]; + return this; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.year.setText(item.getVodYear()); + binding.site.setText(item.getSiteName()); + binding.remark.setText(item.getVodRemarks()); + binding.site.setVisibility(item.getSiteVisible()); + binding.year.setVisibility(item.getYearVisible()); + binding.name.setVisibility(item.getNameVisible()); + binding.remark.setVisibility(item.getRemarkVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.rect(item.getVodName(), item.getVodPic(), binding.image); + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ArrayPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ArrayPresenter.java new file mode 100644 index 00000000..86604c34 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ArrayPresenter.java @@ -0,0 +1,63 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.AdapterArrayBinding; +import com.fongmi.android.tv.utils.ResUtil; + +public class ArrayPresenter extends Presenter { + + private final OnClickListener mListener; + private final String backward; + private final String forward; + private final String reverse; + + public ArrayPresenter(OnClickListener listener) { + this.mListener = listener; + this.backward = ResUtil.getString(R.string.play_backward); + this.forward = ResUtil.getString(R.string.play_forward); + this.reverse = ResUtil.getString(R.string.play_reverse); + } + + public interface OnClickListener { + + void onRevSort(); + + void onRevPlay(TextView view); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterArrayBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + ViewHolder holder = (ViewHolder) viewHolder; + String text = object.toString(); + holder.binding.text.setText(text); + if (text.equals(reverse)) setOnClickListener(holder, view -> mListener.onRevSort()); + else if (text.equals(backward) || text.equals(forward)) setOnClickListener(holder, view -> mListener.onRevPlay(holder.binding.text)); + else setOnClickListener(holder, null); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterArrayBinding binding; + + public ViewHolder(@NonNull AdapterArrayBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ChannelPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ChannelPresenter.java new file mode 100644 index 00000000..63e25a24 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ChannelPresenter.java @@ -0,0 +1,60 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.databinding.AdapterChannelBinding; + +public class ChannelPresenter extends Presenter { + + private final OnClickListener mListener; + + public ChannelPresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + + void showEpg(Channel item); + + void onItemClick(Channel item); + + boolean onLongClick(Channel item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterChannelBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Channel item = (Channel) object; + ViewHolder holder = (ViewHolder) viewHolder; + item.loadLogo(holder.binding.logo); + holder.binding.name.setText(item.getName()); + holder.binding.number.setText(item.getNumber()); + holder.binding.getRoot().setSelected(item.isSelected()); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + holder.view.setOnLongClickListener(view -> mListener.onLongClick(item)); + holder.binding.getRoot().setRightListener(() -> mListener.showEpg(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterChannelBinding binding; + + public ViewHolder(@NonNull AdapterChannelBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/CollectPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/CollectPresenter.java new file mode 100644 index 00000000..4fb7e5d4 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/CollectPresenter.java @@ -0,0 +1,40 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Collect; +import com.fongmi.android.tv.databinding.AdapterFilterBinding; + +public class CollectPresenter extends Presenter { + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterFilterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Collect item = (Collect) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(item.getSite().getName()); + setOnClickListener(holder, null); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterFilterBinding binding; + + public ViewHolder(@NonNull AdapterFilterBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/EpgDataPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/EpgDataPresenter.java new file mode 100644 index 00000000..4d77a026 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/EpgDataPresenter.java @@ -0,0 +1,58 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.EpgData; +import com.fongmi.android.tv.databinding.AdapterEpgDataBinding; + +public class EpgDataPresenter extends Presenter { + + private final OnClickListener mListener; + + public EpgDataPresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + + void hideEpg(); + + void onItemClick(EpgData item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterEpgDataBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + EpgData item = (EpgData) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.time.setText(item.getTime()); + holder.binding.title.setText(item.getTitle()); + holder.binding.getRoot().setSelected(item.isSelected()); + holder.binding.getRoot().setLeftListener(mListener::hideEpg); + setOnClickListener(holder, view -> { + if (!item.isFuture()) mListener.onItemClick(item); + }); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterEpgDataBinding binding; + + public ViewHolder(@NonNull AdapterEpgDataBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/EpisodePresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/EpisodePresenter.java new file mode 100644 index 00000000..7c458206 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/EpisodePresenter.java @@ -0,0 +1,65 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.AdapterEpisodeBinding; + +public class EpisodePresenter extends Presenter { + + private final OnClickListener mListener; + private int nextFocusDown; + private int nextFocusUp; + + public EpisodePresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + void onItemClick(Episode item); + } + + public void setNextFocusDown(int nextFocus) { + this.nextFocusDown = nextFocus; + } + + public void setNextFocusUp(int nextFocus) { + this.nextFocusUp = nextFocus; + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterEpisodeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Episode item = (Episode) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setMaxEms(Product.getEms()); + holder.binding.text.setNextFocusUpId(nextFocusUp); + holder.binding.text.setNextFocusDownId(nextFocusDown); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.text.setText(item.getDesc().concat(item.getName())); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterEpisodeBinding binding; + + public ViewHolder(@NonNull AdapterEpisodeBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FilePresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FilePresenter.java new file mode 100644 index 00000000..419e49b0 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FilePresenter.java @@ -0,0 +1,54 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.AdapterFileBinding; + +import java.io.File; + +public class FilePresenter extends Presenter { + + private final OnClickListener mListener; + + public FilePresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(File file); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterFileBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + File file = (File) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.name.setText(file.getName()); + holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(file)); + holder.binding.image.setImageResource(file.isDirectory() ? R.drawable.ic_folder : R.drawable.ic_file); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterFileBinding binding; + + public ViewHolder(@NonNull AdapterFileBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FilterPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FilterPresenter.java new file mode 100644 index 00000000..262442fc --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FilterPresenter.java @@ -0,0 +1,56 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Value; +import com.fongmi.android.tv.databinding.AdapterFilterBinding; + +public class FilterPresenter extends Presenter { + + private OnClickListener mListener; + private final String mKey; + + public FilterPresenter(String key) { + mKey = key; + } + + public interface OnClickListener { + void onItemClick(String key, Value item); + } + + public void setOnClickListener(OnClickListener listener) { + this.mListener = listener; + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterFilterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Value item = (Value) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(item.getN()); + holder.binding.text.setActivated(item.isActivated()); + setOnClickListener(holder, view -> mListener.onItemClick(mKey, item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterFilterBinding binding; + + public ViewHolder(@NonNull AdapterFilterBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FlagPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FlagPresenter.java new file mode 100644 index 00000000..e4d0be03 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FlagPresenter.java @@ -0,0 +1,59 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Flag; +import com.fongmi.android.tv.databinding.AdapterFlagBinding; + +public class FlagPresenter extends Presenter { + + private final OnClickListener mListener; + private int nextFocusDown; + + public FlagPresenter(OnClickListener listener) { + this.mListener = listener; + this.nextFocusDown = R.id.episode; + } + + public interface OnClickListener { + void onItemClick(Flag item); + } + + public void setNextFocusDown(int nextFocusDown) { + this.nextFocusDown = nextFocusDown; + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterFlagBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Flag item = (Flag) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(item.getShow()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.text.setNextFocusDownId(nextFocusDown); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterFlagBinding binding; + + public ViewHolder(@NonNull AdapterFlagBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FuncPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FuncPresenter.java new file mode 100644 index 00000000..fedccc77 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/FuncPresenter.java @@ -0,0 +1,51 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Func; +import com.fongmi.android.tv.databinding.AdapterFuncBinding; + +public class FuncPresenter extends Presenter { + + private final OnClickListener mListener; + + public FuncPresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + void onItemClick(Func item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterFuncBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Func item = (Func) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(item.getText()); + holder.binding.icon.setImageResource(item.getDrawable()); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterFuncBinding binding; + + public ViewHolder(@NonNull AdapterFuncBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/GroupPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/GroupPresenter.java new file mode 100644 index 00000000..ab0b420e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/GroupPresenter.java @@ -0,0 +1,50 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.databinding.AdapterGroupBinding; + +public class GroupPresenter extends Presenter { + + private final OnClickListener mListener; + + public GroupPresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + void onItemClick(Group item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterGroupBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Group item = (Group) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.name.setText(item.getName()); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterGroupBinding binding; + + public ViewHolder(@NonNull AdapterGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/HeaderPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/HeaderPresenter.java new file mode 100644 index 00000000..1963ee0e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/HeaderPresenter.java @@ -0,0 +1,38 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.databinding.AdapterHeaderBinding; +import com.fongmi.android.tv.utils.ResUtil; + +public class HeaderPresenter extends Presenter { + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new HeaderPresenter.ViewHolder(AdapterHeaderBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + HeaderPresenter.ViewHolder holder = (HeaderPresenter.ViewHolder) viewHolder; + holder.binding.text.setText(object instanceof String ? object.toString() : ResUtil.getString((int) object)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterHeaderBinding binding; + + public ViewHolder(@NonNull AdapterHeaderBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/HistoryPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/HistoryPresenter.java new file mode 100644 index 00000000..ffbdbdcc --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/HistoryPresenter.java @@ -0,0 +1,95 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.databinding.AdapterVodBinding; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.ResUtil; + +public class HistoryPresenter extends Presenter { + + private final OnClickListener mListener; + private int width, height; + private boolean delete; + + public HistoryPresenter(OnClickListener listener) { + this.mListener = listener; + setLayoutSize(); + } + + public interface OnClickListener { + + void onItemClick(History item); + + void onItemDelete(History item); + + boolean onLongClick(); + } + + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + } + + private void setLayoutSize() { + int space = ResUtil.dp2px(48) + ResUtil.dp2px(16 * (Product.getColumn() - 1)); + int base = ResUtil.getScreenWidth() - space; + width = base / Product.getColumn(); + height = (int) (width / 0.75f); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + ViewHolder holder = new ViewHolder(AdapterVodBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + holder.binding.getRoot().getLayoutParams().width = width; + holder.binding.getRoot().getLayoutParams().height = height; + return holder; + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + History item = (History) object; + ViewHolder holder = (ViewHolder) viewHolder; + setClickListener(holder.view, item); + holder.binding.name.setText(item.getVodName()); + holder.binding.site.setText(item.getSiteName()); + holder.binding.site.setVisibility(item.getSiteVisible()); + holder.binding.remark.setVisibility(delete ? View.GONE : View.VISIBLE); + holder.binding.delete.setVisibility(!delete ? View.GONE : View.VISIBLE); + holder.binding.remark.setText(ResUtil.getString(R.string.vod_last, item.getVodRemarks())); + ImgUtil.loadVod(item.getVodName(), item.getVodPic(), holder.binding.image); + } + + private void setClickListener(View root, History item) { + root.setOnLongClickListener(view -> mListener.onLongClick()); + root.setOnClickListener(view -> { + if (isDelete()) mListener.onItemDelete(item); + else mListener.onItemClick(item); + }); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterVodBinding binding; + + public ViewHolder(@NonNull AdapterVodBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ParsePresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ParsePresenter.java new file mode 100644 index 00000000..b93ecd9a --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ParsePresenter.java @@ -0,0 +1,51 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Parse; +import com.fongmi.android.tv.databinding.AdapterParseBinding; + +public class ParsePresenter extends Presenter { + + private final OnClickListener mListener; + + public ParsePresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + void onItemClick(Parse parse); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterParseBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Parse item = (Parse) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(item.getName()); + holder.binding.text.setActivated(item.isActivated()); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterParseBinding binding; + + public ViewHolder(@NonNull AdapterParseBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/PartPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/PartPresenter.java new file mode 100644 index 00000000..15c28185 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/PartPresenter.java @@ -0,0 +1,57 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.databinding.AdapterPartBinding; + +public class PartPresenter extends Presenter { + + private final OnClickListener mListener; + private int nextFocusUp; + + public PartPresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + void onItemClick(String item); + } + + public void setNextFocusUp(int nextFocusUp) { + this.nextFocusUp = nextFocusUp; + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterPartBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + String text = object.toString(); + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(text); + holder.binding.text.setMaxEms(Product.getEms()); + holder.binding.text.setNextFocusUpId(nextFocusUp); + setOnClickListener(holder, view -> mListener.onItemClick(text)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterPartBinding binding; + + public ViewHolder(@NonNull AdapterPartBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ProgressPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ProgressPresenter.java new file mode 100644 index 00000000..e187b7b2 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/ProgressPresenter.java @@ -0,0 +1,32 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.databinding.AdapterProgressBinding; + +public class ProgressPresenter extends Presenter { + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterProgressBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + public ViewHolder(@NonNull AdapterProgressBinding binding) { + super(binding.getRoot()); + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/QuickPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/QuickPresenter.java new file mode 100644 index 00000000..ebe411a8 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/QuickPresenter.java @@ -0,0 +1,64 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterQuickBinding; +import com.fongmi.android.tv.utils.ResUtil; + +public class QuickPresenter extends Presenter { + + private final OnClickListener mListener; + private int width; + + public QuickPresenter(OnClickListener listener) { + this.mListener = listener; + setLayoutSize(); + } + + private void setLayoutSize() { + int space = ResUtil.dp2px(24) + ResUtil.dp2px(32); + int base = ResUtil.getScreenWidth() - space; + width = base / 4; + } + + public interface OnClickListener { + + void onItemClick(Vod item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + ViewHolder holder = new ViewHolder(AdapterQuickBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + holder.binding.getRoot().getLayoutParams().width = width; + return holder; + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Vod item = (Vod) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.name.setText(item.getVodName()); + holder.binding.site.setText(item.getSiteName()); + holder.binding.remark.setText(item.getVodRemarks()); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterQuickBinding binding; + + public ViewHolder(@NonNull AdapterQuickBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/TypePresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/TypePresenter.java new file mode 100644 index 00000000..ef624371 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/TypePresenter.java @@ -0,0 +1,62 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Class; +import com.fongmi.android.tv.databinding.AdapterTypeBinding; +import com.fongmi.android.tv.utils.ResUtil; + +public class TypePresenter extends Presenter { + + private final OnClickListener mListener; + + public TypePresenter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(Class item); + + void onRefresh(Class item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + return new ViewHolder(AdapterTypeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + Class item = (Class) object; + ViewHolder holder = (ViewHolder) viewHolder; + holder.binding.text.setText(item.getTypeName()); + holder.binding.text.setCompoundDrawablePadding(ResUtil.dp2px(4)); + holder.binding.text.setCompoundDrawablesWithIntrinsicBounds(0, 0, getIcon(item), 0); + holder.binding.text.setListener(() -> mListener.onRefresh(item)); + setOnClickListener(holder, view -> mListener.onItemClick(item)); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } + + private int getIcon(Class item) { + return item.getFilter() == null ? 0 : item.getFilter() ? R.drawable.ic_vod_filter_off : R.drawable.ic_vod_filter_on; + } + + public static class ViewHolder extends Presenter.ViewHolder { + + private final AdapterTypeBinding binding; + + public ViewHolder(@NonNull AdapterTypeBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/VodPresenter.java b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/VodPresenter.java new file mode 100644 index 00000000..3fc4100e --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/presenter/VodPresenter.java @@ -0,0 +1,63 @@ +package com.fongmi.android.tv.ui.presenter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.leanback.widget.Presenter; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodListBinding; +import com.fongmi.android.tv.databinding.AdapterVodOvalBinding; +import com.fongmi.android.tv.databinding.AdapterVodRectBinding; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.holder.VodListHolder; +import com.fongmi.android.tv.ui.holder.VodOvalHolder; +import com.fongmi.android.tv.ui.holder.VodRectHolder; + +public class VodPresenter extends Presenter { + + private final OnClickListener mListener; + private final Style style; + private final int[] size; + + public VodPresenter(OnClickListener listener) { + this(listener, Style.rect()); + } + + public VodPresenter(OnClickListener listener, Style style) { + this.mListener = listener; + this.style = style; + this.size = Product.getSpec(style); + } + + public interface OnClickListener { + + void onItemClick(Vod item); + + boolean onLongClick(Vod item); + } + + @Override + public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) { + switch (style.getViewType()) { + case ViewType.LIST: + return new VodListHolder(AdapterVodListBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener); + case ViewType.OVAL: + return new VodOvalHolder(AdapterVodOvalBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener).size(size); + default: + return new VodRectHolder(AdapterVodRectBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener).size(size); + } + } + + @Override + public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object object) { + ((BaseVodHolder) viewHolder).initView((Vod) object); + } + + @Override + public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) { + } +} \ No newline at end of file diff --git a/app/src/leanback/java/com/fongmi/android/tv/utils/KeyUtil.java b/app/src/leanback/java/com/fongmi/android/tv/utils/KeyUtil.java new file mode 100644 index 00000000..32ee50b5 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/utils/KeyUtil.java @@ -0,0 +1,38 @@ +package com.fongmi.android.tv.utils; + +import android.view.KeyEvent; + +public class KeyUtil { + + public static boolean isEnterKey(KeyEvent event) { + return event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER || event.getKeyCode() == KeyEvent.KEYCODE_ENTER || event.getKeyCode() == KeyEvent.KEYCODE_SPACE || event.getKeyCode() == KeyEvent.KEYCODE_NUMPAD_ENTER; + } + + public static boolean isUpKey(KeyEvent event) { + return event.getKeyCode() == KeyEvent.KEYCODE_DPAD_UP || event.getKeyCode() == KeyEvent.KEYCODE_CHANNEL_UP || event.getKeyCode() == KeyEvent.KEYCODE_PAGE_UP || event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PREVIOUS; + } + + public static boolean isDownKey(KeyEvent event) { + return event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN || event.getKeyCode() == KeyEvent.KEYCODE_CHANNEL_DOWN || event.getKeyCode() == KeyEvent.KEYCODE_PAGE_DOWN || event.getKeyCode() == KeyEvent.KEYCODE_MEDIA_NEXT; + } + + public static boolean isLeftKey(KeyEvent event) { + return event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT; + } + + public static boolean isRightKey(KeyEvent event) { + return event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT; + } + + public static boolean isBackKey(KeyEvent event) { + return event.getKeyCode() == KeyEvent.KEYCODE_BACK; + } + + public static boolean isDigitKey(KeyEvent event) { + return event.getKeyCode() >= KeyEvent.KEYCODE_0 && event.getKeyCode() <= KeyEvent.KEYCODE_9 || event.getKeyCode() >= KeyEvent.KEYCODE_NUMPAD_0 && event.getKeyCode() <= KeyEvent.KEYCODE_NUMPAD_9; + } + + public static boolean isMenuKey(KeyEvent event) { + return event.getAction() == KeyEvent.ACTION_UP && event.getKeyCode() == KeyEvent.KEYCODE_MENU; + } +} diff --git a/app/src/leanback/java/com/fongmi/android/tv/utils/QRCode.java b/app/src/leanback/java/com/fongmi/android/tv/utils/QRCode.java new file mode 100644 index 00000000..331c8745 --- /dev/null +++ b/app/src/leanback/java/com/fongmi/android/tv/utils/QRCode.java @@ -0,0 +1,38 @@ +package com.fongmi.android.tv.utils; + +import android.graphics.Bitmap; +import android.graphics.Color; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.EncodeHintType; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.common.BitMatrix; + +import java.util.EnumMap; +import java.util.Map; + +public class QRCode { + + public static Bitmap getBitmap(String content, int size, int margin) { + try { + Map hints = new EnumMap<>(EncodeHintType.class); + hints.put(EncodeHintType.CHARACTER_SET, "UTF-8"); + hints.put(EncodeHintType.MARGIN, margin); + BitMatrix matrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, ResUtil.dp2px(size), ResUtil.dp2px(size), hints); + int width = matrix.getWidth(); + int height = matrix.getHeight(); + int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + int offset = y * width; + for (int x = 0; x < width; x++) { + pixels[offset + x] = matrix.get(x, y) ? Color.BLACK : Color.WHITE; + } + } + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } catch (Exception e) { + return null; + } + } +} diff --git a/app/src/leanback/res/anim/cycles.xml b/app/src/leanback/res/anim/cycles.xml new file mode 100644 index 00000000..6f48b61c --- /dev/null +++ b/app/src/leanback/res/anim/cycles.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/anim/flicker.xml b/app/src/leanback/res/anim/flicker.xml new file mode 100644 index 00000000..bf4e80ab --- /dev/null +++ b/app/src/leanback/res/anim/flicker.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/anim/shake.xml b/app/src/leanback/res/anim/shake.xml new file mode 100644 index 00000000..7be47cd9 --- /dev/null +++ b/app/src/leanback/res/anim/shake.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/color/channel.xml b/app/src/leanback/res/color/channel.xml new file mode 100644 index 00000000..1d23cc3b --- /dev/null +++ b/app/src/leanback/res/color/channel.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/color/epg.xml b/app/src/leanback/res/color/epg.xml new file mode 100644 index 00000000..311ab299 --- /dev/null +++ b/app/src/leanback/res/color/epg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/color/group.xml b/app/src/leanback/res/color/group.xml new file mode 100644 index 00000000..6be396f5 --- /dev/null +++ b/app/src/leanback/res/color/group.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_cast.png b/app/src/leanback/res/drawable-hdpi/ic_home_cast.png new file mode 100644 index 00000000..d547c82d Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_cast.png differ diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_keep.png b/app/src/leanback/res/drawable-hdpi/ic_home_keep.png new file mode 100644 index 00000000..22a395be Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_keep.png differ diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_live.png b/app/src/leanback/res/drawable-hdpi/ic_home_live.png new file mode 100644 index 00000000..e3831293 Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_live.png differ diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_push.png b/app/src/leanback/res/drawable-hdpi/ic_home_push.png new file mode 100644 index 00000000..76123162 Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_push.png differ diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_search.png b/app/src/leanback/res/drawable-hdpi/ic_home_search.png new file mode 100644 index 00000000..4aeae830 Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_search.png differ diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_setting.png b/app/src/leanback/res/drawable-hdpi/ic_home_setting.png new file mode 100644 index 00000000..53706e7d Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_setting.png differ diff --git a/app/src/leanback/res/drawable-hdpi/ic_home_vod.png b/app/src/leanback/res/drawable-hdpi/ic_home_vod.png new file mode 100644 index 00000000..066ac3c2 Binary files /dev/null and b/app/src/leanback/res/drawable-hdpi/ic_home_vod.png differ diff --git a/app/src/leanback/res/drawable-nodpi/wallpaper_1.webp b/app/src/leanback/res/drawable-nodpi/wallpaper_1.webp new file mode 100644 index 00000000..1ca38f6e Binary files /dev/null and b/app/src/leanback/res/drawable-nodpi/wallpaper_1.webp differ diff --git a/app/src/leanback/res/drawable-nodpi/wallpaper_2.webp b/app/src/leanback/res/drawable-nodpi/wallpaper_2.webp new file mode 100644 index 00000000..19185f77 Binary files /dev/null and b/app/src/leanback/res/drawable-nodpi/wallpaper_2.webp differ diff --git a/app/src/leanback/res/drawable-nodpi/wallpaper_3.webp b/app/src/leanback/res/drawable-nodpi/wallpaper_3.webp new file mode 100644 index 00000000..1830f00e Binary files /dev/null and b/app/src/leanback/res/drawable-nodpi/wallpaper_3.webp differ diff --git a/app/src/leanback/res/drawable-nodpi/wallpaper_4.webp b/app/src/leanback/res/drawable-nodpi/wallpaper_4.webp new file mode 100644 index 00000000..a89e0a1a Binary files /dev/null and b/app/src/leanback/res/drawable-nodpi/wallpaper_4.webp differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_cast.png b/app/src/leanback/res/drawable-xhdpi/ic_home_cast.png new file mode 100644 index 00000000..a48c5894 Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_cast.png differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_keep.png b/app/src/leanback/res/drawable-xhdpi/ic_home_keep.png new file mode 100644 index 00000000..3e5cff29 Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_keep.png differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_live.png b/app/src/leanback/res/drawable-xhdpi/ic_home_live.png new file mode 100644 index 00000000..4cc6efd8 Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_live.png differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_push.png b/app/src/leanback/res/drawable-xhdpi/ic_home_push.png new file mode 100644 index 00000000..0297b41b Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_push.png differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_search.png b/app/src/leanback/res/drawable-xhdpi/ic_home_search.png new file mode 100644 index 00000000..8d0fe6e7 Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_search.png differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_setting.png b/app/src/leanback/res/drawable-xhdpi/ic_home_setting.png new file mode 100644 index 00000000..fb568101 Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_setting.png differ diff --git a/app/src/leanback/res/drawable-xhdpi/ic_home_vod.png b/app/src/leanback/res/drawable-xhdpi/ic_home_vod.png new file mode 100644 index 00000000..d66f7dc4 Binary files /dev/null and b/app/src/leanback/res/drawable-xhdpi/ic_home_vod.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_cast.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_cast.png new file mode 100644 index 00000000..66d251ed Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_cast.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_keep.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_keep.png new file mode 100644 index 00000000..68dee8d4 Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_keep.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_live.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_live.png new file mode 100644 index 00000000..86804583 Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_live.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_push.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_push.png new file mode 100644 index 00000000..464bb806 Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_push.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_search.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_search.png new file mode 100644 index 00000000..abd58a7d Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_search.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_setting.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_setting.png new file mode 100644 index 00000000..33185868 Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_setting.png differ diff --git a/app/src/leanback/res/drawable-xxhdpi/ic_home_vod.png b/app/src/leanback/res/drawable-xxhdpi/ic_home_vod.png new file mode 100644 index 00000000..496c2860 Binary files /dev/null and b/app/src/leanback/res/drawable-xxhdpi/ic_home_vod.png differ diff --git a/app/src/leanback/res/drawable/ic_action_choose.xml b/app/src/leanback/res/drawable/ic_action_choose.xml new file mode 100644 index 00000000..854b5fc2 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_action_choose.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_action_subtitle.xml b/app/src/leanback/res/drawable/ic_action_subtitle.xml new file mode 100644 index 00000000..d3089ee8 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_action_subtitle.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_banner.png b/app/src/leanback/res/drawable/ic_banner.png new file mode 100644 index 00000000..69732e7b Binary files /dev/null and b/app/src/leanback/res/drawable/ic_banner.png differ diff --git a/app/src/leanback/res/drawable/ic_detail_change.xml b/app/src/leanback/res/drawable/ic_detail_change.xml new file mode 100644 index 00000000..e4bf9504 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_detail_change.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_detail_desc.xml b/app/src/leanback/res/drawable/ic_detail_desc.xml new file mode 100644 index 00000000..d08b48de --- /dev/null +++ b/app/src/leanback/res/drawable/ic_detail_desc.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_detail_keep_off.xml b/app/src/leanback/res/drawable/ic_detail_keep_off.xml new file mode 100644 index 00000000..590f9d89 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_detail_keep_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_detail_keep_on.xml b/app/src/leanback/res/drawable/ic_detail_keep_on.xml new file mode 100644 index 00000000..365ac84a --- /dev/null +++ b/app/src/leanback/res/drawable/ic_detail_keep_on.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_keyboard.xml b/app/src/leanback/res/drawable/ic_keyboard.xml new file mode 100644 index 00000000..44884fae --- /dev/null +++ b/app/src/leanback/res/drawable/ic_keyboard.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_keyboard_back.xml b/app/src/leanback/res/drawable/ic_keyboard_back.xml new file mode 100644 index 00000000..485fd8d2 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_keyboard_back.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_keyboard_left.xml b/app/src/leanback/res/drawable/ic_keyboard_left.xml new file mode 100644 index 00000000..4de32144 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_keyboard_left.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_keyboard_remote.xml b/app/src/leanback/res/drawable/ic_keyboard_remote.xml new file mode 100644 index 00000000..5949891c --- /dev/null +++ b/app/src/leanback/res/drawable/ic_keyboard_remote.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_keyboard_right.xml b/app/src/leanback/res/drawable/ic_keyboard_right.xml new file mode 100644 index 00000000..d2378ee0 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_keyboard_right.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_keyboard_search.xml b/app/src/leanback/res/drawable/ic_keyboard_search.xml new file mode 100644 index 00000000..5023dc87 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_keyboard_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_live_block.xml b/app/src/leanback/res/drawable/ic_live_block.xml new file mode 100644 index 00000000..96487ce8 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_live_block.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_live_boot.xml b/app/src/leanback/res/drawable/ic_live_boot.xml new file mode 100644 index 00000000..8ce6e6fc --- /dev/null +++ b/app/src/leanback/res/drawable/ic_live_boot.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_live_pass.xml b/app/src/leanback/res/drawable/ic_live_pass.xml new file mode 100644 index 00000000..a3314c43 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_live_pass.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_search_mic.xml b/app/src/leanback/res/drawable/ic_search_mic.xml new file mode 100644 index 00000000..d104b52f --- /dev/null +++ b/app/src/leanback/res/drawable/ic_search_mic.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_setting_delete.xml b/app/src/leanback/res/drawable/ic_setting_delete.xml new file mode 100644 index 00000000..fe52cfac --- /dev/null +++ b/app/src/leanback/res/drawable/ic_setting_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_site_cancel.xml b/app/src/leanback/res/drawable/ic_site_cancel.xml new file mode 100644 index 00000000..ab4b662b --- /dev/null +++ b/app/src/leanback/res/drawable/ic_site_cancel.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_site_change.xml b/app/src/leanback/res/drawable/ic_site_change.xml new file mode 100644 index 00000000..49e364ba --- /dev/null +++ b/app/src/leanback/res/drawable/ic_site_change.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_site_grid.xml b/app/src/leanback/res/drawable/ic_site_grid.xml new file mode 100644 index 00000000..c3a2bead --- /dev/null +++ b/app/src/leanback/res/drawable/ic_site_grid.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/leanback/res/drawable/ic_site_list.xml b/app/src/leanback/res/drawable/ic_site_list.xml new file mode 100644 index 00000000..f7ce5634 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_site_list.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_site_search.xml b/app/src/leanback/res/drawable/ic_site_search.xml new file mode 100644 index 00000000..5023dc87 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_site_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_site_select.xml b/app/src/leanback/res/drawable/ic_site_select.xml new file mode 100644 index 00000000..738c6892 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_site_select.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_subtitle_down.xml b/app/src/leanback/res/drawable/ic_subtitle_down.xml new file mode 100644 index 00000000..9652643d --- /dev/null +++ b/app/src/leanback/res/drawable/ic_subtitle_down.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_subtitle_large.xml b/app/src/leanback/res/drawable/ic_subtitle_large.xml new file mode 100644 index 00000000..64c880a0 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_subtitle_large.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_subtitle_reset.xml b/app/src/leanback/res/drawable/ic_subtitle_reset.xml new file mode 100644 index 00000000..76013aea --- /dev/null +++ b/app/src/leanback/res/drawable/ic_subtitle_reset.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_subtitle_small.xml b/app/src/leanback/res/drawable/ic_subtitle_small.xml new file mode 100644 index 00000000..363792bb --- /dev/null +++ b/app/src/leanback/res/drawable/ic_subtitle_small.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_subtitle_up.xml b/app/src/leanback/res/drawable/ic_subtitle_up.xml new file mode 100644 index 00000000..37ad456f --- /dev/null +++ b/app/src/leanback/res/drawable/ic_subtitle_up.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_vod_delete.xml b/app/src/leanback/res/drawable/ic_vod_delete.xml new file mode 100644 index 00000000..f6a3ae53 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_vod_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_vod_filter_off.xml b/app/src/leanback/res/drawable/ic_vod_filter_off.xml new file mode 100644 index 00000000..14e61c95 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_vod_filter_off.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/leanback/res/drawable/ic_vod_filter_on.xml b/app/src/leanback/res/drawable/ic_vod_filter_on.xml new file mode 100644 index 00000000..11c2b145 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_vod_filter_on.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/ic_widget_play.xml b/app/src/leanback/res/drawable/ic_widget_play.xml new file mode 100644 index 00000000..34d70a51 --- /dev/null +++ b/app/src/leanback/res/drawable/ic_widget_play.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/leanback/res/drawable/selector_channel.xml b/app/src/leanback/res/drawable/selector_channel.xml new file mode 100644 index 00000000..a8c20ac9 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_channel.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_epg.xml b/app/src/leanback/res/drawable/selector_epg.xml new file mode 100644 index 00000000..318a7508 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_epg.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_group.xml b/app/src/leanback/res/drawable/selector_group.xml new file mode 100644 index 00000000..37081f77 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_group.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_item.xml b/app/src/leanback/res/drawable/selector_item.xml new file mode 100644 index 00000000..61f04ce9 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_item.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_item_round.xml b/app/src/leanback/res/drawable/selector_item_round.xml new file mode 100644 index 00000000..0338ec0c --- /dev/null +++ b/app/src/leanback/res/drawable/selector_item_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_keyboard.xml b/app/src/leanback/res/drawable/selector_keyboard.xml new file mode 100644 index 00000000..f17cb388 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_keyboard.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_subtitle.xml b/app/src/leanback/res/drawable/selector_subtitle.xml new file mode 100644 index 00000000..9de12f18 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_subtitle.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_text.xml b/app/src/leanback/res/drawable/selector_text.xml new file mode 100644 index 00000000..c6934619 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_text.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_video.xml b/app/src/leanback/res/drawable/selector_video.xml new file mode 100644 index 00000000..b399be12 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_video.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_vod.xml b/app/src/leanback/res/drawable/selector_vod.xml new file mode 100644 index 00000000..08e82848 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_vod.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/selector_vod_oval.xml b/app/src/leanback/res/drawable/selector_vod_oval.xml new file mode 100644 index 00000000..7b3dabf2 --- /dev/null +++ b/app/src/leanback/res/drawable/selector_vod_oval.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_bottom_sheet.xml b/app/src/leanback/res/drawable/shape_bottom_sheet.xml new file mode 100644 index 00000000..f95f9d32 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_bottom_sheet.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_channel.xml b/app/src/leanback/res/drawable/shape_channel.xml new file mode 100644 index 00000000..ea193c06 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_channel.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_controller.xml b/app/src/leanback/res/drawable/shape_controller.xml new file mode 100644 index 00000000..bec4cc94 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_controller.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_epg_focused.xml b/app/src/leanback/res/drawable/shape_epg_focused.xml new file mode 100644 index 00000000..baddab9e --- /dev/null +++ b/app/src/leanback/res/drawable/shape_epg_focused.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_group.xml b/app/src/leanback/res/drawable/shape_group.xml new file mode 100644 index 00000000..e786aab0 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_group.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_activated.xml b/app/src/leanback/res/drawable/shape_item_activated.xml new file mode 100644 index 00000000..e8517914 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_activated.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_focused.xml b/app/src/leanback/res/drawable/shape_item_focused.xml new file mode 100644 index 00000000..a6e8c67a --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_focused.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_normal.xml b/app/src/leanback/res/drawable/shape_item_normal.xml new file mode 100644 index 00000000..edef8746 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_normal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_round_activated.xml b/app/src/leanback/res/drawable/shape_item_round_activated.xml new file mode 100644 index 00000000..65f476d6 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_round_activated.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_round_focused.xml b/app/src/leanback/res/drawable/shape_item_round_focused.xml new file mode 100644 index 00000000..c9869aa4 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_round_focused.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_round_normal.xml b/app/src/leanback/res/drawable/shape_item_round_normal.xml new file mode 100644 index 00000000..468adb02 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_round_normal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_item_selected.xml b/app/src/leanback/res/drawable/shape_item_selected.xml new file mode 100644 index 00000000..57f07b7d --- /dev/null +++ b/app/src/leanback/res/drawable/shape_item_selected.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_keyboard_focused.xml b/app/src/leanback/res/drawable/shape_keyboard_focused.xml new file mode 100644 index 00000000..3ecccbbf --- /dev/null +++ b/app/src/leanback/res/drawable/shape_keyboard_focused.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_keyboard_normal.xml b/app/src/leanback/res/drawable/shape_keyboard_normal.xml new file mode 100644 index 00000000..967d62ab --- /dev/null +++ b/app/src/leanback/res/drawable/shape_keyboard_normal.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_live_info.xml b/app/src/leanback/res/drawable/shape_live_info.xml new file mode 100644 index 00000000..dc4b2e12 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_live_info.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_live_list.xml b/app/src/leanback/res/drawable/shape_live_list.xml new file mode 100644 index 00000000..a2664c9f --- /dev/null +++ b/app/src/leanback/res/drawable/shape_live_list.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_subtitle_focused.xml b/app/src/leanback/res/drawable/shape_subtitle_focused.xml new file mode 100644 index 00000000..829b75d9 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_subtitle_focused.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_subtitle_pressed.xml b/app/src/leanback/res/drawable/shape_subtitle_pressed.xml new file mode 100644 index 00000000..cbcbfd03 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_subtitle_pressed.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_text_activated.xml b/app/src/leanback/res/drawable/shape_text_activated.xml new file mode 100644 index 00000000..28ed9761 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_text_activated.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_text_focused.xml b/app/src/leanback/res/drawable/shape_text_focused.xml new file mode 100644 index 00000000..2f2e5407 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_text_focused.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_text_normal.xml b/app/src/leanback/res/drawable/shape_text_normal.xml new file mode 100644 index 00000000..28ed9761 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_text_normal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_video_focused.xml b/app/src/leanback/res/drawable/shape_video_focused.xml new file mode 100644 index 00000000..59328d5a --- /dev/null +++ b/app/src/leanback/res/drawable/shape_video_focused.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_vod_focused.xml b/app/src/leanback/res/drawable/shape_vod_focused.xml new file mode 100644 index 00000000..1d4d00e4 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_vod_focused.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_vod_list.xml b/app/src/leanback/res/drawable/shape_vod_list.xml new file mode 100644 index 00000000..d0324ba5 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_vod_list.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_vod_name.xml b/app/src/leanback/res/drawable/shape_vod_name.xml new file mode 100644 index 00000000..a2c02176 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_vod_name.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_vod_oval_focused.xml b/app/src/leanback/res/drawable/shape_vod_oval_focused.xml new file mode 100644 index 00000000..0ef85827 --- /dev/null +++ b/app/src/leanback/res/drawable/shape_vod_oval_focused.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/drawable/shape_widget.xml b/app/src/leanback/res/drawable/shape_widget.xml new file mode 100644 index 00000000..78cfa62e --- /dev/null +++ b/app/src/leanback/res/drawable/shape_widget.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_cast.xml b/app/src/leanback/res/layout/activity_cast.xml new file mode 100644 index 00000000..5f8bf713 --- /dev/null +++ b/app/src/leanback/res/layout/activity_cast.xml @@ -0,0 +1,31 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_collect.xml b/app/src/leanback/res/layout/activity_collect.xml new file mode 100644 index 00000000..47b03c1f --- /dev/null +++ b/app/src/leanback/res/layout/activity_collect.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/app/src/leanback/res/layout/activity_file.xml b/app/src/leanback/res/layout/activity_file.xml new file mode 100644 index 00000000..a0f1b66c --- /dev/null +++ b/app/src/leanback/res/layout/activity_file.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_home.xml b/app/src/leanback/res/layout/activity_home.xml new file mode 100644 index 00000000..66eb1eb0 --- /dev/null +++ b/app/src/leanback/res/layout/activity_home.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_keep.xml b/app/src/leanback/res/layout/activity_keep.xml new file mode 100644 index 00000000..0cb60436 --- /dev/null +++ b/app/src/leanback/res/layout/activity_keep.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + diff --git a/app/src/leanback/res/layout/activity_live.xml b/app/src/leanback/res/layout/activity_live.xml new file mode 100644 index 00000000..e1eb7b74 --- /dev/null +++ b/app/src/leanback/res/layout/activity_live.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/leanback/res/layout/activity_push.xml b/app/src/leanback/res/layout/activity_push.xml new file mode 100644 index 00000000..1efec0c6 --- /dev/null +++ b/app/src/leanback/res/layout/activity_push.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_search.xml b/app/src/leanback/res/layout/activity_search.xml new file mode 100644 index 00000000..15fefc46 --- /dev/null +++ b/app/src/leanback/res/layout/activity_search.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/leanback/res/layout/activity_setting.xml b/app/src/leanback/res/layout/activity_setting.xml new file mode 100644 index 00000000..ee00263c --- /dev/null +++ b/app/src/leanback/res/layout/activity_setting.xml @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_setting_player.xml b/app/src/leanback/res/layout/activity_setting_player.xml new file mode 100644 index 00000000..8c568477 --- /dev/null +++ b/app/src/leanback/res/layout/activity_setting_player.xml @@ -0,0 +1,355 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_video.xml b/app/src/leanback/res/layout/activity_video.xml new file mode 100644 index 00000000..e9975eee --- /dev/null +++ b/app/src/leanback/res/layout/activity_video.xml @@ -0,0 +1,316 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/activity_vod.xml b/app/src/leanback/res/layout/activity_vod.xml new file mode 100644 index 00000000..c3cf6e8d --- /dev/null +++ b/app/src/leanback/res/layout/activity_vod.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/leanback/res/layout/adapter_array.xml b/app/src/leanback/res/layout/adapter_array.xml new file mode 100644 index 00000000..94840e35 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_array.xml @@ -0,0 +1,15 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_channel.xml b/app/src/leanback/res/layout/adapter_channel.xml new file mode 100644 index 00000000..a21460d9 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_channel.xml @@ -0,0 +1,45 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_config.xml b/app/src/leanback/res/layout/adapter_config.xml new file mode 100644 index 00000000..eef0b9a9 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_config.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_danmaku.xml b/app/src/leanback/res/layout/adapter_danmaku.xml new file mode 100644 index 00000000..3db66936 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_danmaku.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_doh.xml b/app/src/leanback/res/layout/adapter_doh.xml new file mode 100644 index 00000000..35faeb7a --- /dev/null +++ b/app/src/leanback/res/layout/adapter_doh.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_epg_data.xml b/app/src/leanback/res/layout/adapter_epg_data.xml new file mode 100644 index 00000000..9bb7bcc9 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_epg_data.xml @@ -0,0 +1,38 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_episode.xml b/app/src/leanback/res/layout/adapter_episode.xml new file mode 100644 index 00000000..26861fb8 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_episode.xml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_file.xml b/app/src/leanback/res/layout/adapter_file.xml new file mode 100644 index 00000000..7090bdf4 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_file.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_filter.xml b/app/src/leanback/res/layout/adapter_filter.xml new file mode 100644 index 00000000..9227ec84 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_filter.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_flag.xml b/app/src/leanback/res/layout/adapter_flag.xml new file mode 100644 index 00000000..2efba166 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_flag.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_func.xml b/app/src/leanback/res/layout/adapter_func.xml new file mode 100644 index 00000000..abdfb01a --- /dev/null +++ b/app/src/leanback/res/layout/adapter_func.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_group.xml b/app/src/leanback/res/layout/adapter_group.xml new file mode 100644 index 00000000..81974d5c --- /dev/null +++ b/app/src/leanback/res/layout/adapter_group.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_header.xml b/app/src/leanback/res/layout/adapter_header.xml new file mode 100644 index 00000000..c4b0e4a9 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_header.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_keyboard_icon.xml b/app/src/leanback/res/layout/adapter_keyboard_icon.xml new file mode 100644 index 00000000..2fa324c6 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_keyboard_icon.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_keyboard_text.xml b/app/src/leanback/res/layout/adapter_keyboard_text.xml new file mode 100644 index 00000000..ecd3e52a --- /dev/null +++ b/app/src/leanback/res/layout/adapter_keyboard_text.xml @@ -0,0 +1,13 @@ + + diff --git a/app/src/leanback/res/layout/adapter_live.xml b/app/src/leanback/res/layout/adapter_live.xml new file mode 100644 index 00000000..99a79f25 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_live.xml @@ -0,0 +1,47 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_parse.xml b/app/src/leanback/res/layout/adapter_parse.xml new file mode 100644 index 00000000..c9900081 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_parse.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_part.xml b/app/src/leanback/res/layout/adapter_part.xml new file mode 100644 index 00000000..19653368 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_part.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_progress.xml b/app/src/leanback/res/layout/adapter_progress.xml new file mode 100644 index 00000000..a58dd354 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_progress.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_quality.xml b/app/src/leanback/res/layout/adapter_quality.xml new file mode 100644 index 00000000..e149963c --- /dev/null +++ b/app/src/leanback/res/layout/adapter_quality.xml @@ -0,0 +1,17 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_quick.xml b/app/src/leanback/res/layout/adapter_quick.xml new file mode 100644 index 00000000..78885a4d --- /dev/null +++ b/app/src/leanback/res/layout/adapter_quick.xml @@ -0,0 +1,42 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_restore.xml b/app/src/leanback/res/layout/adapter_restore.xml new file mode 100644 index 00000000..da423e6c --- /dev/null +++ b/app/src/leanback/res/layout/adapter_restore.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_search_record.xml b/app/src/leanback/res/layout/adapter_search_record.xml new file mode 100644 index 00000000..cdd7b41f --- /dev/null +++ b/app/src/leanback/res/layout/adapter_search_record.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_search_word.xml b/app/src/leanback/res/layout/adapter_search_word.xml new file mode 100644 index 00000000..47e894de --- /dev/null +++ b/app/src/leanback/res/layout/adapter_search_word.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_site.xml b/app/src/leanback/res/layout/adapter_site.xml new file mode 100644 index 00000000..11413619 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_site.xml @@ -0,0 +1,35 @@ + + + + + + + + diff --git a/app/src/leanback/res/layout/adapter_track.xml b/app/src/leanback/res/layout/adapter_track.xml new file mode 100644 index 00000000..f709cf40 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_track.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_type.xml b/app/src/leanback/res/layout/adapter_type.xml new file mode 100644 index 00000000..bcc8f584 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_type.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_vod.xml b/app/src/leanback/res/layout/adapter_vod.xml new file mode 100644 index 00000000..e3df6dfd --- /dev/null +++ b/app/src/leanback/res/layout/adapter_vod.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_vod_list.xml b/app/src/leanback/res/layout/adapter_vod_list.xml new file mode 100644 index 00000000..c314c772 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_vod_list.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_vod_oval.xml b/app/src/leanback/res/layout/adapter_vod_oval.xml new file mode 100644 index 00000000..a2ba5081 --- /dev/null +++ b/app/src/leanback/res/layout/adapter_vod_oval.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/adapter_vod_rect.xml b/app/src/leanback/res/layout/adapter_vod_rect.xml new file mode 100644 index 00000000..1873be2e --- /dev/null +++ b/app/src/leanback/res/layout/adapter_vod_rect.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_buffer.xml b/app/src/leanback/res/layout/dialog_buffer.xml new file mode 100644 index 00000000..db4c88fc --- /dev/null +++ b/app/src/leanback/res/layout/dialog_buffer.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_config.xml b/app/src/leanback/res/layout/dialog_config.xml new file mode 100644 index 00000000..2c2e2830 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_config.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_danmaku.xml b/app/src/leanback/res/layout/dialog_danmaku.xml new file mode 100644 index 00000000..6a68cbc7 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_danmaku.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_desc.xml b/app/src/leanback/res/layout/dialog_desc.xml new file mode 100644 index 00000000..3658a463 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_desc.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_doh.xml b/app/src/leanback/res/layout/dialog_doh.xml new file mode 100644 index 00000000..a856de7e --- /dev/null +++ b/app/src/leanback/res/layout/dialog_doh.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_history.xml b/app/src/leanback/res/layout/dialog_history.xml new file mode 100644 index 00000000..29632553 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_history.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_live.xml b/app/src/leanback/res/layout/dialog_live.xml new file mode 100644 index 00000000..b2124f51 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_live.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_pass.xml b/app/src/leanback/res/layout/dialog_pass.xml new file mode 100644 index 00000000..522aa19d --- /dev/null +++ b/app/src/leanback/res/layout/dialog_pass.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_proxy.xml b/app/src/leanback/res/layout/dialog_proxy.xml new file mode 100644 index 00000000..a9a0c541 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_proxy.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_restore.xml b/app/src/leanback/res/layout/dialog_restore.xml new file mode 100644 index 00000000..29632553 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_restore.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_site.xml b/app/src/leanback/res/layout/dialog_site.xml new file mode 100644 index 00000000..74cf92c7 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_site.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_speed.xml b/app/src/leanback/res/layout/dialog_speed.xml new file mode 100644 index 00000000..15a49a75 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_speed.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_subtitle.xml b/app/src/leanback/res/layout/dialog_subtitle.xml new file mode 100644 index 00000000..fa01a014 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_subtitle.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_track.xml b/app/src/leanback/res/layout/dialog_track.xml new file mode 100644 index 00000000..9b351dd0 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_track.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_ua.xml b/app/src/leanback/res/layout/dialog_ua.xml new file mode 100644 index 00000000..018a366b --- /dev/null +++ b/app/src/leanback/res/layout/dialog_ua.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/dialog_update.xml b/app/src/leanback/res/layout/dialog_update.xml new file mode 100644 index 00000000..64c995b4 --- /dev/null +++ b/app/src/leanback/res/layout/dialog_update.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/fragment_vod.xml b/app/src/leanback/res/layout/fragment_vod.xml new file mode 100644 index 00000000..8681257d --- /dev/null +++ b/app/src/leanback/res/layout/fragment_vod.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_control_cast.xml b/app/src/leanback/res/layout/view_control_cast.xml new file mode 100644 index 00000000..d30325f7 --- /dev/null +++ b/app/src/leanback/res/layout/view_control_cast.xml @@ -0,0 +1,151 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_control_live.xml b/app/src/leanback/res/layout/view_control_live.xml new file mode 100644 index 00000000..3d02b328 --- /dev/null +++ b/app/src/leanback/res/layout/view_control_live.xml @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_control_seek.xml b/app/src/leanback/res/layout/view_control_seek.xml new file mode 100644 index 00000000..f168da95 --- /dev/null +++ b/app/src/leanback/res/layout/view_control_seek.xml @@ -0,0 +1,45 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_control_vod.xml b/app/src/leanback/res/layout/view_control_vod.xml new file mode 100644 index 00000000..f52b6923 --- /dev/null +++ b/app/src/leanback/res/layout/view_control_vod.xml @@ -0,0 +1,253 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_widget_cast.xml b/app/src/leanback/res/layout/view_widget_cast.xml new file mode 100644 index 00000000..9f26da6b --- /dev/null +++ b/app/src/leanback/res/layout/view_widget_cast.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_widget_live.xml b/app/src/leanback/res/layout/view_widget_live.xml new file mode 100644 index 00000000..d8f7fd17 --- /dev/null +++ b/app/src/leanback/res/layout/view_widget_live.xml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/layout/view_widget_vod.xml b/app/src/leanback/res/layout/view_widget_vod.xml new file mode 100644 index 00000000..d59cb179 --- /dev/null +++ b/app/src/leanback/res/layout/view_widget_vod.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/leanback/res/values-v27/styles.xml b/app/src/leanback/res/values-v27/styles.xml new file mode 100644 index 00000000..0fc60ad5 --- /dev/null +++ b/app/src/leanback/res/values-v27/styles.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/leanback/res/values-zh-rCN/strings.xml b/app/src/leanback/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..0af8836c --- /dev/null +++ b/app/src/leanback/res/values-zh-rCN/strings.xml @@ -0,0 +1,24 @@ + + + + 视频 + 直播 + 搜索 + 收藏 + 推送 + 投屏 + 设置 + 最近观看 + 更新推荐 + + + 正在等待连接… + + + %s”的搜索结果 + + + 推送剪贴板内容 + 使用手机扫描二维码,或于浏览器访问地址\n%s + + \ No newline at end of file diff --git a/app/src/leanback/res/values-zh-rTW/strings.xml b/app/src/leanback/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..5b6df9c9 --- /dev/null +++ b/app/src/leanback/res/values-zh-rTW/strings.xml @@ -0,0 +1,24 @@ + + + + 點播 + 直播 + 搜尋 + 收藏 + 推送 + 投屏 + 設定 + 最近觀看 + 更新推薦 + + + 正在等待連線… + + + %s」的搜尋結果 + + + 推送剪貼簿內容 + 使用手機掃描 QR Code,或於瀏覽器輸入網址\n%s + + \ No newline at end of file diff --git a/app/src/leanback/res/values/colors.xml b/app/src/leanback/res/values/colors.xml new file mode 100644 index 00000000..e41a4d77 --- /dev/null +++ b/app/src/leanback/res/values/colors.xml @@ -0,0 +1,7 @@ + + + @color/black + @color/black + @color/blue_500 + + \ No newline at end of file diff --git a/app/src/leanback/res/values/strings.xml b/app/src/leanback/res/values/strings.xml new file mode 100644 index 00000000..de082be2 --- /dev/null +++ b/app/src/leanback/res/values/strings.xml @@ -0,0 +1,24 @@ + + + + Vod + Live + Search + Keep + Push + Cast + Setting + History + Recommend + + + Waiting for connection… + + + Search results for %s + + + Push clipboard content + Scan QR Code or enter the URL in browser\n%s + + \ No newline at end of file diff --git a/app/src/leanback/res/values/styles.xml b/app/src/leanback/res/values/styles.xml new file mode 100644 index 00000000..d8c4bd8f --- /dev/null +++ b/app/src/leanback/res/values/styles.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8e652eea --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/css/style.css b/app/src/main/assets/css/style.css new file mode 100644 index 00000000..6603395a --- /dev/null +++ b/app/src/main/assets/css/style.css @@ -0,0 +1,5577 @@ +body { + --weui-BTN-DISABLED-FONT-COLOR: rgba(0, 0, 0, 0.2); +} + +body[data-weui-theme="dark"] { + --weui-BTN-DISABLED-FONT-COLOR: hsla(0, 0%, 100%, 0.2); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BTN-DISABLED-FONT-COLOR: hsla(0, 0%, 100%, 0.2); + } +} + +body { + --weui-BTN-DEFAULT-BG: #f2f2f2; +} + +body[data-weui-theme="dark"] { + --weui-BTN-DEFAULT-BG: hsla(0, 0%, 100%, 0.08); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BTN-DEFAULT-BG: hsla(0, 0%, 100%, 0.08); + } +} + +body { + --weui-BTN-DEFAULT-COLOR: #06ae56; +} + +body[data-weui-theme="dark"] { + --weui-BTN-DEFAULT-COLOR: hsla(0, 0%, 100%, 0.8); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BTN-DEFAULT-COLOR: hsla(0, 0%, 100%, 0.8); + } +} + +body { + --weui-BTN-DEFAULT-ACTIVE-BG: #e6e6e6; +} + +body[data-weui-theme="dark"] { + --weui-BTN-DEFAULT-ACTIVE-BG: hsla(0, 0%, 100%, 0.126); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BTN-DEFAULT-ACTIVE-BG: hsla(0, 0%, 100%, 0.126); + } +} + +body { + --weui-BTN-ACTIVE-MASK: rgba(0, 0, 0, 0.1); +} + +body[data-weui-theme="dark"] { + --weui-BTN-ACTIVE-MASK: hsla(0, 0%, 100%, 0.05); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BTN-ACTIVE-MASK: hsla(0, 0%, 100%, 0.05); + } +} + +body[data-weui-mode="care"] { + --weui-BTN-DEFAULT-COLOR: #018942; +} + +body[data-weui-mode="care"][data-weui-theme="dark"] { + --weui-BTN-DEFAULT-COLOR: hsla(0, 0%, 100%, 0.8); +} + +@media (prefers-color-scheme: dark) { + body[data-weui-mode="care"]:not([data-weui-theme="light"]) { + --weui-BTN-DEFAULT-COLOR: hsla(0, 0%, 100%, 0.8); + } +} + +body { + --weui-DIALOG-LINE-COLOR: rgba(0, 0, 0, 0.1); +} + +body[data-weui-theme="dark"] { + --weui-DIALOG-LINE-COLOR: hsla(0, 0%, 100%, 0.1); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-DIALOG-LINE-COLOR: hsla(0, 0%, 100%, 0.1); + } +} + +html { + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} + +body { + line-height: 1.6; + font-family: system-ui, -apple-system, Helvetica Neue, sans-serif; +} + +* { + margin: 0; + padding: 0; + outline: 0; +} + +a img { + border: 0; +} + +a { + text-decoration: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +input, +textarea { + caret-color: #07c160; + caret-color: var(--weui-BRAND); +} + +::-webkit-input-placeholder { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +::placeholder { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +body { + --weui-BG-0: #ededed; + --weui-BG-1: #f7f7f7; + --weui-BG-2: #fff; + --weui-BG-3: #f7f7f7; + --weui-BG-4: #4c4c4c; + --weui-BG-5: #fff; + --weui-FG-0: rgba(0, 0, 0, 0.9); + --weui-FG-HALF: rgba(0, 0, 0, 0.9); + --weui-FG-1: rgba(0, 0, 0, 0.5); + --weui-FG-2: rgba(0, 0, 0, 0.3); + --weui-FG-3: rgba(0, 0, 0, 0.1); + --weui-FG-4: rgba(0, 0, 0, 0.15); + --weui-RED: #fa5151; + --weui-ORANGE: #fa9d3b; + --weui-YELLOW: #ffc300; + --weui-GREEN: #91d300; + --weui-LIGHTGREEN: #95ec69; + --weui-BRAND: #07c160; + --weui-BLUE: #10aeff; + --weui-INDIGO: #1485ee; + --weui-PURPLE: #6467f0; + --weui-WHITE: #fff; + --weui-LINK: #576b95; + --weui-TEXTGREEN: #06ae56; + --weui-FG: #000; + --weui-BG: #fff; + --weui-TAG-TEXT-ORANGE: #fa9d3b; + --weui-TAG-BACKGROUND-ORANGE: rgba(250, 157, 59, 0.1); + --weui-TAG-TEXT-GREEN: #06ae56; + --weui-TAG-BACKGROUND-GREEN: rgba(6, 174, 86, 0.1); + --weui-TAG-TEXT-BLUE: #10aeff; + --weui-TAG-BACKGROUND-BLUE: rgba(16, 174, 255, 0.1); + --weui-TAG-TEXT-BLACK: rgba(0, 0, 0, 0.5); + --weui-TAG-BACKGROUND-BLACK: rgba(0, 0, 0, 0.05); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BG-0: #111; + --weui-BG-1: #1e1e1e; + --weui-BG-2: #191919; + --weui-BG-3: #202020; + --weui-BG-4: #404040; + --weui-BG-5: #2c2c2c; + --weui-FG-0: hsla(0, 0%, 100%, 0.8); + --weui-FG-HALF: hsla(0, 0%, 100%, 0.6); + --weui-FG-1: hsla(0, 0%, 100%, 0.5); + --weui-FG-2: hsla(0, 0%, 100%, 0.3); + --weui-FG-3: hsla(0, 0%, 100%, 0.1); + --weui-FG-4: hsla(0, 0%, 100%, 0.15); + --weui-RED: #fa5151; + --weui-ORANGE: #c87d2f; + --weui-YELLOW: #cc9c00; + --weui-GREEN: #74a800; + --weui-LIGHTGREEN: #3eb575; + --weui-BRAND: #07c160; + --weui-BLUE: #10aeff; + --weui-INDIGO: #1196ff; + --weui-PURPLE: #8183ff; + --weui-WHITE: hsla(0, 0%, 100%, 0.8); + --weui-LINK: #7d90a9; + --weui-TEXTGREEN: #259c5c; + --weui-FG: #fff; + --weui-BG: #000; + --weui-TAG-TEXT-ORANGE: rgba(250, 157, 59, 0.6); + --weui-TAG-BACKGROUND-ORANGE: rgba(250, 157, 59, 0.1); + --weui-TAG-TEXT-GREEN: rgba(6, 174, 86, 0.6); + --weui-TAG-BACKGROUND-GREEN: rgba(6, 174, 86, 0.1); + --weui-TAG-TEXT-BLUE: rgba(16, 174, 255, 0.6); + --weui-TAG-BACKGROUND-BLUE: rgba(16, 174, 255, 0.1); + --weui-TAG-TEXT-BLACK: hsla(0, 0%, 100%, 0.5); + --weui-TAG-BACKGROUND-BLACK: hsla(0, 0%, 100%, 0.05); + } +} + +body[data-weui-theme="dark"] { + --weui-BG-0: #111; + --weui-BG-1: #1e1e1e; + --weui-BG-2: #191919; + --weui-BG-3: #202020; + --weui-BG-4: #404040; + --weui-BG-5: #2c2c2c; + --weui-FG-0: hsla(0, 0%, 100%, 0.8); + --weui-FG-HALF: hsla(0, 0%, 100%, 0.6); + --weui-FG-1: hsla(0, 0%, 100%, 0.5); + --weui-FG-2: hsla(0, 0%, 100%, 0.3); + --weui-FG-3: hsla(0, 0%, 100%, 0.1); + --weui-FG-4: hsla(0, 0%, 100%, 0.15); + --weui-RED: #fa5151; + --weui-ORANGE: #c87d2f; + --weui-YELLOW: #cc9c00; + --weui-GREEN: #74a800; + --weui-LIGHTGREEN: #3eb575; + --weui-BRAND: #07c160; + --weui-BLUE: #10aeff; + --weui-INDIGO: #1196ff; + --weui-PURPLE: #8183ff; + --weui-WHITE: hsla(0, 0%, 100%, 0.8); + --weui-LINK: #7d90a9; + --weui-TEXTGREEN: #259c5c; + --weui-FG: #fff; + --weui-BG: #000; + --weui-TAG-TEXT-ORANGE: rgba(250, 157, 59, 0.6); + --weui-TAG-BACKGROUND-ORANGE: rgba(250, 157, 59, 0.1); + --weui-TAG-TEXT-GREEN: rgba(6, 174, 86, 0.6); + --weui-TAG-BACKGROUND-GREEN: rgba(6, 174, 86, 0.1); + --weui-TAG-TEXT-BLUE: rgba(16, 174, 255, 0.6); + --weui-TAG-BACKGROUND-BLUE: rgba(16, 174, 255, 0.1); + --weui-TAG-TEXT-BLACK: hsla(0, 0%, 100%, 0.5); + --weui-TAG-BACKGROUND-BLACK: hsla(0, 0%, 100%, 0.05); +} + +body[data-weui-mode="care"] { + --weui-BG-0: #ededed; + --weui-BG-1: #f7f7f7; + --weui-BG-2: #fff; + --weui-BG-3: #f7f7f7; + --weui-BG-4: #4c4c4c; + --weui-BG-5: #fff; + --weui-FG-0: #000; + --weui-FG-HALF: #000; + --weui-FG-1: rgba(0, 0, 0, 0.6); + --weui-FG-2: rgba(0, 0, 0, 0.42); + --weui-FG-3: rgba(0, 0, 0, 0.1); + --weui-FG-4: rgba(0, 0, 0, 0.15); + --weui-RED: #dc3636; + --weui-ORANGE: #e17719; + --weui-YELLOW: #bb8e00; + --weui-GREEN: #4f8400; + --weui-LIGHTGREEN: #2e8800; + --weui-BRAND: #018942; + --weui-BLUE: #007dbb; + --weui-INDIGO: #0075e2; + --weui-PURPLE: #6265f1; + --weui-WHITE: #fff; + --weui-LINK: #576b95; + --weui-TEXTGREEN: #06ae56; + --weui-FG: #000; + --weui-BG: #fff; + --weui-TAG-TEXT-ORANGE: #e17719; + --weui-TAG-BACKGROUND-ORANGE: rgba(225, 119, 25, 0.1); + --weui-TAG-TEXT-GREEN: #06ae56; + --weui-TAG-BACKGROUND-GREEN: rgba(6, 174, 86, 0.1); + --weui-TAG-TEXT-BLUE: #007dbb; + --weui-TAG-BACKGROUND-BLUE: rgba(0, 125, 187, 0.1); + --weui-TAG-TEXT-BLACK: rgba(0, 0, 0, 0.5); + --weui-TAG-BACKGROUND-BLACK: rgba(0, 0, 0, 0.05); +} + +@media (prefers-color-scheme: dark) { + body[data-weui-mode="care"]:not([data-weui-theme="light"]) { + --weui-BG-0: #111; + --weui-BG-1: #1e1e1e; + --weui-BG-2: #191919; + --weui-BG-3: #202020; + --weui-BG-4: #404040; + --weui-BG-5: #2c2c2c; + --weui-FG-0: hsla(0, 0%, 100%, 0.85); + --weui-FG-HALF: hsla(0, 0%, 100%, 0.65); + --weui-FG-1: hsla(0, 0%, 100%, 0.55); + --weui-FG-2: hsla(0, 0%, 100%, 0.35); + --weui-FG-3: hsla(0, 0%, 100%, 0.1); + --weui-FG-4: hsla(0, 0%, 100%, 0.15); + --weui-RED: #fa5151; + --weui-ORANGE: #c87d2f; + --weui-YELLOW: #cc9c00; + --weui-GREEN: #74a800; + --weui-LIGHTGREEN: #3eb575; + --weui-BRAND: #07c160; + --weui-BLUE: #10aeff; + --weui-INDIGO: #1196ff; + --weui-PURPLE: #8183ff; + --weui-WHITE: hsla(0, 0%, 100%, 0.8); + --weui-LINK: #7d90a9; + --weui-TEXTGREEN: #259c5c; + --weui-FG: #fff; + --weui-BG: #000; + --weui-TAG-TEXT-ORANGE: rgba(250, 157, 59, 0.6); + --weui-TAG-BACKGROUND-ORANGE: rgba(250, 157, 59, 0.1); + --weui-TAG-TEXT-GREEN: rgba(6, 174, 86, 0.6); + --weui-TAG-BACKGROUND-GREEN: rgba(6, 174, 86, 0.1); + --weui-TAG-TEXT-BLUE: rgba(16, 174, 255, 0.6); + --weui-TAG-BACKGROUND-BLUE: rgba(16, 174, 255, 0.1); + --weui-TAG-TEXT-BLACK: hsla(0, 0%, 100%, 0.5); + --weui-TAG-BACKGROUND-BLACK: hsla(0, 0%, 100%, 0.05); + } +} + +body[data-weui-mode="care"][data-weui-theme="dark"] { + --weui-BG-0: #111; + --weui-BG-1: #1e1e1e; + --weui-BG-2: #191919; + --weui-BG-3: #202020; + --weui-BG-4: #404040; + --weui-BG-5: #2c2c2c; + --weui-FG-0: hsla(0, 0%, 100%, 0.85); + --weui-FG-HALF: hsla(0, 0%, 100%, 0.65); + --weui-FG-1: hsla(0, 0%, 100%, 0.55); + --weui-FG-2: hsla(0, 0%, 100%, 0.35); + --weui-FG-3: hsla(0, 0%, 100%, 0.1); + --weui-FG-4: hsla(0, 0%, 100%, 0.15); + --weui-RED: #fa5151; + --weui-ORANGE: #c87d2f; + --weui-YELLOW: #cc9c00; + --weui-GREEN: #74a800; + --weui-LIGHTGREEN: #3eb575; + --weui-BRAND: #07c160; + --weui-BLUE: #10aeff; + --weui-INDIGO: #1196ff; + --weui-PURPLE: #8183ff; + --weui-WHITE: hsla(0, 0%, 100%, 0.8); + --weui-LINK: #7d90a9; + --weui-TEXTGREEN: #259c5c; + --weui-FG: #fff; + --weui-BG: #000; + --weui-TAG-TEXT-ORANGE: rgba(250, 157, 59, 0.6); + --weui-TAG-BACKGROUND-ORANGE: rgba(250, 157, 59, 0.1); + --weui-TAG-TEXT-GREEN: rgba(6, 174, 86, 0.6); + --weui-TAG-BACKGROUND-GREEN: rgba(6, 174, 86, 0.1); + --weui-TAG-TEXT-BLUE: rgba(16, 174, 255, 0.6); + --weui-TAG-BACKGROUND-BLUE: rgba(16, 174, 255, 0.1); + --weui-TAG-TEXT-BLACK: hsla(0, 0%, 100%, 0.5); + --weui-TAG-BACKGROUND-BLACK: hsla(0, 0%, 100%, 0.05); +} + +body { + --weui-BG-COLOR-ACTIVE: #ececec; +} + +body[data-weui-theme="dark"] { + --weui-BG-COLOR-ACTIVE: #373737; +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) { + --weui-BG-COLOR-ACTIVE: #373737; + } +} + +[class*=" weui-icon-"][class*=" weui-icon-"], +[class*=" weui-icon-"][class^="weui-icon-"], +[class^="weui-icon-"][class*=" weui-icon-"], +[class^="weui-icon-"][class^="weui-icon-"] { + display: inline-block; + vertical-align: middle; + font-size: 10px; + width: 2.4em; + height: 2.4em; + -webkit-mask-position: 50% 50%; + mask-position: 50% 50%; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100%; + mask-size: 100%; + background-color: currentColor; +} + +.weui-icon-circle { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%221000%22%20height%3D%221000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M500%20916.667C269.881%20916.667%2083.333%20730.119%2083.333%20500%2083.333%20269.881%20269.881%2083.333%20500%2083.333c230.119%200%20416.667%20186.548%20416.667%20416.667%200%20230.119-186.548%20416.667-416.667%20416.667zm0-50c202.504%200%20366.667-164.163%20366.667-366.667%200-202.504-164.163-366.667-366.667-366.667-202.504%200-366.667%20164.163-366.667%20366.667%200%20202.504%20164.163%20366.667%20366.667%20366.667z%22%20fill-rule%3D%22evenodd%22%20fill-opacity%3D%22.9%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%221000%22%20height%3D%221000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M500%20916.667C269.881%20916.667%2083.333%20730.119%2083.333%20500%2083.333%20269.881%20269.881%2083.333%20500%2083.333c230.119%200%20416.667%20186.548%20416.667%20416.667%200%20230.119-186.548%20416.667-416.667%20416.667zm0-50c202.504%200%20366.667-164.163%20366.667-366.667%200-202.504-164.163-366.667-366.667-366.667-202.504%200-366.667%20164.163-366.667%20366.667%200%20202.504%20164.163%20366.667%20366.667%20366.667z%22%20fill-rule%3D%22evenodd%22%20fill-opacity%3D%22.9%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-download { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M11.25%2012.04l-1.72-1.72-1.06%201.06%202.828%202.83a1%201%200%20001.414-.001l2.828-2.828-1.06-1.061-1.73%201.73V7h-1.5v5.04zm0-5.04V2h1.5v5h6.251c.55%200%20.999.446.999.996v13.008a.998.998%200%2001-.996.996H4.996A.998.998%200%20014%2021.004V7.996A1%201%200%20014.999%207h6.251z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M11.25%2012.04l-1.72-1.72-1.06%201.06%202.828%202.83a1%201%200%20001.414-.001l2.828-2.828-1.06-1.061-1.73%201.73V7h-1.5v5.04zm0-5.04V2h1.5v5h6.251c.55%200%20.999.446.999.996v13.008a.998.998%200%2001-.996.996H4.996A.998.998%200%20014%2021.004V7.996A1%201%200%20014.999%207h6.251z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-info { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-.75-12v7h1.5v-7h-1.5zM12%209a1%201%200%20100-2%201%201%200%20000%202z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-.75-12v7h1.5v-7h-1.5zM12%209a1%201%200%20100-2%201%201%200%20000%202z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-safe-success { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201000%201000%22%3E%3Cpath%20d%3D%22M500.9%204.6C315.5%2046.7%20180.4%2093.1%2057.6%20132c0%20129.3.2%20231.7.2%20339.7%200%20304.2%20248.3%20471.6%20443.1%20523.7C695.7%20943.3%20944%20775.9%20944%20471.7c0-108%20.2-210.4.2-339.7C821.4%2093.1%20686.3%2046.7%20500.9%204.6zm248.3%20349.1l-299.7%20295c-2.1%202-5.3%202-7.4-.1L304.4%20506.1c-2-2.1-2.3-5.7-.6-8l18.3-24.9c1.7-2.3%205-2.8%207.2-1l112.2%2086c2.3%201.8%206%201.7%208.1-.1l274.7-228.9c2.2-1.8%205.7-1.7%207.7.3l17%2016.8c2.2%202.1%202.2%205.3.2%207.4z%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%23070202%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201000%201000%22%3E%3Cpath%20d%3D%22M500.9%204.6C315.5%2046.7%20180.4%2093.1%2057.6%20132c0%20129.3.2%20231.7.2%20339.7%200%20304.2%20248.3%20471.6%20443.1%20523.7C695.7%20943.3%20944%20775.9%20944%20471.7c0-108%20.2-210.4.2-339.7C821.4%2093.1%20686.3%2046.7%20500.9%204.6zm248.3%20349.1l-299.7%20295c-2.1%202-5.3%202-7.4-.1L304.4%20506.1c-2-2.1-2.3-5.7-.6-8l18.3-24.9c1.7-2.3%205-2.8%207.2-1l112.2%2086c2.3%201.8%206%201.7%208.1-.1l274.7-228.9c2.2-1.8%205.7-1.7%207.7.3l17%2016.8c2.2%202.1%202.2%205.3.2%207.4z%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%23070202%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-safe-warn { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201000%201000%22%3E%3Cpath%20d%3D%22M500.9%204.5c-185.4%2042-320.4%2088.4-443.2%20127.3%200%20129.3.2%20231.7.2%20339.6%200%20304.1%20248.2%20471.4%20443%20523.6%20194.7-52.2%20443-219.5%20443-523.6%200-107.9.2-210.3.2-339.6C821.3%2092.9%20686.2%2046.5%20500.9%204.5zm-26.1%20271.1h52.1c5.8%200%2010.3%204.7%2010.1%2010.4l-11.6%20313.8c-.1%202.8-2.5%205.2-5.4%205.2h-38.2c-2.9%200-5.3-2.3-5.4-5.2L464.8%20286c-.2-5.8%204.3-10.4%2010-10.4zm26.1%20448.3c-20.2%200-36.5-16.3-36.5-36.5s16.3-36.5%2036.5-36.5%2036.5%2016.3%2036.5%2036.5-16.4%2036.5-36.5%2036.5z%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%23020202%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%201000%201000%22%3E%3Cpath%20d%3D%22M500.9%204.5c-185.4%2042-320.4%2088.4-443.2%20127.3%200%20129.3.2%20231.7.2%20339.6%200%20304.1%20248.2%20471.4%20443%20523.6%20194.7-52.2%20443-219.5%20443-523.6%200-107.9.2-210.3.2-339.6C821.3%2092.9%20686.2%2046.5%20500.9%204.5zm-26.1%20271.1h52.1c5.8%200%2010.3%204.7%2010.1%2010.4l-11.6%20313.8c-.1%202.8-2.5%205.2-5.4%205.2h-38.2c-2.9%200-5.3-2.3-5.4-5.2L464.8%20286c-.2-5.8%204.3-10.4%2010-10.4zm26.1%20448.3c-20.2%200-36.5-16.3-36.5-36.5s16.3-36.5%2036.5-36.5%2036.5%2016.3%2036.5%2036.5-16.4%2036.5-36.5%2036.5z%22%20fill-rule%3D%22evenodd%22%20clip-rule%3D%22evenodd%22%20fill%3D%22%23020202%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-success { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-1.177-7.86l-2.765-2.767L7%2012.431l3.119%203.121a1%201%200%20001.414%200l5.952-5.95-1.062-1.062-5.6%205.6z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-1.177-7.86l-2.765-2.767L7%2012.431l3.119%203.121a1%201%200%20001.414%200l5.952-5.95-1.062-1.062-5.6%205.6z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-success-circle { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6zm-1.172-6.242l5.809-5.808.848.849-5.95%205.95a1%201%200%2001-1.414%200L7%2012.426l.849-.849%202.98%202.98z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6zm-1.172-6.242l5.809-5.808.848.849-5.95%205.95a1%201%200%2001-1.414%200L7%2012.426l.849-.849%202.98%202.98z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-success-no-circle { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.657%2018.435L3%2012.778l1.414-1.414%204.95%204.95L20.678%205l1.414%201.414-12.02%2012.021a1%201%200%2001-1.415%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.657%2018.435L3%2012.778l1.414-1.414%204.95%204.95L20.678%205l1.414%201.414-12.02%2012.021a1%201%200%2001-1.415%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-waiting { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.75%2011.38V6h-1.5v6l4.243%204.243%201.06-1.06-3.803-3.804zM12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.75%2011.38V6h-1.5v6l4.243%204.243%201.06-1.06-3.803-3.804zM12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-waiting-circle { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.6%2011.503l3.891%203.891-.848.849L11.4%2012V6h1.2v5.503zM12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.6%2011.503l3.891%203.891-.848.849L11.4%2012V6h1.2v5.503zM12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-warn { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-.763-15.864l.11%207.596h1.305l.11-7.596h-1.525zm.759%2010.967c.512%200%20.902-.383.902-.882%200-.5-.39-.882-.902-.882a.878.878%200%2000-.896.882c0%20.499.396.882.896.882z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-.763-15.864l.11%207.596h1.305l.11-7.596h-1.525zm.759%2010.967c.512%200%20.902-.383.902-.882%200-.5-.39-.882-.902-.882a.878.878%200%2000-.896.882c0%20.499.396.882.896.882z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-info-circle { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6zM11.4%2010h1.2v7h-1.2v-7zm.6-1a1%201%200%20110-2%201%201%200%20010%202z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6zM11.4%2010h1.2v7h-1.2v-7zm.6-1a1%201%200%20110-2%201%201%200%20010%202z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-cancel { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6z%22%20fill-rule%3D%22nonzero%22%2F%3E%3Cpath%20d%3D%22M12.849%2012l3.11%203.111-.848.849L12%2012.849l-3.111%203.11-.849-.848L11.151%2012l-3.11-3.111.848-.849L12%2011.151l3.111-3.11.849.848L12.849%2012z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cg%20fill-rule%3D%22evenodd%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6z%22%20fill-rule%3D%22nonzero%22%2F%3E%3Cpath%20d%3D%22M12.849%2012l3.11%203.111-.848.849L12%2012.849l-3.111%203.11-.849-.848L11.151%2012l-3.11-3.111.848-.849L12%2011.151l3.111-3.11.849.848L12.849%2012z%22%2F%3E%3C%2Fg%3E%3C%2Fsvg%3E); +} + +.weui-icon-search { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M16.31%2015.561l4.114%204.115-.848.848-4.123-4.123a7%207%200%2011.857-.84zM16.8%2011a5.8%205.8%200%2010-11.6%200%205.8%205.8%200%200011.6%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M16.31%2015.561l4.114%204.115-.848.848-4.123-4.123a7%207%200%2011.857-.84zM16.8%2011a5.8%205.8%200%2010-11.6%200%205.8%205.8%200%200011.6%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-clear { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M13.06%2012l3.006-3.005-1.06-1.06L12%2010.938%208.995%207.934l-1.06%201.06L10.938%2012l-3.005%203.005%201.06%201.06L12%2013.062l3.005%203.005%201.06-1.06L13.062%2012zM12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M13.06%2012l3.006-3.005-1.06-1.06L12%2010.938%208.995%207.934l-1.06%201.06L10.938%2012l-3.005%203.005%201.06%201.06L12%2013.062l3.005%203.005%201.06-1.06L13.062%2012zM12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-back { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm1.999-6.563L10.68%2012%2014%208.562%2012.953%207.5%209.29%2011.277a1.045%201.045%200%20000%201.446l3.663%203.777L14%2015.437z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm1.999-6.563L10.68%2012%2014%208.562%2012.953%207.5%209.29%2011.277a1.045%201.045%200%20000%201.446l3.663%203.777L14%2015.437z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-delete { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M6.774%206.4l.812%2013.648a.8.8%200%2000.798.752h7.232a.8.8%200%2000.798-.752L17.226%206.4H6.774zm11.655%200l-.817%2013.719A2%202%200%200115.616%2022H8.384a2%202%200%2001-1.996-1.881L5.571%206.4H3.5v-.7a.5.5%200%2001.5-.5h16a.5.5%200%2001.5.5v.7h-2.071zM14%203a.5.5%200%2001.5.5v.7h-5v-.7A.5.5%200%200110%203h4zM9.5%209h1.2l.5%209H10l-.5-9zm3.8%200h1.2l-.5%209h-1.2l.5-9z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M6.774%206.4l.812%2013.648a.8.8%200%2000.798.752h7.232a.8.8%200%2000.798-.752L17.226%206.4H6.774zm11.655%200l-.817%2013.719A2%202%200%200115.616%2022H8.384a2%202%200%2001-1.996-1.881L5.571%206.4H3.5v-.7a.5.5%200%2001.5-.5h16a.5.5%200%2001.5.5v.7h-2.071zM14%203a.5.5%200%2001.5.5v.7h-5v-.7A.5.5%200%200110%203h4zM9.5%209h1.2l.5%209H10l-.5-9zm3.8%200h1.2l-.5%209h-1.2l.5-9z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-success-no-circle-thin { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.864%2016.617l-5.303-5.303-1.061%201.06%205.657%205.657a1%201%200%20001.414%200L21.238%206.364l-1.06-1.06L8.864%2016.616z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.864%2016.617l-5.303-5.303-1.061%201.06%205.657%205.657a1%201%200%20001.414%200L21.238%206.364l-1.06-1.06L8.864%2016.616z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-arrow { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-arrow-bold { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2212%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.157%2012.711L4.5%2018.368l-1.414-1.414%204.95-4.95-4.95-4.95L4.5%205.64l5.657%205.657a1%201%200%20010%201.414z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20height%3D%2224%22%20width%3D%2212%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10.157%2012.711L4.5%2018.368l-1.414-1.414%204.95-4.95-4.95-4.95L4.5%205.64l5.657%205.657a1%201%200%20010%201.414z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-back-arrow { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M3.343%2012l7.071%207.071L9%2020.485l-7.778-7.778a1%201%200%20010-1.414L9%203.515l1.414%201.414L3.344%2012z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M3.343%2012l7.071%207.071L9%2020.485l-7.778-7.778a1%201%200%20010-1.414L9%203.515l1.414%201.414L3.344%2012z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-back-arrow-thin { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10%2019.438L8.955%2020.5l-7.666-7.79a1.02%201.02%200%20010-1.42L8.955%203.5%2010%204.563%202.682%2012%2010%2019.438z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10%2019.438L8.955%2020.5l-7.666-7.79a1.02%201.02%200%20010-1.42L8.955%203.5%2010%204.563%202.682%2012%2010%2019.438z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-close { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2010.586l5.657-5.657%201.414%201.414L13.414%2012l5.657%205.657-1.414%201.414L12%2013.414l-5.657%205.657-1.414-1.414L10.586%2012%204.929%206.343%206.343%204.93%2012%2010.586z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2010.586l5.657-5.657%201.414%201.414L13.414%2012l5.657%205.657-1.414%201.414L12%2013.414l-5.657%205.657-1.414-1.414L10.586%2012%204.929%206.343%206.343%204.93%2012%2010.586z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-close-thin { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.25%2010.693L6.057%204.5%205%205.557l6.193%206.193L5%2017.943%206.057%2019l6.193-6.193L18.443%2019l1.057-1.057-6.193-6.193L19.5%205.557%2018.443%204.5z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.25%2010.693L6.057%204.5%205%205.557l6.193%206.193L5%2017.943%206.057%2019l6.193-6.193L18.443%2019l1.057-1.057-6.193-6.193L19.5%205.557%2018.443%204.5z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-back-circle { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6zm1.999-5.363L12.953%2016.5%209.29%2012.723a1.045%201.045%200%20010-1.446L12.953%207.5%2014%208.563%2010.68%2012%2014%2015.438z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm0-1.2a8.8%208.8%200%20100-17.6%208.8%208.8%200%20000%2017.6zm1.999-5.363L12.953%2016.5%209.29%2012.723a1.045%201.045%200%20010-1.446L12.953%207.5%2014%208.563%2010.68%2012%2014%2015.438z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-icon-success { + color: #07c160; + color: var(--weui-BRAND); +} + +.weui-icon-waiting { + color: #10aeff; + color: var(--weui-BLUE); +} + +.weui-icon-warn { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-icon-info { + color: #10aeff; + color: var(--weui-BLUE); +} + +.weui-icon-success-circle, +.weui-icon-success-no-circle, +.weui-icon-success-no-circle-thin { + color: #07c160; + color: var(--weui-BRAND); +} + +.weui-icon-waiting-circle { + color: #10aeff; + color: var(--weui-BLUE); +} + +.weui-icon-circle { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +.weui-icon-download { + color: #07c160; + color: var(--weui-BRAND); +} + +.weui-icon-info-circle { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +.weui-icon-safe-success { + color: #07c160; + color: var(--weui-BRAND); +} + +.weui-icon-safe-warn { + color: #ffc300; + color: var(--weui-YELLOW); +} + +.weui-icon-cancel { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-icon-search { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-icon-clear { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +.weui-icon-clear:active { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-icon-delete.weui-icon_gallery-delete { + color: #fff; + color: var(--weui-WHITE); +} + +.weui-icon-arrow-bold.weui-icon-arrow, +.weui-icon-arrow-bold.weui-icon-arrow-bold, +.weui-icon-arrow-bold.weui-icon-back-arrow, +.weui-icon-arrow-bold.weui-icon-back-arrow-thin, +.weui-icon-arrow.weui-icon-arrow, +.weui-icon-arrow.weui-icon-arrow-bold, +.weui-icon-arrow.weui-icon-back-arrow, +.weui-icon-arrow.weui-icon-back-arrow-thin, +.weui-icon-back-arrow-thin.weui-icon-arrow, +.weui-icon-back-arrow-thin.weui-icon-arrow-bold, +.weui-icon-back-arrow-thin.weui-icon-back-arrow, +.weui-icon-back-arrow-thin.weui-icon-back-arrow-thin, +.weui-icon-back-arrow.weui-icon-arrow, +.weui-icon-back-arrow.weui-icon-arrow-bold, +.weui-icon-back-arrow.weui-icon-back-arrow, +.weui-icon-back-arrow.weui-icon-back-arrow-thin { + width: 1.2em; +} + +.weui-icon-arrow, +.weui-icon-arrow-bold { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +.weui-icon-back, +.weui-icon-back-arrow, +.weui-icon-back-arrow-thin, +.weui-icon-back-circle { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-icon_msg.weui-icon_msg { + width: 6.4em; + height: 6.4em; +} + +.weui-icon_msg.weui-icon_msg.weui-icon-warn { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-icon_msg.weui-icon_msg.weui-icon-info-circle { + color: #10aeff; + color: var(--weui-BLUE); +} + +.weui-icon_msg-primary.weui-icon_msg-primary { + width: 6.4em; + height: 6.4em; +} + +.weui-icon_msg-primary.weui-icon_msg-primary.weui-icon-warn { + color: #ffc300; + color: var(--weui-YELLOW); +} + +.weui-hidden_abs { + opacity: 0; +} + +.weui-hidden-space:empty:before, +.weui-hidden_abs { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; +} + +.weui-hidden-space:empty:before { + content: "\00A0"; +} + +.weui-a11y-combo { + position: relative; +} + +.weui-a11y-combo__helper { + opacity: 0; + position: absolute; + width: 100%; + height: 100%; + overflow: hidden; +} + +.weui-a11y-combo__content { + position: relative; + z-index: 1; +} + +.weui-wa-hotarea-el { + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + min-width: 44px; + min-height: 44px; + width: 100%; + height: 100%; +} + +.weui-wa-hotarea, +.weui-wa-hotarea-el__wrp, +.weui-wa-hotarea_before { + position: relative; +} + +.weui-wa-hotarea-el__wrp a, +.weui-wa-hotarea-el__wrp navigator, +.weui-wa-hotarea_before a, +.weui-wa-hotarea_before navigator, +.weui-wa-hotarea a, +.weui-wa-hotarea navigator { + position: relative; + z-index: 1; +} + +.weui-wa-hotarea:after, +.weui-wa-hotarea_before:before { + content: ""; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + min-width: 44px; + min-height: 44px; + width: 100%; + height: 100%; +} + +.weui-link { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-link, +.weui-link:visited { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-link:active { + opacity: 0.5; +} + +.weui-btn { + position: relative; + display: block; + width: 184px; + margin-left: auto; + margin-right: auto; + padding: 8px 24px; + box-sizing: border-box; + font-weight: 700; + font-size: 17px; + text-align: center; + text-decoration: none; + color: #fff; + line-height: 1.88235294; + border-radius: 8px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-user-select: none; + user-select: none; +} + +.weui-btn:before { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.1); + background-color: var(--weui-BTN-ACTIVE-MASK); + border-radius: 8px; +} + +.weui-btn:not(.weui-btn_disabled):not(.weui-btn_loading):active:before, +.weui-btn:not([disabled]):not(.weui-btn_loading):active:before { + content: ""; +} + +.weui-btn_block { + width: auto; +} + +.weui-btn_inline { + display: inline-block; +} + +.weui-btn_default { + background-color: #f2f2f2; + background-color: var(--weui-BTN-DEFAULT-BG); +} + +.weui-btn_default, +.weui-btn_default:not(.weui-btn_disabled):visited { + color: #06ae56; + color: var(--weui-BTN-DEFAULT-COLOR); +} + +.weui-btn_primary { + background-color: #07c160; + background-color: var(--weui-BRAND); +} + +.weui-btn_primary:not(.weui-btn_disabled):visited { + color: #fff; +} + +.weui-btn_warn { + background-color: #f2f2f2; + background-color: var(--weui-BTN-DEFAULT-BG); +} + +.weui-btn_warn, +.weui-btn_warn:not(.weui-btn_disabled):visited { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-btn[disabled], +.weui-btn_disabled { + color: rgba(0, 0, 0, 0.2); + color: var(--weui-BTN-DISABLED-FONT-COLOR); + background-color: #f2f2f2; + background-color: var(--weui-BTN-DEFAULT-BG); +} + +.weui-btn_loading .weui-loading { + margin: -0.2em 0.34em 0 0; +} + +.weui-btn_loading .weui-primary-loading { + margin: -0.2em 8px 0 0; + vertical-align: middle; + color: currentColor; +} + +.weui-btn_loading .weui-primary-loading:before { + content: ""; +} + +.weui-btn_loading.weui-btn_primary { + color: #fff; + color: var(--weui-WHITE); +} + +.weui-btn_cell { + position: relative; + display: block; + margin-left: auto; + margin-right: auto; + box-sizing: border-box; + font-size: 17px; + text-align: center; + text-decoration: none; + color: #fff; + line-height: 1.41176471; + padding: 16px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + overflow: hidden; + background-color: #fff; + background-color: var(--weui-BG-5); +} + +.weui-btn_cell+.weui-btn_cell { + margin-top: 16px; +} + +.weui-btn_cell:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-btn_cell__icon { + display: inline-block; + vertical-align: middle; + width: 24px; + height: 24px; + margin: -0.2em 0.34em 0 0; +} + +.weui-btn_cell-default { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-btn_cell-primary { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-btn_cell-warn { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-bottom-fixed-opr-page { + height: 100%; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; +} + +.weui-bottom-fixed-opr-page__content { + min-height: 0; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + padding-bottom: 80px; + box-sizing: border-box; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} + +.weui-bottom-fixed-opr { + padding: 16px 32px 24px; + padding: 16px calc(32px + constant(safe-area-inset-right)) calc(24px + constant(safe-area-inset-bottom)) calc(32px + constant(safe-area-inset-left)); + padding: 16px calc(32px + env(safe-area-inset-right)) calc(24px + env(safe-area-inset-bottom)) calc(32px + env(safe-area-inset-left)); + background: #fff; + position: relative; +} + +.weui-bottom-fixed-opr:before { + content: ""; + height: 80px; + background: -webkit-linear-gradient(bottom, #fff, hsla(0, 0%, 100%, 0)); + background: linear-gradient(0deg, #fff, hsla(0, 0%, 100%, 0)); + position: absolute; + bottom: calc(100% - 1px); + left: 0; + right: 0; + -webkit-transform: translateZ(0); + transform: translateZ(0); +} + +body[data-weui-theme="dark"] .weui-bottom-fixed-opr { + background: #191919; +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) .weui-bottom-fixed-opr { + background: #191919; + } +} + +body[data-weui-theme="dark"] .weui-bottom-fixed-opr:before { + background: -webkit-linear-gradient(bottom, #191919, rgba(25, 25, 25, 0)); + background: linear-gradient(0deg, #191919, rgba(25, 25, 25, 0)); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) .weui-bottom-fixed-opr:before { + background: -webkit-linear-gradient(bottom, #191919, rgba(25, 25, 25, 0)); + background: linear-gradient(0deg, #191919, rgba(25, 25, 25, 0)); + } +} + +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; +} + +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn { + width: 184px; + padding-left: 16px; + padding-right: 16px; +} + +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2), +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2)+.weui-btn { + margin: 0 8px; + width: 136px; +} + +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2)+.weui-btn:first-child, +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2):first-child { + margin-left: 0; +} + +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2)+.weui-btn:last-child, +.weui-bottom-fixed-opr-page .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2):last-child { + margin-right: 0; +} + +.weui-bottom-fixed-opr-page_btn-wrap .weui-bottom-fixed-opr { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; +} + +.weui-bottom-fixed-opr-page_btn-wrap .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2), +.weui-bottom-fixed-opr-page_btn-wrap .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2)+.weui-btn { + width: 184px; + margin: 16px 0 0; +} + +.weui-bottom-fixed-opr-page_btn-wrap .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2)+.weui-btn:first-child, +.weui-bottom-fixed-opr-page_btn-wrap .weui-bottom-fixed-opr .weui-btn:nth-last-child(n + 2):first-child { + margin-top: 0; +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed { + padding: 0; +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed .weui-half-screen-dialog__hd { + padding: 0 24px; + padding: 0 calc(24px + constant(safe-area-inset-right)) 0 calc(24px + constant(safe-area-inset-left)); + padding: 0 calc(24px + env(safe-area-inset-right)) 0 calc(24px + env(safe-area-inset-left)); +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed .weui-half-screen-dialog__bd { + padding-bottom: 0; +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed .weui-half-screen-dialog__ft { + padding: 0; +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed .weui-bottom-fixed-opr-page { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-height: 0; +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed .weui-bottom-fixed-opr-page__content { + padding: 0 24px; + padding: 0 calc(24px + constant(safe-area-inset-right)) 0 calc(24px + constant(safe-area-inset-left)); + padding: 0 calc(24px + env(safe-area-inset-right)) 0 calc(24px + env(safe-area-inset-left)); +} + +.weui-half-screen-dialog.weui-half-screen-dialog_bottom-fixed .weui-bottom-fixed-opr { + padding: 16px 0 64px; + padding: 16px 0 calc(64px + constant(safe-area-inset-bottom)); + padding: 16px 0 calc(64px + env(safe-area-inset-bottom)); +} + +button.weui-btn, +input.weui-btn { + border-width: 0; + outline: 0; + -webkit-appearance: none; +} + +button.weui-btn:focus, +input.weui-btn:focus { + outline: 0; +} + +button.weui-btn_inline, +button.weui-btn_mini, +input.weui-btn_inline, +input.weui-btn_mini { + width: auto; +} + +.weui-btn_mini { + font-size: 16px; + border-radius: 6px; +} + +.weui-btn_mini, +.weui-btn_xmini { + display: inline-block; + width: auto; + padding: 0 12px; + line-height: 2; +} + +.weui-btn_xmini { + font-size: 14px; + font-weight: 500; + border-radius: 2.8px; +} + +.weui-btn+.weui-btn { + margin-top: 16px; +} + +.weui-btn.weui-btn_mini+.weui-btn.weui-btn_mini, +.weui-btn.weui-btn_xmini+.weui-btn.weui-btn_xmini { + margin-top: auto; +} + +.weui-btn.weui-btn_inline+.weui-btn.weui-btn_inline { + margin-left: 16px; +} + +.weui-btn-area { + margin: 48px 16px 8px; +} + +.weui-btn-area_inline { + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-btn-area_inline .weui-btn { + margin-top: auto; + margin-right: 16px; + width: 100%; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-btn-area_inline .weui-btn:last-child { + margin-right: 0; +} + +.weui-btn_icon, +.weui-btn_reset { + background: transparent; + border: 0; + padding: 0; + outline: 0; +} + +.weui-btn_icon { + font-size: 0; +} + +.weui-btn_icon:active [class*="weui-icon-"] { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-cells { + margin-top: 8px; + background-color: #fff; + background-color: var(--weui-BG-2); + overflow: hidden; + position: relative; +} + +.weui-cells:before { + top: 0; + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-cells:after, +.weui-cells:before { + content: " "; + position: absolute; + left: 0; + right: 0; + height: 1px; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + z-index: 2; +} + +.weui-cells:after { + bottom: 0; + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-cells__title { + margin-top: 16px; + margin-bottom: 3px; + padding-left: 16px; + padding-right: 16px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + font-size: 14px; + line-height: 1.4; +} + +.weui-cells__title+.weui-cells { + margin-top: 0; +} + +.weui-cells__tips { + margin-top: 8px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + padding-left: 16px; + padding-right: 16px; + font-size: 14px; + line-height: 1.4; +} + +.weui-cells__tips a, +.weui-cells__tips navigator { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-cells__tips navigator { + display: inline; +} + +.weui-cell { + padding: 16px; + position: relative; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + line-height: 1.41176471; + font-size: 17px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-cell:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + left: 16px; + z-index: 2; +} + +.weui-cell:first-child:before { + display: none; +} + +.weui-cell_active:active:after { + content: ""; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.1); + background: var(--weui-FG-3); + pointer-events: none; +} + +.weui-cell_primary { + -webkit-box-align: start; + -webkit-align-items: flex-start; + align-items: flex-start; +} + +.weui-cell__bd { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-width: 0; +} + +.weui-cell__ft { + text-align: right; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-cell__ft button { + vertical-align: bottom; +} + +.weui-cell__name { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-cell__desc { + font-size: 12px; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + line-height: 1.4; + padding-top: 4px; +} + +.weui-cell_swiped { + display: block; + padding: 0; +} + +.weui-cell_swiped>.weui-cell__bd { + position: relative; + z-index: 1; + background-color: #fff; + background-color: var(--weui-BG-2); +} + +.weui-cell_swiped>.weui-cell__ft { + position: absolute; + right: 0; + top: 0; + bottom: 0; + color: #fff; +} + +.weui-cell_swiped>.weui-cell__ft, +.weui-swiped-btn { + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-swiped-btn { + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + padding: 16px 1em; + line-height: 1.41176471; + color: inherit; +} + +.weui-swiped-btn_default { + background-color: #ededed; + background-color: var(--weui-BG-0); +} + +.weui-swiped-btn_warn { + background-color: #fa5151; + background-color: var(--weui-RED); +} + +.weui-cell_access { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + color: inherit; +} + +.weui-cell_access:active:after { + content: ""; + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.1); + background: var(--weui-FG-3); + pointer-events: none; +} + +.weui-cell_access .weui-cell__ft { + padding-right: 24px; + position: relative; +} + +.weui-cell_access .weui-cell__ft:after { + content: " "; + width: 12px; + height: 24px; + -webkit-mask-position: 0 0; + mask-position: 0 0; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100%; + mask-size: 100%; + background-color: currentColor; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + position: absolute; + top: 50%; + right: 0; + margin-top: -12px; +} + +.weui-cell_link { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-cell_link:first-child:before { + display: block; +} + +.weui-check__label { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-check__label.weui-cell_disabled, +.weui-check__label.weui-cell_readonly { + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-check { + opacity: 0; + position: absolute; + width: 0; + height: 0; + overflow: hidden; +} + +.weui-check[disabled]+.weui-icon-checked { + opacity: 0.1; +} + +.weui-cells_radio .weui-cell__ft { + padding-left: 16px; + font-size: 0; +} + +.weui-cells_radio .weui-check+.weui-icon-checked { + min-width: 16px; + color: transparent; +} + +.weui-cells_radio .weui-check:checked+.weui-icon-checked, +.weui-cells_radio .weui-check[aria-checked="true"]+.weui-icon-checked { + color: #07c160; + color: var(--weui-BRAND); + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.657%2018.435L3%2012.778l1.414-1.414%204.95%204.95L20.678%205l1.414%201.414-12.02%2012.021a1%201%200%2001-1.415%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M8.657%2018.435L3%2012.778l1.414-1.414%204.95%204.95L20.678%205l1.414%201.414-12.02%2012.021a1%201%200%2001-1.415%200z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-cells_checkbox .weui-check__label:before { + left: 55px; +} + +.weui-cells_checkbox .weui-cell__hd { + padding-right: 16px; + font-size: 0; +} + +.weui-cells_checkbox .weui-icon-checked { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%221000%22%20height%3D%221000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M500%20916.667C269.881%20916.667%2083.333%20730.119%2083.333%20500%2083.333%20269.881%20269.881%2083.333%20500%2083.333c230.119%200%20416.667%20186.548%20416.667%20416.667%200%20230.119-186.548%20416.667-416.667%20416.667zm0-50c202.504%200%20366.667-164.163%20366.667-366.667%200-202.504-164.163-366.667-366.667-366.667-202.504%200-366.667%20164.163-366.667%20366.667%200%20202.504%20164.163%20366.667%20366.667%20366.667z%22%20fill-rule%3D%22evenodd%22%20fill-opacity%3D%22.9%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%221000%22%20height%3D%221000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M500%20916.667C269.881%20916.667%2083.333%20730.119%2083.333%20500%2083.333%20269.881%20269.881%2083.333%20500%2083.333c230.119%200%20416.667%20186.548%20416.667%20416.667%200%20230.119-186.548%20416.667-416.667%20416.667zm0-50c202.504%200%20366.667-164.163%20366.667-366.667%200-202.504-164.163-366.667-366.667-366.667-202.504%200-366.667%20164.163-366.667%20366.667%200%20202.504%20164.163%20366.667%20366.667%20366.667z%22%20fill-rule%3D%22evenodd%22%20fill-opacity%3D%22.9%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-cells_checkbox .weui-check:checked+.weui-icon-checked, +.weui-cells_checkbox .weui-check[aria-checked="true"]+.weui-icon-checked { + color: #07c160; + color: var(--weui-BRAND); + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-1.177-7.86l-2.765-2.767L7%2012.431l3.119%203.121a1%201%200%20001.414%200l5.952-5.95-1.062-1.062-5.6%205.6z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-1.177-7.86l-2.765-2.767L7%2012.431l3.119%203.121a1%201%200%20001.414%200l5.952-5.95-1.062-1.062-5.6%205.6z%22%2F%3E%3C%2Fsvg%3E); +} + +.weui-label { + display: block; + width: 105px; + word-wrap: break-word; + word-break: break-all; +} + +.weui-input { + width: 100%; + border: 0; + outline: 0; + -webkit-appearance: none; + background-color: transparent; + font-size: inherit; + color: inherit; + height: 1.41176471em; + line-height: 1.41176471; +} + +.weui-input::-webkit-inner-spin-button, +.weui-input::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +.weui-input:focus:not(:placeholder-shown)+.weui-btn_input-clear { + display: inline; +} + +.weui-textarea { + display: block; + border: 0; + resize: none; + background: transparent; + width: 100%; + color: inherit; + font-size: 1em; + line-height: inherit; + height: 80px; + outline: 0; +} + +.weui-textarea-counter { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + text-align: right; + font-size: 14px; +} + +.weui-cell_warn, +.weui-cell_warn .weui-textarea-counter { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-cell_warn .weui-icon-warn { + display: inline-block; +} + +.weui-cell_disabled .weui-input:disabled, +.weui-cell_disabled .weui-textarea:disabled, +.weui-cell_readonly .weui-input:disabled, +.weui-cell_readonly .weui-textarea:disabled { + opacity: 1; + -webkit-text-fill-color: rgba(0, 0, 0, 0.5); + -webkit-text-fill-color: var(--weui-FG-1); +} + +.weui-cell_disabled .weui-input[disabled], +.weui-cell_disabled .weui-input[readonly], +.weui-cell_disabled .weui-textarea[disabled], +.weui-cell_disabled .weui-textarea[readonly], +.weui-cell_readonly .weui-input[disabled], +.weui-cell_readonly .weui-input[readonly], +.weui-cell_readonly .weui-textarea[disabled], +.weui-cell_readonly .weui-textarea[readonly] { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-btn_input-clear { + display: none; + padding-left: 8px; +} + +.weui-btn_input-clear [class*="weui-icon-"] { + width: 18px; +} + +.weui-cells_form .weui-cell_disabled:active, +.weui-cells_form .weui-cell_readonly:active, +.weui-cells_form .weui-cell_switch:active, +.weui-cells_form .weui-cell_vcode:active { + background-color: transparent; +} + +.weui-cells_form .weui-cell__ft { + font-size: 0; +} + +.weui-cells_form .weui-icon-warn { + display: none; +} + +.weui-cells_form input, +.weui-cells_form label[for], +.weui-cells_form textarea { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-form-preview { + position: relative; + background-color: #fff; + background-color: var(--weui-BG-2); +} + +.weui-form-preview:before { + top: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-form-preview:after, +.weui-form-preview:before { + content: " "; + position: absolute; + left: 0; + right: 0; + height: 1px; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-form-preview:after { + bottom: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-form-preview__hd { + position: relative; + padding: 16px; + text-align: right; + line-height: 2.5em; +} + +.weui-form-preview__hd:after { + content: " "; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 1px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + left: 16px; +} + +.weui-form-preview__hd .weui-form-preview__value { + font-style: normal; + font-size: 1.6em; +} + +.weui-form-preview__bd { + padding: 16px; + font-size: 0.9em; + text-align: right; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + line-height: 2; +} + +.weui-form-preview__ft { + position: relative; + line-height: 50px; + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-form-preview__ft:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-DIALOG-LINE-COLOR); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-DIALOG-LINE-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-form-preview__item { + overflow: hidden; +} + +.weui-form-preview__label { + float: left; + margin-right: 1em; + min-width: 4em; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + text-align: justify; + text-align-last: justify; +} + +.weui-form-preview__value { + display: block; + overflow: hidden; + word-break: normal; + word-wrap: break-word; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-form-preview__btn { + position: relative; + display: block; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + color: #576b95; + color: var(--weui-LINK); + text-align: center; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +button.weui-form-preview__btn { + background-color: transparent; + border: 0; + outline: 0; + line-height: inherit; + font-size: inherit; +} + +.weui-form-preview__btn:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-form-preview__btn:after { + content: " "; + position: absolute; + left: 0; + top: 0; + width: 1px; + bottom: 0; + border-left: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid var(--weui-DIALOG-LINE-COLOR); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-DIALOG-LINE-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +.weui-form-preview__btn:first-child:after { + display: none; +} + +.weui-form-preview__btn_default { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-HALF); +} + +.weui-form-preview__btn_primary { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-form-preview__list { + padding-top: 24px; + padding-bottom: 24px; + line-height: 1.4; + font-size: 14px; + position: relative; +} + +.weui-form-preview__list:before { + content: ""; + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-form-preview__list:last-child { + padding-bottom: 0; +} + +.weui-form-preview__list .weui-form-preview__label { + text-align: left; + text-align-last: unset; + width: 6em; +} + +.weui-form-preview__list .weui-form-preview__value { + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-form-preview__list .weui-form-preview__item { + margin-top: 12px; +} + +.weui-form-preview__list .weui-form-preview__item:first-child, +.weui-form-preview__list>.weui-cells__title:first-child { + margin-top: 0; +} + +.weui-cell_select { + padding: 0; +} + +.weui-cell_select .weui-cell__bd:after { + content: " "; + width: 12px; + height: 24px; + -webkit-mask-position: 0 0; + mask-position: 0 0; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100%; + mask-size: 100%; + background-color: currentColor; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + position: absolute; + top: 50%; + right: 16px; + margin-top: -12px; +} + +.weui-select { + -webkit-appearance: none; + border: 0; + outline: 0; + background-color: transparent; + width: 100%; + font-size: inherit; + min-height: 56px; + line-height: 56px; + position: relative; + z-index: 1; + padding-left: 16px; + padding-right: 40px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + vertical-align: bottom; + box-sizing: border-box; +} + +.weui-cell_select-before .weui-cell__hd { + padding-left: 0; + position: relative; +} + +.weui-cell_select-before .weui-cell__hd:after { + content: " "; + position: absolute; + right: 0; + top: 0; + width: 1px; + bottom: 0; + border-right: 1px solid rgba(0, 0, 0, 0.1); + border-right: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 100% 0; + transform-origin: 100% 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +.weui-cell_select-before .weui-cell__hd:before { + content: " "; + width: 12px; + height: 24px; + -webkit-mask-position: 0 0; + mask-position: 0 0; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100%; + mask-size: 100%; + background-color: currentColor; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M2.454%206.58l1.06-1.06%205.78%205.779a.996.996%200%20010%201.413l-5.78%205.779-1.06-1.061%205.425-5.425-5.425-5.424z%22%20fill%3D%22%23B2B2B2%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E); + position: absolute; + top: 50%; + right: 16px; + margin-top: -12px; +} + +.weui-cell_select-before .weui-cell__bd { + padding-left: 16px; +} + +.weui-cell_select-before .weui-cell__bd:after { + display: none; +} + +.weui-cell_select-before .weui-select { + max-width: 5em; + width: 105px; + box-sizing: content-box; +} + +.weui-cell_select-after .weui-cell__hd { + padding-left: 16px; +} + +.weui-cell_select-after .weui-select { + padding-left: 0; +} + +.weui-cell_vcode { + padding-top: 0; + padding-right: 0; + padding-bottom: 0; +} + +.weui-vcode-btn, +.weui-vcode-img { + margin-left: 5px; + height: 56px; + vertical-align: middle; +} + +.weui-vcode-btn { + display: inline-block; + padding: 0 0.6em 0 0.7em; + line-height: 56px; + font-size: 17px; + color: #576b95; + color: var(--weui-LINK); + position: relative; +} + +.weui-vcode-btn:before { + content: " "; + position: absolute; + left: 0; + top: 0; + width: 1px; + bottom: 0; + border-left: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +button.weui-vcode-btn { + background-color: transparent; + border: 0; + outline: 0; +} + +.weui-vcode-btn:active { + color: var(--weui-LINK-ACTIVE); +} + +.weui-gallery { + display: none; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: #000; + z-index: 1000; +} + +.weui-gallery__img, +.weui-gallery__opr { + position: absolute; + left: 0; + left: constant(safe-area-inset-left); + left: env(safe-area-inset-left); + right: 0; + right: constant(safe-area-inset-right); + right: env(safe-area-inset-right); +} + +.weui-gallery__img { + top: 0; + top: constant(safe-area-inset-top); + top: env(safe-area-inset-top); + bottom: 60px; + bottom: calc(60px + constant(safe-area-inset-bottom)); + bottom: calc(60px + env(safe-area-inset-bottom)); + width: 100%; + background: 50% no-repeat; + background-size: contain; +} + +.weui-gallery__opr { + position: absolute; + bottom: 0; + background-color: #0d0d0d; + color: #fff; + color: var(--weui-WHITE); + line-height: 60px; + text-align: center; +} + +.weui-gallery__del { + display: block; + padding-bottom: 0; + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); +} + +.weui-gallery__del:active { + opacity: 0.5; +} + +.weui-cell_switch { + padding-top: 12px; + padding-bottom: 12px; +} + +.weui-cell_switch.weui-cell_disabled, +.weui-cell_switch.weui-cell_readonly { + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-switch { + -webkit-appearance: none; + appearance: none; +} + +.weui-switch, +.weui-switch-cp__box { + vertical-align: bottom; + position: relative; + width: 52px; + height: 32px; + background-color: rgba(0, 0, 0, 0.1); + background-color: var(--weui-FG-3); + border: 0; + padding: 2px; + outline: 0; + border-radius: 16px; + box-sizing: border-box; + -webkit-transition: background-color 0.1s, border 0.1s; + transition: background-color 0.1s, border 0.1s; +} + +.weui-switch-cp__box:after, +.weui-switch:after { + content: " "; + position: absolute; + top: 2px; + left: 2px; + width: 28px; + height: 28px; + border-radius: 15px; + background-color: #fff; + box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.06); + -webkit-transition: -webkit-transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35); + transition: -webkit-transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35); + transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35); + transition: transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35), + -webkit-transform 0.35s cubic-bezier(0.4, 0.4, 0.25, 1.35); +} + +.weui-switch-cp__input:checked+.weui-switch-cp__box, +.weui-switch-cp__input[aria-checked="true"]+.weui-switch-cp__box, +.weui-switch:checked { + background-color: #07c160; + background-color: var(--weui-BRAND); +} + +.weui-switch-cp__input:checked+.weui-switch-cp__box:after, +.weui-switch-cp__input[aria-checked="true"]+.weui-switch-cp__box:after, +.weui-switch:checked:after { + -webkit-transform: translateX(20px); + transform: translateX(20px); +} + +.weui-switch-cp__input[aria-disabled="true"]+.weui-switch-cp__box, +.weui-switch-cp__input[disabled]+.weui-switch-cp__box, +.weui-switch[disabled] { + opacity: 0.1; +} + +.weui-switch-cp__input { + position: absolute; + width: 0; + height: 0; + opacity: 0; + overflow: hidden; +} + +.weui-switch-cp__box { + display: block; +} + +.weui-cell_uploader { + padding-bottom: 24px; +} + +.weui-uploader { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-uploader__hd { + display: -webkit-box; + display: -webkit-flex; + display: flex; + padding-bottom: 16px; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-uploader__title { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-uploader__info { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +.weui-uploader__bd { + margin-bottom: -8px; + margin-right: -8px; + overflow: hidden; +} + +.weui-uploader__files { + list-style: none; +} + +.weui-uploader__file { + float: left; + margin-right: 8px; + margin-bottom: 8px; + width: 96px; + height: 96px; + background: no-repeat 50%; + background-size: cover; +} + +.weui-uploader__file_status { + position: relative; +} + +.weui-uploader__file_status:before { + content: " "; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: rgba(0, 0, 0, 0.5); +} + +.weui-uploader__file_status .weui-uploader__file-content { + display: block; +} + +.weui-uploader__file-content { + display: none; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + color: #fff; + color: var(--weui-WHITE); +} + +.weui-uploader__file-content .weui-icon-warn { + display: inline-block; +} + +.weui-uploader__input-box { + float: left; + position: relative; + margin-right: 8px; + margin-bottom: 8px; + width: 96px; + height: 96px; + box-sizing: border-box; + background-color: #ededed; +} + +body[data-weui-theme="dark"] .weui-uploader__input-box { + background-color: #2e2e2e; +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) .weui-uploader__input-box { + background-color: #2e2e2e; + } +} + +.weui-uploader__input-box:after, +.weui-uploader__input-box:before { + content: " "; + position: absolute; + top: 50%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + background-color: #a3a3a3; +} + +body[data-weui-theme="dark"] .weui-uploader__input-box:after, +body[data-weui-theme="dark"] .weui-uploader__input-box:before { + background-color: #6d6d6d; +} + +@media (prefers-color-scheme: dark) { + + body:not([data-weui-theme="light"]) .weui-uploader__input-box:after, + body:not([data-weui-theme="light"]) .weui-uploader__input-box:before { + background-color: #6d6d6d; + } +} + +.weui-uploader__input-box:before { + width: 2px; + height: 32px; +} + +.weui-uploader__input-box:after { + width: 32px; + height: 2px; +} + +.weui-uploader__input-box:active:after, +.weui-uploader__input-box:active:before { + opacity: 0.7; +} + +.weui-uploader__input { + position: absolute; + z-index: 1; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-msg__desc-primary a, +.weui-msg__desc a, +.weui-msg__tips a { + color: #576b95; + color: var(--weui-LINK); + display: inline-block; + vertical-align: baseline; +} + +.weui-msg { + padding-top: 48px; + padding: calc(48px + constant(safe-area-inset-top)) constant(safe-area-inset-right) constant(safe-area-inset-bottom) constant(safe-area-inset-left); + padding: calc(48px + env(safe-area-inset-top)) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + text-align: center; + line-height: 1.4; + min-height: 100%; + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + background-color: #fff; + background-color: var(--weui-BG-2); +} + +.weui-msg__icon-area { + margin-bottom: 32px; +} + +.weui-msg__text-area { + margin-bottom: 32px; + padding: 0 32px; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + line-height: 1.6; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-msg__text-area:first-child { + padding-top: 96px; +} + +.weui-msg__title { + font-weight: 500; + font-size: 22px; +} + +.weui-msg__desc, +.weui-msg__title { + margin-bottom: 16px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-msg__desc { + font-size: 17px; + font-weight: 400; +} + +.weui-msg__desc-primary { + font-size: 14px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + margin-bottom: 16px; +} + +.weui-msg__custom-area { + text-align: left; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; + margin-bottom: 16px; +} + +.weui-msg__title+.weui-msg__custom-area { + margin-top: 48px; +} + +.weui-msg__desc+.weui-msg__custom-area, +.weui-msg__desc-primary+.weui-msg__custom-area { + margin-top: 40px; +} + +.weui-msg__custom-area .weui-cells__group_form .weui-cells { + margin: 0; +} + +.weui-msg__opr-area { + margin-bottom: 16px; +} + +.weui-msg__opr-area .weui-btn-area { + margin: 0; +} + +.weui-msg__opr-area .weui-btn+.weui-btn { + margin-bottom: 16px; +} + +.weui-msg__opr-area:last-child { + margin-bottom: 96px; +} + +.weui-msg__opr-area+.weui-msg__extra-area { + margin-top: 48px; +} + +.weui-msg__tips-area { + margin-bottom: 16px; + padding: 0 40px; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-msg__opr-area+.weui-msg__tips-area { + margin-bottom: 48px; +} + +.weui-msg__tips-area:last-child { + margin-bottom: 64px; +} + +.weui-msg__extra-area, +.weui-msg__tips { + font-size: 12px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-msg__extra-area { + margin-bottom: 24px; + padding: 0 32px; + box-sizing: border-box; +} + +.weui-msg__extra-area a, +.weui-msg__extra-area navigator { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-msg__extra-area navigator { + display: inline; +} + +.weui-msg_align-top .weui-msg__text-area:first-child { + padding-top: 0; +} + +body, +page { + --weui-STEPS-DEFAULT-COLOR: var(--weui-FG-3); + --weui-STEPS-HIGHLIGHT-COLOR: var(--weui-BRAND); + --weui-STEPS-FONT-SIZE: 17; + --weui-STEPS-LINEHEIGHT: 1.4; + --weui-STEPS-DOT-SIZE: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + --weui-STEPS-ICON-SIZE: 40; + --weui-STEPS-VERTICAL-DOT-GAP: calc((1em - var(--weui-STEPS-DOT-SIZE)) / 2); + --weui-STEPS-HORIZONAL-DOT-GAP: 4px; +} + +.weui-steps { + line-height: 1.4; + line-height: var(--weui-STEPS-LINEHEIGHT); + font-size: 17px; + font-size: calc(1px * var(--weui-STEPS-FONT-SIZE)); +} + +.weui-steps__item__desc, +.weui-steps__item__title { + display: block; +} + +.weui-steps__item__title { + font-weight: 500; +} + +.weui-steps__item__desc { + font-size: 14px; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + margin-top: 4px; +} + +.weui-steps_vertical { + position: relative; +} + +.weui-steps_vertical .weui-steps__item { + position: relative; + padding-bottom: 32px; +} + +.weui-steps_vertical .weui-steps__item:before { + content: ""; + content: " "; + position: absolute; + left: 0; + top: 0; + width: 1px; + bottom: 0; + border-left: 1px solid var(--weui-FG-3); + border-left: 1px solid var(--weui-STEPS-DEFAULT-COLOR); + color: var(--weui-FG-3); + color: var(--weui-STEPS-DEFAULT-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); + top: 1.2em; + top: calc((var(--weui-STEPS-LINEHEIGHT) - (var(--weui-STEPS-LINEHEIGHT) - 1) / 2) * 1em); + bottom: -0.2em; + bottom: calc((var(--weui-STEPS-LINEHEIGHT) - 1) / 2 * -1em); +} + +.weui-steps_vertical .weui-steps__item:first-child:not(.weui-steps__item_success) .weui-steps__item__inner:before { + background-color: var(--weui-BRAND); + background-color: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_vertical .weui-steps__item:last-child:before { + display: none; +} + +.weui-steps_vertical .weui-steps__item__inner { + position: relative; + z-index: 1; + padding-left: 36px; +} + +.weui-steps_vertical .weui-steps__item__inner:before { + content: ""; + width: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + width: var(--weui-STEPS-DOT-SIZE); + height: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + height: var(--weui-STEPS-DOT-SIZE); + border-radius: 100%; + background-color: var(--weui-FG-3); + background-color: var(--weui-STEPS-DEFAULT-COLOR); +} + +.weui-steps_vertical .weui-steps__icon, +.weui-steps_vertical .weui-steps__item__inner:before { + position: absolute; + z-index: 1; + left: 0; + top: 0.7em; + top: calc(var(--weui-STEPS-LINEHEIGHT) / 2 * 1em); + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.weui-steps_vertical .weui-steps__icon { + font-size: 17px; + font-size: calc(1px * var(--weui-STEPS-FONT-SIZE)); + width: 2.35294em; + width: calc(var(--weui-STEPS-ICON-SIZE) / var(--weui-STEPS-FONT-SIZE) * 1em); + height: 2.35294em; + height: calc(var(--weui-STEPS-ICON-SIZE) / var(--weui-STEPS-FONT-SIZE) * 1em); + margin-top: 0.39647em; + margin-top: calc((var(--weui-STEPS-ICON-SIZE) / var(--weui-STEPS-FONT-SIZE) * 1em - 1em) / 2 - 0.28em); +} + +.weui-steps_vertical .weui-steps__item_icon:before { + top: calc(((1em - var(--weui-STEPS-DOT-SIZE)) / 2) + 40 / 17 * 1em - 0.14em); + top: calc(var(--weui-STEPS-VERTICAL-DOT-GAP) + var(--weui-STEPS-ICON-SIZE) / var(--weui-STEPS-FONT-SIZE) * 1em - 0.14em); +} + +.weui-steps_vertical .weui-steps__item_icon .weui-steps__item__inner:before { + display: none; +} + +.weui-steps_vertical .weui-steps__item_icon-prev:before { + bottom: calc(((1em - var(--weui-STEPS-DOT-SIZE)) / 2) - 0.4 / 2 * 1em + 0.14em); + bottom: calc(var(--weui-STEPS-VERTICAL-DOT-GAP) - (var(--weui-STEPS-LINEHEIGHT) - 1) / 2 * 1em + 0.14em); +} + +.weui-steps_vertical .weui-steps__item_success:before { + border-color: var(--weui-BRAND); + border-color: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_vertical .weui-steps__item_success+.weui-steps__item .weui-steps__item__inner:before, +.weui-steps_vertical .weui-steps__item_success .weui-steps__item__inner:before { + background-color: var(--weui-BRAND); + background-color: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_horizonal, +.weui-steps_horizonal .weui-steps__item { + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-steps_horizonal .weui-steps__item { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-steps_horizonal .weui-steps__item:before { + content: ""; + display: block; + width: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + width: var(--weui-STEPS-DOT-SIZE); + height: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + height: var(--weui-STEPS-DOT-SIZE); + border-radius: 100%; + background-color: var(--weui-FG-3); + background-color: var(--weui-STEPS-DEFAULT-COLOR); + -webkit-flex-shrink: 0; + flex-shrink: 0; +} + +.weui-steps_horizonal .weui-steps__item:after { + content: ""; + height: 0.5px; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + margin: 0 4px; + margin: 0 var(--weui-STEPS-HORIZONAL-DOT-GAP); + background: var(--weui-FG-3); + background: var(--weui-STEPS-DEFAULT-COLOR); +} + +.weui-steps_horizonal .weui-steps__item:last-child { + -webkit-box-flex: 0; + -webkit-flex: none; + flex: none; +} + +.weui-steps_horizonal .weui-steps__item:last-child:after { + display: none; +} + +.weui-steps_horizonal .weui-steps__item:first-child:not(.weui-steps__item_success):before { + background: var(--weui-BRAND); + background: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_horizonal .weui-steps__item__inner { + margin-left: 8px; +} + +.weui-steps_horizonal .weui-steps__item_success+.weui-steps__item:before, +.weui-steps_horizonal .weui-steps__item_success:after, +.weui-steps_horizonal .weui-steps__item_success:before { + background: var(--weui-BRAND); + background: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_horizonal-primary { + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-steps_horizonal-primary .weui-steps__item { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + position: relative; +} + +.weui-steps_horizonal-primary .weui-steps__item:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid var(--weui-FG-3); + border-top: 1px solid var(--weui-STEPS-DEFAULT-COLOR); + color: var(--weui-FG-3); + color: var(--weui-STEPS-DEFAULT-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-steps_horizonal-primary .weui-steps__item:last-child { + -webkit-box-flex: 0; + -webkit-flex: none; + flex: none; +} + +.weui-steps_horizonal-primary .weui-steps__item:last-child:before { + display: none; +} + +.weui-steps_horizonal-primary .weui-steps__item__inner { + position: relative; + padding-top: 36px; +} + +.weui-steps_horizonal-primary .weui-steps__item__inner:before { + content: ""; + position: absolute; + z-index: 1; + width: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + width: var(--weui-STEPS-DOT-SIZE); + height: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + height: var(--weui-STEPS-DOT-SIZE); + border-radius: 100%; + background-color: var(--weui-FG-3); + background-color: var(--weui-STEPS-DEFAULT-COLOR); + top: 0; + left: 0; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); +} + +.weui-steps_horizonal-primary .weui-steps__item__inner:after { + content: ""; + background-color: #fff; + background-color: var(--weui-BG-2); + width: calc((8 / var(--weui-STEPS-FONT-SIZE) * 1em) + 2 * 4px); + width: calc(var(--weui-STEPS-DOT-SIZE) + 2 * var(--weui-STEPS-HORIZONAL-DOT-GAP)); + height: calc((8 / var(--weui-STEPS-FONT-SIZE) * 1em) + 2 * 4px); + height: calc(var(--weui-STEPS-DOT-SIZE) + 2 * var(--weui-STEPS-HORIZONAL-DOT-GAP)); + position: absolute; + top: 0; + left: 0; + -webkit-transform: translate(calc(-50% + (8 / var(--weui-STEPS-FONT-SIZE) * 1em) / 2), + -50%); + -webkit-transform: translate(calc(-50% + var(--weui-STEPS-DOT-SIZE) / 2), + -50%); + transform: translate(calc(-50% + (8 / var(--weui-STEPS-FONT-SIZE) * 1em) / 2), + -50%); + transform: translate(calc(-50% + var(--weui-STEPS-DOT-SIZE) / 2), -50%); +} + +.weui-steps_horizonal-primary .weui-steps__item_success:before { + border-color: var(--weui-BRAND); + border-color: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_horizonal-primary .weui-steps__item_success+.weui-steps__item .weui-steps__item__inner:before, +.weui-steps_horizonal-primary .weui-steps__item_success .weui-steps__item__inner:before { + background: var(--weui-BRAND); + background: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +.weui-steps_horizonal-center { + display: -webkit-box; + display: -webkit-flex; + display: flex; + text-align: center; +} + +.weui-steps_horizonal-center .weui-steps__item { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + position: relative; +} + +.weui-steps_horizonal-center .weui-steps__item:after, +.weui-steps_horizonal-center .weui-steps__item:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid var(--weui-FG-3); + border-top: 1px solid var(--weui-STEPS-DEFAULT-COLOR); + color: var(--weui-FG-3); + color: var(--weui-STEPS-DEFAULT-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-steps_horizonal-center .weui-steps__item:before { + right: 50%; +} + +.weui-steps_horizonal-center .weui-steps__item:after { + left: 50%; +} + +.weui-steps_horizonal-center .weui-steps__item:first-child:before, +.weui-steps_horizonal-center .weui-steps__item:last-child:after { + display: none; +} + +.weui-steps_horizonal-center .weui-steps__item__inner { + position: relative; + z-index: 1; + padding-top: 36px; +} + +.weui-steps_horizonal-center .weui-steps__item__inner:before { + content: ""; + position: absolute; + z-index: 1; + width: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + width: var(--weui-STEPS-DOT-SIZE); + height: calc(8 / var(--weui-STEPS-FONT-SIZE) * 1em); + height: var(--weui-STEPS-DOT-SIZE); + border-radius: 100%; + background-color: var(--weui-FG-3); + background-color: var(--weui-STEPS-DEFAULT-COLOR); + top: 0; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.weui-steps_horizonal-center .weui-steps__item__inner:after { + content: ""; + background-color: #fff; + background-color: var(--weui-BG-2); + width: calc((8 / var(--weui-STEPS-FONT-SIZE) * 1em) + 2 * 4px); + width: calc(var(--weui-STEPS-DOT-SIZE) + 2 * var(--weui-STEPS-HORIZONAL-DOT-GAP)); + height: calc((8 / var(--weui-STEPS-FONT-SIZE) * 1em) + 2 * 4px); + height: calc(var(--weui-STEPS-DOT-SIZE) + 2 * var(--weui-STEPS-HORIZONAL-DOT-GAP)); + position: absolute; + top: 0; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); +} + +.weui-steps_horizonal-center .weui-steps__item_success+.weui-steps__item .weui-steps__item__inner:before, +.weui-steps_horizonal-center .weui-steps__item_success+.weui-steps__item:before, +.weui-steps_horizonal-center .weui-steps__item_success .weui-steps__item__inner:before, +.weui-steps_horizonal-center .weui-steps__item_success:after, +.weui-steps_horizonal-center .weui-steps__item_success:before { + background: var(--weui-BRAND); + background: var(--weui-STEPS-HIGHLIGHT-COLOR); +} + +body, +page { + --weui-cellMarginLR: 16px; + --weui-cellPaddingLR: 16px; +} + +.weui-cells__group { + border: 0; +} + +.weui-cells__group:first-child { + margin-top: 0; +} + +.weui-cells__group_form { + margin-top: 24px; +} + +.weui-cells__group_form .weui-cells { + margin-left: 16px; + margin-left: var(--weui-cellMarginLR); + margin-right: 16px; + margin-right: var(--weui-cellMarginLR); +} + +.weui-cells__group_form .weui-cells:after, +.weui-cells__group_form .weui-cells:before { + left: 16px; + left: var(--weui-cellPaddingLR); + right: 16px; + right: var(--weui-cellPaddingLR); +} + +.weui-cells__group_form .weui-cell { + padding: 16px; + padding: 16px var(--weui-cellPaddingLR); +} + +.weui-cells__group_form .weui-cell:before { + left: 16px; + left: var(--weui-cellPaddingLR); + right: 16px; + right: var(--weui-cellPaddingLR); +} + +.weui-cells__group_form .weui-cell__hd { + padding-right: 16px; +} + +.weui-cells__group_form .weui-cell__ft { + padding-left: 16px; +} + +.weui-cells__group_form .weui-cells__title { + margin-top: 24px; + margin-bottom: 8px; + padding: 0 32px; +} + +.weui-cells__group_form:first-child .weui-cells__title { + margin-top: 0; +} + +.weui-cells__group_form .weui-cells__tips { + margin-top: 8px; + padding: 0 32px; + padding: 0 calc(var(--weui-cellMarginLR) + var(--weui-cellPaddingLR)); + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); +} + +.weui-cells__group_form .weui-cells__tips a { + font-weight: 700; +} + +.weui-cells__group_form .weui-cells__tips_warn { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-cells__group_form .weui-label { + max-width: 5em; + margin-right: 8px; +} + +.weui-cells__group_form .weui-cell_access:active:after, +.weui-cells__group_form .weui-cell_active:active:after { + border-radius: 8px; +} + +.weui-cells__group_form .weui-cell_warn input { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-cells__group_form .weui-cell_disabled:active:after, +.weui-cells__group_form .weui-cell_readonly:active:after, +.weui-cells__group_form .weui-cell_switch:active:after, +.weui-cells__group_form .weui-cell_vcode:active:after, +.weui-cells__group_form .weui-icon-warn { + display: none; +} + +.weui-cells__group_form input, +.weui-cells__group_form label[for], +.weui-cells__group_form textarea { + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-cells__group_form .weui-cell_wrap { + -webkit-box-align: initial; + -webkit-align-items: initial; + align-items: initial; + padding-top: 8px; + padding-bottom: 8px; +} + +.weui-cells__group_form .weui-cell_wrap .weui-cell__hd { + padding-right: 0; +} + +.weui-cells__group_form .weui-cell_wrap .weui-label { + margin-top: 8px; +} + +.weui-cells__group_form .weui-cell_wrap .weui-cell__bd { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-wrap: wrap; + flex-wrap: wrap; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-cells__group_form .weui-cell__control { + margin: 8px 0 8px 0; +} + +.weui-cells__group_form .weui-cell__control_flex { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-width: 30vw; +} + +.weui-cells__group_form .weui-vcode-btn { + font-size: 16px; + padding: 0 12px; + height: auto; + width: auto; + line-height: 2; + border-radius: 6px; + color: #06ae56; + color: var(--weui-BTN-DEFAULT-COLOR); + background-color: #f2f2f2; + background-color: var(--weui-BTN-DEFAULT-BG); +} + +.weui-cells__group_form .weui-vcode-btn:before { + display: none; +} + +.weui-cells__group_form .weui-cell_vcode.weui-cell_wrap { + padding-top: 4px; + padding-bottom: 4px; +} + +.weui-cells__group_form .weui-cell_vcode.weui-cell_wrap .weui-label { + margin-top: 12px; +} + +.weui-cells__group_form .weui-cell_vcode.weui-cell_wrap .weui-input { + font-size: 17px; + min-height: 1.88235294em; +} + +.weui-cells__group_form .weui-cells_checkbox .weui-check__label:before { + left: 56px; + left: calc(40px + var(--weui-cellPaddingLR)); +} + +.weui-cells__group_form .weui-cell_select { + padding: 0; +} + +.weui-cells__group_form .weui-cell_select-before .weui-cell__hd { + padding-right: 0; +} + +.weui-cells__group_form .weui-cell_switch { + padding: 12px 16px; +} + +.weui-cells__group_form-primary { + margin-top: 32px; +} + +.weui-cells__group_form-primary .weui-cells { + background: #f7f7f7; + background: var(--weui-BG-1); + border-radius: 8px; + overflow: hidden; +} + +.weui-cells__group_form-primary .weui-cells:after, +.weui-cells__group_form-primary .weui-cells:before { + display: none; +} + +.weui-cells__group_form-primary .weui-cell_access:active:after, +.weui-cells__group_form-primary .weui-cell_active:active:after { + border-radius: 0; +} + +.weui-form { + padding: 56px 0 0; + padding: calc(56px + constant(safe-area-inset-top)) constant(safe-area-inset-right) constant(safe-area-inset-bottom) constant(safe-area-inset-left); + padding: calc(56px + env(safe-area-inset-top)) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + line-height: 1.4; + min-height: 100%; + box-sizing: border-box; + background-color: #fff; + background-color: var(--weui-BG-2); +} + +.weui-form .weui-footer, +.weui-form .weui-footer__link { + font-size: 14px; +} + +.weui-form .weui-agree { + padding: 0; + display: -webkit-box; + display: -webkit-flex; + display: flex; + text-align: justify; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + line-height: 1.6; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-form .weui-agree__checkbox { + -webkit-flex-shrink: 0; + flex-shrink: 0; + margin-top: 0; +} + +.weui-form .weui-agree__text { + min-width: 0; +} + +.weui-form__text-area { + padding: 0 32px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + text-align: center; +} + +.weui-form__control-area { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + margin: 48px 0; +} + +.weui-form__extra-area, +.weui-form__tips-area { + margin-bottom: 24px; + padding: 0 32px; + text-align: center; +} + +.weui-form__extra-area { + margin-top: 52px; +} + +.weui-form__opr-area { + padding: 0 32px; +} + +.weui-form__opr-area:last-child { + margin-bottom: 96px; +} + +.weui-form__opr-area+.weui-form__tips-area { + margin-top: 16px; + margin-bottom: 0; +} + +.weui-form__tips-area+.weui-form__extra-area { + margin-top: 32px; +} + +.weui-form__tips-area:last-child { + margin-bottom: 60px; +} + +.weui-form__title { + font-size: 22px; + font-weight: 700; + line-height: 1.36; +} + +.weui-form__desc { + font-size: 17px; + margin-top: 16px; +} + +.weui-form__tips { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + font-size: 14px; +} + +.weui-form__tips a, +.weui-form__tips navigator { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-form__tips navigator { + display: inline; +} + +.weui-article { + padding: 48px 24px; + padding: 48px calc(24px + constant(safe-area-inset-right)) calc(48px + constant(safe-area-inset-bottom)) calc(24px + constant(safe-area-inset-left)); + padding: 48px calc(24px + env(safe-area-inset-right)) calc(48px + env(safe-area-inset-bottom)) calc(24px + env(safe-area-inset-left)); + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + font-size: 17px; + line-height: 1.6; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-article section { + margin-bottom: 48px; +} + +.weui-article section section { + margin-bottom: 32px; +} + +.weui-article section section section { + margin-bottom: 24px; +} + +.weui-article h1, +.weui-article h2, +.weui-article h3, +.weui-article h4, +.weui-article h5, +.weui-article h6 { + line-height: 1.4; +} + +.weui-article h1 { + font-size: 22px; + font-weight: 500; + margin-bottom: 48px; + text-align: center; +} + +.weui-article h2 { + font-size: 20px; + font-weight: 500; + margin-bottom: 16px; +} + +.weui-article h3 { + font-size: 17px; + font-weight: 500; + margin-bottom: 8px; +} + +.weui-article h4 { + font-size: 17px; + font-weight: 400; + margin-bottom: 4px; +} + +.weui-article h5, +.weui-article h6 { + font-weight: 400; +} + +.weui-article * { + max-width: 100%; + box-sizing: border-box; + word-wrap: break-word; +} + +.weui-article img { + vertical-align: bottom; +} + +.weui-article p { + margin: 0 0 24px; +} + +.weui-article ol, +.weui-article ul { + list-style: none; + margin-bottom: 24px; +} + +.weui-article ol ol, +.weui-article ol ul, +.weui-article ul ol, +.weui-article ul ul { + padding-left: 16px; + margin-bottom: 0; +} + +.weui-tabbar { + display: -webkit-box; + display: -webkit-flex; + display: flex; + position: relative; + z-index: 500; + background-color: #f7f7f7; + background-color: var(--weui-BG-1); +} + +.weui-tabbar:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-tabbar__item { + display: block; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + font-size: 0; + padding-top: 8px; + padding-bottom: 8px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + text-align: center; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-tabbar__item:first-child { + padding-left: constant(safe-area-inset-left); + padding-left: env(safe-area-inset-left); +} + +.weui-tabbar__item:last-child { + padding-right: constant(safe-area-inset-right); + padding-right: env(safe-area-inset-right); +} + +.weui-tabbar__item.weui-bar__item_on .weui-tabbar__icon, +.weui-tabbar__item.weui-bar__item_on .weui-tabbar__label { + color: #07c160; + color: var(--weui-BRAND); + + svg { + fill: #07c160; + } + + svg { + fill: var(--weui-BRAND); + } +} + +.weui-tabbar__icon { + display: inline-block; + margin-bottom: 6px; + + svg { + fill: rgba(0, 0, 0, 0.9); + } + + svg { + fill: var(--weui-FG-0); + } +} + +.weui-tabbar__label { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + font-size: 12px; + line-height: 1.4; + font-weight: bold; +} + +.weui-navbar { + display: -webkit-box; + display: -webkit-flex; + display: flex; + position: relative; + z-index: 500; + background-color: #fff; + background-color: var(--weui-BG-2); + padding-top: constant(safe-area-inset-top); + padding-top: env(safe-area-inset-top); +} + +.weui-navbar:after { + content: " "; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 1px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-navbar+.weui-tab__panel { + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); +} + +.weui-navbar__item { + position: relative; + display: block; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + padding: 16px 0; + padding-top: calc(16px + constant(safe-area-inset-top)); + padding-top: calc(16px + env(safe-area-inset-top)); + text-align: center; + font-size: 17px; + line-height: 1.41176471; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-navbar__item.weui-bar__item_on, +.weui-navbar__item:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-navbar__item:after { + content: " "; + position: absolute; + right: 0; + top: 0; + width: 1px; + bottom: 0; + border-right: 1px solid rgba(0, 0, 0, 0.1); + border-right: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 100% 0; + transform-origin: 100% 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +.weui-navbar__item:first-child { + padding-left: constant(safe-area-inset-left); + padding-left: env(safe-area-inset-left); +} + +.weui-navbar__item:last-child { + padding-right: constant(safe-area-inset-right); + padding-right: env(safe-area-inset-right); +} + +.weui-navbar__item:last-child:after { + display: none; +} + +.weui-tab { + display: -webkit-box; + display: -webkit-flex; + display: flex; + height: 100%; + box-sizing: border-box; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; +} + +.weui-tab__panel { + box-sizing: border-box; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + overflow: auto; + -webkit-overflow-scrolling: touch; +} + +.weui-tab__content { + display: none; +} + +.weui-progress { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-progress__bar { + background-color: #ededed; + background-color: var(--weui-BG-0); + height: 3px; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-progress__inner-bar { + width: 0; + height: 100%; + background-color: #07c160; + background-color: var(--weui-BRAND); +} + +.weui-progress__opr { + display: block; + margin-left: 15px; + font-size: 0; +} + +.weui-panel { + background-color: #fff; + background-color: var(--weui-BG-2); + margin-top: 10px; + position: relative; + overflow: hidden; +} + +.weui-panel:first-child { + margin-top: 0; +} + +.weui-panel:before { + top: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-panel:after, +.weui-panel:before { + content: " "; + position: absolute; + left: 0; + right: 0; + height: 1px; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-panel:after { + bottom: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-panel .weui-cells:after { + display: none; +} + +.weui-panel__hd { + padding: 16px 16px 13px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + font-size: 15px; + font-weight: 500; + position: relative; +} + +.weui-panel__hd:after { + content: " "; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 1px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + left: 15px; +} + +.weui-media-box { + padding: 16px; + position: relative; +} + +.weui-media-box:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); + left: 16px; +} + +.weui-media-box:first-child:before { + display: none; +} + +a.weui-media-box { + color: #000; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +a.weui-media-box:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-media-box__title { + display: block; + font-weight: 400; + font-size: 17px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + width: auto; + white-space: nowrap; + word-wrap: normal; +} + +.weui-media-box__desc, +.weui-media-box__title { + line-height: 1.4; + overflow: hidden; + text-overflow: ellipsis; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-media-box__desc { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + font-size: 14px; + padding-top: 4px; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.weui-media-box__info { + display: block; + margin-top: 16px; + padding-bottom: 4px; + font-size: 13px; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + line-height: 1em; + list-style: none; + overflow: hidden; +} + +.weui-media-box__info__meta { + float: left; + padding-right: 1em; +} + +.weui-media-box__info__meta_extra { + padding-left: 1em; + border-left: 1px solid rgba(0, 0, 0, 0.3); + border-left: 1px solid var(--weui-FG-2); +} + +.weui-media-box_appmsg { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-media-box_appmsg .weui-media-box__hd { + margin-right: 16px; + width: 60px; + height: 60px; + line-height: 60px; + text-align: center; +} + +.weui-media-box_appmsg .weui-media-box__thumb { + width: 100%; + max-height: 100%; + vertical-align: top; +} + +.weui-media-box_appmsg .weui-media-box__bd { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-width: 0; +} + +.weui-media-box_small-appmsg { + padding: 0; +} + +.weui-media-box_small-appmsg .weui-cells { + margin-top: 0; +} + +.weui-media-box_small-appmsg .weui-cells:before { + display: none; +} + +.weui-grids { + position: relative; + overflow: hidden; +} + +.weui-grids:before { + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-grids:after, +.weui-grids:before { + content: " "; + position: absolute; + left: 0; + top: 0; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-grids:after { + width: 1px; + bottom: 0; + border-left: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +.weui-grid { + position: relative; + float: left; + padding: 20px 10px; + width: 33.33333333%; + box-sizing: border-box; +} + +.weui-grid:before { + top: 0; + width: 1px; + border-right: 1px solid rgba(0, 0, 0, 0.1); + border-right: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 100% 0; + transform-origin: 100% 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +.weui-grid:after, +.weui-grid:before { + content: " "; + position: absolute; + right: 0; + bottom: 0; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-grid:after { + left: 0; + height: 1px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-grid:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-grid__icon { + width: 28px; + height: 28px; + margin: 0 auto; +} + +.weui-grid__icon img { + display: block; + width: 100%; + height: 100%; +} + +.weui-grid__icon+.weui-grid__label { + margin-top: 4px; +} + +.weui-grid__label { + display: block; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.weui-footer, +.weui-grid__label { + text-align: center; + font-size: 14px; +} + +.weui-footer { + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + line-height: 1.4; +} + +.weui-footer a, +.weui-footer navigator { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-footer navigator { + display: inline; +} + +.weui-footer_fixed-bottom { + position: fixed; + bottom: 0; + left: 0; + right: 0; + padding-top: 16px; + padding-bottom: 16px; + padding-bottom: calc(16px + constant(safe-area-inset-bottom)); + padding-bottom: calc(16px + env(safe-area-inset-bottom)); + left: constant(safe-area-inset-left); + left: env(safe-area-inset-left); + right: constant(safe-area-inset-right); + right: env(safe-area-inset-right); +} + +.weui-footer__links { + font-size: 0; +} + +.weui-footer__link { + display: inline-block; + vertical-align: top; + margin: 0 8px; + position: relative; + font-size: 14px; +} + +.weui-footer__link:before { + content: " "; + position: absolute; + left: 0; + top: 0; + width: 1px; + bottom: 0; + border-left: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); + left: -8px; + top: 0.36em; + bottom: 0.36em; +} + +.weui-footer__link:first-child:before { + display: none; +} + +.weui-footer__text { + padding: 0 16px; + font-size: 12px; +} + +.weui-flex { + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-flex__item { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-dialog { + position: fixed; + z-index: 5000; + top: 50%; + left: 16px; + right: 16px; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + background-color: #fff; + background-color: var(--weui-BG-2); + text-align: center; + border-radius: 12px; + overflow: hidden; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + max-height: 90%; +} + +.weui-dialog__hd { + padding: 32px 24px 16px; +} + +.weui-dialog__title { + font-weight: 700; + font-size: 20px; + line-height: 1.4; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-dialog__bd { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + padding: 0 24px; + margin-bottom: 32px; + font-size: 17px; + line-height: 1.4; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-dialog__bd:first-child { + min-height: 40px; + padding: 32px 24px 0; + font-weight: 700; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + -webkit-flex-direction: column; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + flex-direction: column; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; +} + +.weui-dialog__bd:first-child, +.weui-dialog__ft { + display: -webkit-box; + display: -webkit-flex; + display: flex; +} + +.weui-dialog__ft { + position: relative; +} + +.weui-dialog__ft:after { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-DIALOG-LINE-COLOR); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-DIALOG-LINE-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-dialog__btn { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + display: block; + line-height: 1.41176471; + padding: 16px 0; + font-size: 17px; + color: #576b95; + color: var(--weui-LINK); + font-weight: 700; + text-decoration: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-user-select: none; + user-select: none; + position: relative; + overflow: hidden; +} + +.weui-dialog__btn:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-dialog__btn:after { + content: " "; + position: absolute; + left: 0; + top: 0; + width: 1px; + bottom: 0; + border-left: 1px solid rgba(0, 0, 0, 0.1); + border-left: 1px solid var(--weui-DIALOG-LINE-COLOR); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-DIALOG-LINE-COLOR); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleX(0.5); + transform: scaleX(0.5); +} + +.weui-dialog__btn:first-child:after { + display: none; +} + +.weui-dialog__btn_default { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-HALF); +} + +.weui-skin_android .weui-dialog { + text-align: left; + box-shadow: 0 6px 30px 0 rgba(0, 0, 0, 0.1); +} + +.weui-skin_android .weui-dialog__title { + font-size: 22px; + line-height: 1.4; +} + +.weui-skin_android .weui-dialog__hd { + text-align: left; +} + +.weui-skin_android .weui-dialog__bd { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + text-align: left; +} + +.weui-skin_android .weui-dialog__bd:first-child { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-skin_android .weui-dialog__ft { + display: block; + text-align: right; + line-height: 40px; + min-height: 40px; + padding: 0 24px 16px; +} + +.weui-skin_android .weui-dialog__ft:after { + display: none; +} + +.weui-skin_android .weui-dialog__btn { + display: inline-block; + vertical-align: top; + padding: 0 0.8em; +} + +.weui-skin_android .weui-dialog__btn:after { + display: none; +} + +.weui-skin_android .weui-dialog__btn:last-child { + margin-right: -0.8em; +} + +.weui-skin_android .weui-dialog__btn_default { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-HALF); +} + +@media screen and (min-width: 352px) { + .weui-dialog { + width: 320px; + margin: 0 auto; + } +} + +.weui-half-screen-dialog { + position: fixed; + left: 0; + right: 0; + bottom: 0; + min-height: 255px; + max-height: 75%; + z-index: 5000; + line-height: 1.4; + background-color: #fff; + background-color: var(--weui-BG-2); + border-top-left-radius: 12px; + border-top-right-radius: 12px; + overflow: hidden; + padding: 0 24px; + padding: 0 calc(24px + constant(safe-area-inset-right)) constant(safe-area-inset-bottom) calc(24px + constant(safe-area-inset-left)); + padding: 0 calc(24px + env(safe-area-inset-right)) env(safe-area-inset-bottom) calc(24px + env(safe-area-inset-left)); + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; +} + +@media only screen and (max-device-height: 558px) { + .weui-half-screen-dialog { + max-height: calc(100% - 16px); + } +} + +.weui-half-screen-dialog__hd { + min-height: 64px; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-flex-shrink: 0; + flex-shrink: 0; +} + +.weui-half-screen-dialog__hd .weui-btn_icon, +.weui-half-screen-dialog__hd .weui-icon-btn { + position: absolute; + top: 50%; + -webkit-transform: translateY(-50%); + transform: translateY(-50%); + color: inherit; +} + +.weui-half-screen-dialog__hd .weui-btn_icon:active, +.weui-half-screen-dialog__hd .weui-icon-btn:active { + opacity: 0.5; +} + +.weui-half-screen-dialog__hd__side { + position: relative; + left: -8px; +} + +.weui-half-screen-dialog__hd__main { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-half-screen-dialog__hd__side+.weui-half-screen-dialog__hd__main { + text-align: center; + padding: 0 40px; +} + +.weui-half-screen-dialog__hd__main+.weui-half-screen-dialog__hd__side { + right: -8px; + left: auto; +} + +.weui-half-screen-dialog__hd__main+.weui-half-screen-dialog__hd__side .weui-btn_icon, +.weui-half-screen-dialog__hd__main+.weui-half-screen-dialog__hd__side .weui-icon-btn { + right: 0; +} + +.weui-half-screen-dialog__title { + display: block; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + font-weight: 700; + font-size: 15px; +} + +.weui-half-screen-dialog__subtitle { + display: block; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + font-size: 10px; +} + +.weui-half-screen-dialog__bd { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-height: 0; + overflow-y: auto; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; + padding-bottom: 56px; + font-size: 14px; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-half-screen-dialog__desc { + font-size: 17px; + font-weight: 700; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + line-height: 1.4; +} + +.weui-half-screen-dialog__tips { + padding-top: 16px; + font-size: 14px; + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + line-height: 1.4; +} + +.weui-half-screen-dialog__ft { + padding: 0 0 64px; + text-align: center; +} + +.weui-half-screen-dialog__ft .weui-btn:nth-last-child(n + 2), +.weui-half-screen-dialog__ft .weui-btn:nth-last-child(n + 2)+.weui-btn { + display: inline-block; + vertical-align: top; + margin: 0 8px; + width: 120px; +} + +.weui-half-screen-dialog__btn-area { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; +} + +.weui-half-screen-dialog__btn-area .weui-btn { + width: 184px; + padding-left: 16px; + padding-right: 16px; +} + +.weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2), +.weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2)+.weui-btn { + margin: 0 8px; + width: 136px; +} + +.weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2)+.weui-btn:first-child, +.weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2):first-child { + margin-left: 0; +} + +.weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2)+.weui-btn:last-child, +.weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2):last-child { + margin-right: 0; +} + +.weui-half-screen-dialog__btn-area+.weui-half-screen-dialog__attachment-area { + margin-top: 24px; + margin-bottom: -34px; +} + +.weui-half-screen-dialog_btn-wrap .weui-half-screen-dialog__btn-area { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; +} + +.weui-half-screen-dialog_btn-wrap .weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2), +.weui-half-screen-dialog_btn-wrap .weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2)+.weui-btn { + width: 184px; + margin: 16px 0 0; +} + +.weui-half-screen-dialog_btn-wrap .weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2)+.weui-btn:first-child, +.weui-half-screen-dialog_btn-wrap .weui-half-screen-dialog__btn-area .weui-btn:nth-last-child(n + 2):first-child { + margin-top: 0; +} + +.weui-half-screen-dialog_large { + max-height: none; + top: 16px; +} + +.weui-icon-more { + -webkit-mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M5 10.25a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 0 1 0-3.5zm7 0a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 0 1 0-3.5zm7 0a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 0 1 0-3.5z'/%3E%3C/svg%3E") no-repeat 50% 50%; + mask: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E %3Cpath fill-opacity='.9' fill-rule='evenodd' d='M5 10.25a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 0 1 0-3.5zm7 0a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 0 1 0-3.5zm7 0a1.75 1.75 0 1 1 0 3.5 1.75 1.75 0 0 1 0-3.5z'/%3E%3C/svg%3E") no-repeat 50% 50%; +} + +.weui-icon-slide-down { + -webkit-mask-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'%3E %3Cdefs%3E %3Crect id='dda90263-a290-4594-926f-6aba8cb4779f-a' width='24' height='24' x='0' y='0' rx='12'/%3E %3C/defs%3E %3Cg fill='none' fill-rule='evenodd'%3E %3Cmask id='dda90263-a290-4594-926f-6aba8cb4779f-b' fill='%23fff'%3E %3Cuse xlink:href='%23dda90263-a290-4594-926f-6aba8cb4779f-a'/%3E %3C/mask%3E %3Cuse fill='%23000' fill-opacity='.05' xlink:href='%23dda90263-a290-4594-926f-6aba8cb4779f-a'/%3E %3Cg fill-opacity='.9' mask='url(%23dda90263-a290-4594-926f-6aba8cb4779f-b)'%3E %3Cpath fill='%23000' d='M11.407 15.464L6.693 10.75l1.179-1.179 4.125 4.125 4.124-4.125L17.3 10.75l-4.714 4.714a.833.833 0 0 1-1.179 0z'/%3E %3C/g%3E %3C/g%3E%3C/svg%3E"); + mask-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 24 24'%3E %3Cdefs%3E %3Crect id='dda90263-a290-4594-926f-6aba8cb4779f-a' width='24' height='24' x='0' y='0' rx='12'/%3E %3C/defs%3E %3Cg fill='none' fill-rule='evenodd'%3E %3Cmask id='dda90263-a290-4594-926f-6aba8cb4779f-b' fill='%23fff'%3E %3Cuse xlink:href='%23dda90263-a290-4594-926f-6aba8cb4779f-a'/%3E %3C/mask%3E %3Cuse fill='%23000' fill-opacity='.05' xlink:href='%23dda90263-a290-4594-926f-6aba8cb4779f-a'/%3E %3Cg fill-opacity='.9' mask='url(%23dda90263-a290-4594-926f-6aba8cb4779f-b)'%3E %3Cpath fill='%23000' d='M11.407 15.464L6.693 10.75l1.179-1.179 4.125 4.125 4.124-4.125L17.3 10.75l-4.714 4.714a.833.833 0 0 1-1.179 0z'/%3E %3C/g%3E %3C/g%3E%3C/svg%3E"); +} + +.weui-icon-btn.weui-icon-btn { + outline: 0; + -webkit-appearance: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + border-width: 0; + background-color: transparent; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + font-size: 0; + width: auto; + height: auto; +} + +.weui-icon-btn_goback.weui-icon-btn_goback { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + width: 1.2em; + height: 2.4em; + -webkit-mask: url("data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10%2019.438L8.955%2020.5l-7.666-7.79a1.02%201.02%200%20010-1.42L8.955%203.5%2010%204.563%202.682%2012%2010%2019.438z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E") no-repeat 50% 50%; + mask: url("data:image/svg+xml,%3Csvg%20width%3D%2212%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M10%2019.438L8.955%2020.5l-7.666-7.79a1.02%201.02%200%20010-1.42L8.955%203.5%2010%204.563%202.682%2012%2010%2019.438z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E") no-repeat 50% 50%; +} + +.weui-icon-btn_close.weui-icon-btn_close { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + width: 1.4em; + height: 2.4em; + -webkit-mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.25%2010.693L6.057%204.5%205%205.557l6.193%206.193L5%2017.943%206.057%2019l6.193-6.193L18.443%2019l1.057-1.057-6.193-6.193L19.5%205.557%2018.443%204.5z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E") no-repeat 50% 50%; + mask: url("data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12.25%2010.693L6.057%204.5%205%205.557l6.193%206.193L5%2017.943%206.057%2019l6.193-6.193L18.443%2019l1.057-1.057-6.193-6.193L19.5%205.557%2018.443%204.5z%22%20fill-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E") no-repeat 50% 50%; +} + +.weui-toast { + position: fixed; + z-index: 5000; + font-size: 10px; + width: 13.6em; + height: 13.6em; + top: 40%; + left: 50%; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + text-align: center; + border-radius: 12px; + color: hsla(0, 0%, 100%, 0.9); + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + background-color: #4c4c4c; + background-color: var(--weui-BG-4); + box-sizing: border-box; + line-height: 1.4; +} + +.weui-toast_text { + width: auto; + height: auto; + min-width: 152px; + max-width: 216px; + padding: 12px 0; +} + +.weui-toast_text .weui-toast__content { + font-size: 14px; +} + +.weui-icon_toast { + display: block; + margin-bottom: 16px; +} + +.weui-icon_toast.weui-icon_toast { + width: 4em; + height: 4em; +} + +.weui-icon_toast.weui-icon-success-no-circle, +.weui-icon_toast.weui-icon-warn { + color: hsla(0, 0%, 100%, 0.9); +} + +.weui-icon_toast.weui-loading { + width: 3.6em; + height: 3.6em; +} + +.weui-icon_toast.weui-primary-loading { + width: 1em; + height: 1em; + font-size: 40px; + color: #ededed; +} + +.weui-icon_toast.weui-primary-loading:before { + border-width: 4px 0 4px 4px; +} + +.weui-icon_toast.weui-primary-loading:after { + border-width: 4px 4px 4px 0; +} + +.weui-icon_toast.weui-primary-loading .weui-primary-loading__dot { + width: 4px; + height: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} + +.weui-toast__content { + font-size: 17px; + padding: 0 12px; + word-wrap: break-word; + -webkit-hyphens: auto; + hyphens: auto; +} + +.weui-toast_text-more .weui-icon_toast { + margin-bottom: 12px; +} + +.weui-toast_text-more .weui-toast__content { + font-size: 14px; + line-height: 1.6; +} + +.weui-mask { + background: rgba(0, 0, 0, 0.6); +} + +.weui-mask, +.weui-mask_transparent { + position: fixed; + z-index: 1000; + top: 0; + right: 0; + left: 0; + bottom: 0; +} + +.weui-actionsheet { + position: fixed; + left: 0; + bottom: 0; + -webkit-transform: translateY(100%); + transform: translateY(100%); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + z-index: 5000; + width: 100%; + background-color: #f7f7f7; + background-color: var(--weui-BG-1); + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + overflow: hidden; +} + +.weui-actionsheet__title { + position: relative; + height: 56px; + padding: 8px 24px; + padding: 8px calc(24px + constant(safe-area-inset-right)) 8px calc(24px + constant(safe-area-inset-left)); + padding: 8px calc(24px + env(safe-area-inset-right)) 8px calc(24px + env(safe-area-inset-left)); + box-sizing: border-box; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + flex-direction: column; + text-align: center; + font-size: 12px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + line-height: 1.4; + background: #fff; + background: var(--weui-BG-2); +} + +.weui-actionsheet__title:before { + content: " "; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 1px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-actionsheet__title .weui-actionsheet__title-text { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-box-orient: vertical; + -webkit-line-clamp: 2; +} + +.weui-actionsheet__action, +.weui-actionsheet__menu { + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + background-color: #fff; + background-color: var(--weui-BG-2); +} + +.weui-actionsheet__action { + margin-top: 8px; +} + +.weui-actionsheet__action .weui-actionsheet__cell:last-child { + padding-bottom: calc(16px + constant(safe-area-inset-bottom)); + padding-bottom: calc(16px + env(safe-area-inset-bottom)); +} + +.weui-actionsheet__cell { + position: relative; + padding: 16px; + padding: 16px calc(16px + constant(safe-area-inset-right)) 16px calc(16px + constant(safe-area-inset-left)); + padding: 16px calc(16px + env(safe-area-inset-right)) 16px calc(16px + env(safe-area-inset-left)); + text-align: center; + font-size: 17px; + line-height: 1.41176471; + overflow: hidden; +} + +.weui-actionsheet__cell:before { + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-actionsheet__cell:active { + background-color: #ececec; + background-color: var(--weui-BG-COLOR-ACTIVE); +} + +.weui-actionsheet__cell:first-child:before { + display: none; +} + +.weui-actionsheet__cell_warn { + color: #fa5151; + color: var(--weui-RED); +} + +.weui-skin_android .weui-actionsheet { + position: fixed; + left: 50%; + top: 50%; + bottom: auto; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + width: 274px; + box-sizing: border-box; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + background: transparent; + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.weui-skin_android .weui-actionsheet__action { + display: none; +} + +.weui-skin_android .weui-actionsheet__menu { + border-radius: 2px; + box-shadow: 0 6px 30px 0 rgba(0, 0, 0, 0.1); +} + +.weui-skin_android .weui-actionsheet__cell { + padding: 16px; + font-size: 17px; + line-height: 1.41176471; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + text-align: left; +} + +.weui-skin_android .weui-actionsheet__cell:first-child { + border-top-left-radius: 2px; + border-top-right-radius: 2px; +} + +.weui-skin_android .weui-actionsheet__cell:last-child { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; +} + +.weui-actionsheet_toggle { + -webkit-transform: translate(0); + transform: translate(0); +} + +.weui-loadmore { + width: 65%; + margin: 20px auto; + text-align: center; + font-size: 0; +} + +.weui-loadmore .weui-loading, +.weui-loadmore .weui-primary-loading { + margin-right: 8px; +} + +.weui-loadmore__tips { + display: inline-block; + vertical-align: middle; + font-size: 14px; + line-height: 1.6; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-loadmore_line { + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + margin-top: 32px; +} + +.weui-loadmore_line .weui-loadmore__tips { + position: relative; + top: -0.9em; + padding: 0 0.55em; + background-color: #fff; + background-color: var(--weui-BG-2); + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +.weui-loadmore_dot .weui-loadmore__tips { + padding: 0 0.16em; +} + +.weui-loadmore_dot .weui-loadmore__tips:before { + content: " "; + width: 4px; + height: 4px; + border-radius: 50%; + background-color: rgba(0, 0, 0, 0.1); + background-color: var(--weui-FG-3); + display: inline-block; + position: relative; + vertical-align: 0; + top: -0.16em; +} + +.weui-badge { + display: inline-block; + padding: 0.15em 0.4em; + min-width: 0.66666667em; + border-radius: 18px; + background-color: #fa5151; + background-color: var(--weui-RED); + color: #fff; + line-height: 1.2; + text-align: center; + font-size: 12px; + vertical-align: middle; +} + +.weui-badge_dot { + padding: 0.4em; + min-width: 0; +} + +.weui-toptips { + display: none; + position: fixed; + -webkit-transform: translateZ(0); + transform: translateZ(0); + top: 8px; + left: 8px; + right: 8px; + padding: 10px; + border-radius: 8px; + font-size: 14px; + text-align: center; + color: #fff; + z-index: 5000; + word-wrap: break-word; + word-break: break-all; +} + +.weui-toptips_warn { + background-color: #fa5151; + background-color: var(--weui-RED); +} + +.weui-list-tips { + list-style: none; + padding-top: 24px; + padding-bottom: 24px; + line-height: 1.4; + font-size: 14px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + position: relative; +} + +.weui-list-tips:before { + content: ""; + content: " "; + position: absolute; + left: 0; + top: 0; + right: 0; + height: 1px; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-list-tips:last-child { + padding-bottom: 0; +} + +.weui-list-tips__item { + position: relative; + padding-left: 15px; + margin: 16px 0; +} + +.weui-list-tips__item:before { + content: "\2022"; + position: absolute; + left: 0; + top: -0.1em; +} + +.weui-list-tips__item:first-child { + margin-top: 0; +} + +.weui-form-preview__list+.weui-list-tips>.weui-list-tips__item:first-child { + margin-top: 6px; +} + +.weui-search-bar { + position: relative; + padding: 8px; + display: -webkit-box; + display: -webkit-flex; + display: flex; + box-sizing: border-box; + background-color: #ededed; + background-color: var(--weui-BG-0); + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-search-bar.weui-search-bar_focusing .weui-search-bar__cancel-btn { + display: block; +} + +.weui-search-bar.weui-search-bar_focusing .weui-search-bar__label { + display: none; +} + +.weui-search-bar .weui-icon-search { + font-size: 10px; + width: 1.6em; + height: 1.6em; + margin-left: 8px; + margin-right: 4px; + -webkit-flex-shrink: 0; + flex-shrink: 0; +} + +.weui-search-bar__form { + position: relative; + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + min-width: 0; + background-color: #fff; + background-color: var(--weui-BG-2); + border-radius: 4px; +} + +.weui-search-bar__box { + position: relative; + z-index: 1; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-search-bar__box .weui-search-bar__input { + padding: 8px 0; + width: 100%; + height: 1.14285714em; + border: 0; + font-size: 14px; + line-height: 1.14285714em; + box-sizing: content-box; + background: transparent; + caret-color: #07c160; + caret-color: var(--weui-BRAND); + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); +} + +.weui-search-bar__box .weui-search-bar__input:focus { + outline: none; +} + +.weui-search-bar__box .weui-icon-clear { + -webkit-flex-shrink: 0; + flex-shrink: 0; + font-size: 10px; + width: 2em; + height: 2em; + margin-left: 8px; + -webkit-mask-size: 2em; + mask-size: 2em; + -webkit-mask-position: calc(100% - 8px) 0; + mask-position: calc(100% - 8px) 0; + min-width: 44px; +} + +.weui-search-bar__box .weui-icon-clear:after { + content: ""; + position: absolute; + top: 0; + bottom: 0; + width: 44px; +} + +.weui-search-bar__label { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 2; + font-size: 0; + border-radius: 4px; + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; + -webkit-box-pack: center; + -webkit-justify-content: center; + justify-content: center; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + background: #fff; + background: var(--weui-BG-2); +} + +.weui-search-bar__label span { + display: inline-block; + font-size: 14px; + vertical-align: middle; +} + +.weui-search-bar__cancel-btn { + -webkit-flex-shrink: 0; + flex-shrink: 0; + display: none; + margin-left: 8px; + line-height: 28px; + color: #576b95; + color: var(--weui-LINK); +} + +.weui-search-bar__input:not(:valid)+.weui-icon-clear { + display: none; +} + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-results-button, +input[type="search"]::-webkit-search-results-decoration { + display: none; +} + +.weui-picker { + position: fixed; + width: 100%; + box-sizing: border-box; + left: 0; + bottom: 0; + z-index: 5000; + background-color: #fff; + background-color: var(--weui-BG-2); + padding-bottom: constant(safe-area-inset-bottom); + padding-bottom: env(safe-area-inset-bottom); + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-transform: translateY(100%); + transform: translateY(100%); + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; +} + +.weui-picker__hd { + display: -webkit-box; + display: -webkit-flex; + display: flex; + padding: 16px; + padding: 16px calc(16px + constant(safe-area-inset-right)) 16px calc(16px + constant(safe-area-inset-left)); + padding: 16px calc(16px + env(safe-area-inset-right)) 16px calc(16px + env(safe-area-inset-left)); + position: relative; + text-align: center; + font-size: 17px; + line-height: 1.4; +} + +.weui-picker__hd:after { + content: " "; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 1px; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-picker__bd { + display: -webkit-box; + display: -webkit-flex; + display: flex; + position: relative; + background-color: #fff; + background-color: var(--weui-BG-2); + height: 240px; + overflow: hidden; +} + +.weui-picker__group { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; + position: relative; + height: 100%; +} + +.weui-picker__group:first-child .weui-picker__item { + padding-left: constant(safe-area-inset-left); + padding-left: env(safe-area-inset-left); +} + +.weui-picker__group:last-child .weui-picker__item { + padding-right: constant(safe-area-inset-right); + padding-right: env(safe-area-inset-right); +} + +.weui-picker__mask { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + margin: 0 auto; + z-index: 3; + background-image: -webkit-linear-gradient(top, + hsla(0, 0%, 100%, 0.95), + hsla(0, 0%, 100%, 0.6)), + -webkit-linear-gradient(bottom, hsla(0, 0%, 100%, 0.95), hsla(0, 0%, 100%, 0.6)); + background-image: linear-gradient(180deg, + hsla(0, 0%, 100%, 0.95), + hsla(0, 0%, 100%, 0.6)), + linear-gradient(0deg, hsla(0, 0%, 100%, 0.95), hsla(0, 0%, 100%, 0.6)); + background-position: top, bottom; + background-size: 100% 92px; + background-repeat: no-repeat; + -webkit-transform: translateZ(0); + transform: translateZ(0); +} + +body[data-weui-theme="dark"] .weui-picker__mask { + background-image: -webkit-linear-gradient(top, + rgba(25, 25, 25, 0.95), + rgba(25, 25, 25, 0.6)), + -webkit-linear-gradient(bottom, rgba(25, 25, 25, 0.95), rgba(25, 25, 25, 0.6)); + background-image: linear-gradient(180deg, + rgba(25, 25, 25, 0.95), + rgba(25, 25, 25, 0.6)), + linear-gradient(0deg, rgba(25, 25, 25, 0.95), rgba(25, 25, 25, 0.6)); +} + +@media (prefers-color-scheme: dark) { + body:not([data-weui-theme="light"]) .weui-picker__mask { + background-image: -webkit-linear-gradient(top, + rgba(25, 25, 25, 0.95), + rgba(25, 25, 25, 0.6)), + -webkit-linear-gradient(bottom, rgba(25, 25, 25, 0.95), rgba(25, 25, 25, 0.6)); + background-image: linear-gradient(180deg, + rgba(25, 25, 25, 0.95), + rgba(25, 25, 25, 0.6)), + linear-gradient(0deg, rgba(25, 25, 25, 0.95), rgba(25, 25, 25, 0.6)); + } +} + +.weui-picker__indicator { + width: 100%; + height: 56px; + position: absolute; + left: 0; + top: 92px; + z-index: 3; +} + +.weui-picker__indicator:before { + top: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); + border-top: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 0; + transform-origin: 0 0; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-picker__indicator:after, +.weui-picker__indicator:before { + content: " "; + position: absolute; + left: 0; + right: 0; + height: 1px; + color: rgba(0, 0, 0, 0.1); + color: var(--weui-FG-3); +} + +.weui-picker__indicator:after { + bottom: 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--weui-FG-3); + -webkit-transform-origin: 0 100%; + transform-origin: 0 100%; + -webkit-transform: scaleY(0.5); + transform: scaleY(0.5); +} + +.weui-picker__content { + position: absolute; + top: 0; + left: 0; + width: 100%; +} + +.weui-picker__item { + height: 48px; + line-height: 48px; + text-align: center; + color: rgba(0, 0, 0, 0.9); + color: var(--weui-FG-0); + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.weui-picker__item_disabled { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); +} + +@-webkit-keyframes a { + 0% { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + -webkit-transform: translateZ(0); + transform: translateZ(0); + } +} + +@keyframes a { + 0% { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } + + to { + -webkit-transform: translateZ(0); + transform: translateZ(0); + } +} + +.weui-animate-slide-up, +.weui-animate_slide-up { + -webkit-animation: a ease 0.3s forwards; + animation: a ease 0.3s forwards; +} + +@-webkit-keyframes b { + 0% { + -webkit-transform: translateZ(0); + transform: translateZ(0); + } + + to { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +@keyframes b { + 0% { + -webkit-transform: translateZ(0); + transform: translateZ(0); + } + + to { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + } +} + +.weui-animate-slide-down, +.weui-animate_slide-down { + -webkit-animation: b ease 0.3s forwards; + animation: b ease 0.3s forwards; +} + +@-webkit-keyframes c { + 0% { + opacity: 0; + } + + to { + opacity: 1; + } +} + +@keyframes c { + 0% { + opacity: 0; + } + + to { + opacity: 1; + } +} + +.weui-animate-fade-in, +.weui-animate_fade-in { + -webkit-animation: c ease 0.3s forwards; + animation: c ease 0.3s forwards; +} + +@-webkit-keyframes d { + 0% { + opacity: 1; + } + + to { + opacity: 0; + } +} + +@keyframes d { + 0% { + opacity: 1; + } + + to { + opacity: 0; + } +} + +.weui-animate-fade-out, +.weui-animate_fade-out { + -webkit-animation: d ease 0.3s forwards; + animation: d ease 0.3s forwards; +} + +.weui-transition.weui-mask { + -webkit-transition: opacity 0.3s, visibility 0.3s; + transition: opacity 0.3s, visibility 0.3s; + opacity: 0; + visibility: hidden; +} + +.weui-transition.weui-half-screen-dialog { + -webkit-transition: -webkit-transform 0.3s; + transition: -webkit-transform 0.3s; + transition: transform 0.3s; + transition: transform 0.3s, -webkit-transform 0.3s; + -webkit-transform: translateY(100%); + transform: translateY(100%); +} + +.weui-transition_show.weui-mask { + opacity: 1; + visibility: visible; +} + +.weui-transition_show.weui-half-screen-dialog { + -webkit-transform: translateY(0); + transform: translateY(0); +} + +.weui-agree { + display: block; + padding: 8px 15px 0; + font-size: 14px; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +.weui-agree a, +.weui-agree navigator { + color: #576b95; + color: var(--weui-LINK); +} + +.weui-agree navigator { + display: inline; +} + +.weui-agree__text { + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + margin-left: 2px; +} + +.weui-agree__checkbox { + -webkit-appearance: none; + appearance: none; + display: inline-block; + border: 0; + outline: 0; + vertical-align: middle; + background-color: currentColor; + -webkit-mask-position: 0 0; + mask-position: 0 0; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + -webkit-mask-size: 100%; + mask-size: 100%; + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%221000%22%20height%3D%221000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M500%20916.667C269.881%20916.667%2083.333%20730.119%2083.333%20500%2083.333%20269.881%20269.881%2083.333%20500%2083.333c230.119%200%20416.667%20186.548%20416.667%20416.667%200%20230.119-186.548%20416.667-416.667%20416.667zm0-50c202.504%200%20366.667-164.163%20366.667-366.667%200-202.504-164.163-366.667-366.667-366.667-202.504%200-366.667%20164.163-366.667%20366.667%200%20202.504%20164.163%20366.667%20366.667%20366.667z%22%20fill-rule%3D%22evenodd%22%20fill-opacity%3D%22.9%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%221000%22%20height%3D%221000%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M500%20916.667C269.881%20916.667%2083.333%20730.119%2083.333%20500%2083.333%20269.881%20269.881%2083.333%20500%2083.333c230.119%200%20416.667%20186.548%20416.667%20416.667%200%20230.119-186.548%20416.667-416.667%20416.667zm0-50c202.504%200%20366.667-164.163%20366.667-366.667%200-202.504-164.163-366.667-366.667-366.667-202.504%200-366.667%20164.163-366.667%20366.667%200%20202.504%20164.163%20366.667%20366.667%20366.667z%22%20fill-rule%3D%22evenodd%22%20fill-opacity%3D%22.9%22%2F%3E%3C%2Fsvg%3E); + color: rgba(0, 0, 0, 0.3); + color: var(--weui-FG-2); + width: 1em; + height: 1em; + font-size: 17px; + margin-top: -0.2em; +} + +.weui-agree__checkbox-check { + opacity: 0; + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; +} + +.weui-agree__checkbox-check[aria-checked="true"]+.weui-agree__checkbox, +.weui-agree__checkbox:checked { + -webkit-mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-1.177-7.86l-2.765-2.767L7%2012.431l3.119%203.121a1%201%200%20001.414%200l5.952-5.95-1.062-1.062-5.6%205.6z%22%2F%3E%3C%2Fsvg%3E); + mask-image: url(data:image/svg+xml,%3Csvg%20width%3D%2224%22%20height%3D%2224%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M12%2022C6.477%2022%202%2017.523%202%2012S6.477%202%2012%202s10%204.477%2010%2010-4.477%2010-10%2010zm-1.177-7.86l-2.765-2.767L7%2012.431l3.119%203.121a1%201%200%20001.414%200l5.952-5.95-1.062-1.062-5.6%205.6z%22%2F%3E%3C%2Fsvg%3E); + color: #07c160; + color: var(--weui-BRAND); +} + +.weui-agree_animate { + -webkit-animation: e 0.3s 1; + animation: e 0.3s 1; +} + +@-webkit-keyframes e { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 16% { + -webkit-transform: translateX(-8px); + transform: translateX(-8px); + } + + 28% { + -webkit-transform: translateX(-16px); + transform: translateX(-16px); + } + + 44% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 59% { + -webkit-transform: translateX(-16px); + transform: translateX(-16px); + } + + 73% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 82% { + -webkit-transform: translateX(16px); + transform: translateX(16px); + } + + 94% { + -webkit-transform: translateX(8px); + transform: translateX(8px); + } + + to { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +@keyframes e { + 0% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 16% { + -webkit-transform: translateX(-8px); + transform: translateX(-8px); + } + + 28% { + -webkit-transform: translateX(-16px); + transform: translateX(-16px); + } + + 44% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 59% { + -webkit-transform: translateX(-16px); + transform: translateX(-16px); + } + + 73% { + -webkit-transform: translateX(0); + transform: translateX(0); + } + + 82% { + -webkit-transform: translateX(16px); + transform: translateX(16px); + } + + 94% { + -webkit-transform: translateX(8px); + transform: translateX(8px); + } + + to { + -webkit-transform: translateX(0); + transform: translateX(0); + } +} + +.weui-primary-loading { + font-size: 16px; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: inline-flex; + position: relative; + width: 1em; + height: 1em; + vertical-align: middle; + color: #606060; + -webkit-animation: f 1s steps(60) infinite; + animation: f 1s steps(60) infinite; +} + +.weui-primary-loading:after, +.weui-primary-loading:before { + content: ""; + display: block; + width: 0.5em; + height: 1em; + box-sizing: border-box; + border: 0.125em solid; + border-color: currentColor; +} + +.weui-primary-loading:before { + border-right-width: 0; + border-top-left-radius: 1em; + border-bottom-left-radius: 1em; + -webkit-mask-image: -webkit-linear-gradient(top, + #000 8%, + rgba(0, 0, 0, 0.3) 95%); +} + +.weui-primary-loading:after { + border-left-width: 0; + border-top-right-radius: 1em; + border-bottom-right-radius: 1em; + -webkit-mask-image: -webkit-linear-gradient(top, + transparent 8%, + rgba(0, 0, 0, 0.3) 95%); +} + +.weui-primary-loading__dot { + position: absolute; + top: 0; + left: 50%; + margin-left: -0.0625em; + width: 0.125em; + height: 0.125em; + border-top-right-radius: 0.125em; + border-bottom-right-radius: 0.125em; + background: currentColor; +} + +.weui-primary-loading_brand { + color: #07c160; + color: var(--weui-BRAND); +} + +.weui-primary-loading_transparent { + color: #ededed; +} + +.weui-loading { + font-size: 10px; + width: 2em; + height: 2em; + display: inline-block; + vertical-align: middle; + -webkit-animation: f 1s steps(12) infinite; + animation: f 1s steps(12) infinite; + background: transparent url("data:image/svg+xml;charset=utf8, %3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 100 100'%3E%3Cpath fill='none' d='M0 0h100v100H0z'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23E9E9E9' rx='5' ry='5' transform='translate(0 -30)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23989697' rx='5' ry='5' transform='rotate(30 105.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%239B999A' rx='5' ry='5' transform='rotate(60 75.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23A3A1A2' rx='5' ry='5' transform='rotate(90 65 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23ABA9AA' rx='5' ry='5' transform='rotate(120 58.66 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23B2B2B2' rx='5' ry='5' transform='rotate(150 54.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23BAB8B9' rx='5' ry='5' transform='rotate(180 50 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23C2C0C1' rx='5' ry='5' transform='rotate(-150 45.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23CBCBCB' rx='5' ry='5' transform='rotate(-120 41.34 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23D2D2D2' rx='5' ry='5' transform='rotate(-90 35 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23DADADA' rx='5' ry='5' transform='rotate(-60 24.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='%23E2E2E2' rx='5' ry='5' transform='rotate(-30 -5.98 65)'/%3E%3C/svg%3E") no-repeat; + background-size: 100%; +} + +.weui-btn_loading.weui-btn_primary .weui-loading, +.weui-loading.weui-loading_transparent { + background-image: url("data:image/svg+xml;charset=utf8, %3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 100 100'%3E%3Cpath fill='none' d='M0 0h100v100H0z'/%3E%3Crect xmlns='http://www.w3.org/2000/svg' width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.56)' rx='5' ry='5' transform='translate(0 -30)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.5)' rx='5' ry='5' transform='rotate(30 105.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.43)' rx='5' ry='5' transform='rotate(60 75.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.38)' rx='5' ry='5' transform='rotate(90 65 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.32)' rx='5' ry='5' transform='rotate(120 58.66 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.28)' rx='5' ry='5' transform='rotate(150 54.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.25)' rx='5' ry='5' transform='rotate(180 50 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.2)' rx='5' ry='5' transform='rotate(-150 45.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.17)' rx='5' ry='5' transform='rotate(-120 41.34 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.14)' rx='5' ry='5' transform='rotate(-90 35 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.1)' rx='5' ry='5' transform='rotate(-60 24.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.03)' rx='5' ry='5' transform='rotate(-30 -5.98 65)'/%3E%3C/svg%3E"); +} + +@-webkit-keyframes f { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + to { + -webkit-transform: rotate(1turn); + transform: rotate(1turn); + } +} + +@keyframes f { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + + to { + -webkit-transform: rotate(1turn); + transform: rotate(1turn); + } +} + +.weui-slider { + padding: 15px 18px; + -webkit-user-select: none; + user-select: none; +} + +.weui-slider__inner { + position: relative; + height: 2px; + background-color: rgba(0, 0, 0, 0.1); + background-color: var(--weui-FG-3); +} + +.weui-slider__track { + height: 100%; + background-color: #07c160; + background-color: var(--weui-BRAND); + width: 0; +} + +.weui-slider__handler { + position: absolute; + left: 0; + top: 50%; + width: 28px; + height: 28px; + margin-left: -14px; + margin-top: -14px; + border-radius: 50%; + background-color: #fff; + box-shadow: 0 0 4px rgba(0, 0, 0, 0.1); + box-shadow: 0 0 4px var(--weui-FG-3); +} + +.weui-slider-box { + display: -webkit-box; + display: -webkit-flex; + display: flex; + -webkit-box-align: center; + -webkit-align-items: center; + align-items: center; +} + +.weui-slider-box .weui-slider { + -webkit-box-flex: 1; + -webkit-flex: 1; + flex: 1; +} + +.weui-slider-box__value { + margin-left: 0.5em; + min-width: 24px; + color: rgba(0, 0, 0, 0.5); + color: var(--weui-FG-1); + text-align: center; + font-size: 14px; +} \ No newline at end of file diff --git a/app/src/main/assets/css/ui.css b/app/src/main/assets/css/ui.css new file mode 100644 index 00000000..341b3cb0 --- /dev/null +++ b/app/src/main/assets/css/ui.css @@ -0,0 +1,26 @@ +body, +html { + height: 100%; + -webkit-tap-highlight-color: transparent; +} + +body { + font-family: system-ui, -apple-system, Helvetica Neue, sans-serif; +} + +.page, +body { + background-color: var(--weui-BG-0); +} + +.page { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + box-sizing: border-box; + z-index: 1; +} \ No newline at end of file diff --git a/app/src/main/assets/favicon.ico b/app/src/main/assets/favicon.ico new file mode 100644 index 00000000..dd17961a Binary files /dev/null and b/app/src/main/assets/favicon.ico differ diff --git a/app/src/main/assets/images/ic_local.svg b/app/src/main/assets/images/ic_local.svg new file mode 100644 index 00000000..c8da56da --- /dev/null +++ b/app/src/main/assets/images/ic_local.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/images/ic_push.svg b/app/src/main/assets/images/ic_push.svg new file mode 100644 index 00000000..bf38f28b --- /dev/null +++ b/app/src/main/assets/images/ic_push.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/images/ic_search.svg b/app/src/main/assets/images/ic_search.svg new file mode 100644 index 00000000..593f6280 --- /dev/null +++ b/app/src/main/assets/images/ic_search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/images/ic_setting.svg b/app/src/main/assets/images/ic_setting.svg new file mode 100644 index 00000000..4165162b --- /dev/null +++ b/app/src/main/assets/images/ic_setting.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/assets/index.html b/app/src/main/assets/index.html new file mode 100644 index 00000000..dc332d77 --- /dev/null +++ b/app/src/main/assets/index.html @@ -0,0 +1,215 @@ + + + + + 影視 + + + + + + + + + + + +
+
+
+
+
+
+

搜尋

+
+
+
+
+
+
+ +
+
+
+
+ +
+
+
+
+
+ + + +
+ + + + +
+
+
+
+ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/js/jquery.min.js b/app/src/main/assets/js/jquery.min.js new file mode 100644 index 00000000..7f37b5d9 --- /dev/null +++ b/app/src/main/assets/js/jquery.min.js @@ -0,0 +1,2 @@ +/*! jQuery v3.7.1 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(ie,e){"use strict";var oe=[],r=Object.getPrototypeOf,ae=oe.slice,g=oe.flat?function(e){return oe.flat.call(e)}:function(e){return oe.concat.apply([],e)},s=oe.push,se=oe.indexOf,n={},i=n.toString,ue=n.hasOwnProperty,o=ue.toString,a=o.call(Object),le={},v=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},y=function(e){return null!=e&&e===e.window},C=ie.document,u={type:!0,src:!0,nonce:!0,noModule:!0};function m(e,t,n){var r,i,o=(n=n||C).createElement("script");if(o.text=e,t)for(r in u)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function x(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[i.call(e)]||"object":typeof e}var t="3.7.1",l=/HTML$/i,ce=function(e,t){return new ce.fn.init(e,t)};function c(e){var t=!!e&&"length"in e&&e.length,n=x(e);return!v(e)&&!y(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+ge+")"+ge+"*"),x=new RegExp(ge+"|>"),j=new RegExp(g),A=new RegExp("^"+t+"$"),D={ID:new RegExp("^#("+t+")"),CLASS:new RegExp("^\\.("+t+")"),TAG:new RegExp("^("+t+"|[*])"),ATTR:new RegExp("^"+p),PSEUDO:new RegExp("^"+g),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ge+"*(even|odd|(([+-]|)(\\d*)n|)"+ge+"*(?:([+-]|)"+ge+"*(\\d+)|))"+ge+"*\\)|)","i"),bool:new RegExp("^(?:"+f+")$","i"),needsContext:new RegExp("^"+ge+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ge+"*((?:-\\d)?\\d*)"+ge+"*\\)|)(?=[^-]|$)","i")},N=/^(?:input|select|textarea|button)$/i,q=/^h\d$/i,L=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,H=/[+~]/,O=new RegExp("\\\\[\\da-fA-F]{1,6}"+ge+"?|\\\\([^\\r\\n\\f])","g"),P=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},M=function(){V()},R=J(function(e){return!0===e.disabled&&fe(e,"fieldset")},{dir:"parentNode",next:"legend"});try{k.apply(oe=ae.call(ye.childNodes),ye.childNodes),oe[ye.childNodes.length].nodeType}catch(e){k={apply:function(e,t){me.apply(e,ae.call(t))},call:function(e){me.apply(e,ae.call(arguments,1))}}}function I(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(V(e),e=e||T,C)){if(11!==p&&(u=L.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return k.call(n,a),n}else if(f&&(a=f.getElementById(i))&&I.contains(e,a)&&a.id===i)return k.call(n,a),n}else{if(u[2])return k.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&e.getElementsByClassName)return k.apply(n,e.getElementsByClassName(i)),n}if(!(h[t+" "]||d&&d.test(t))){if(c=t,f=e,1===p&&(x.test(t)||m.test(t))){(f=H.test(t)&&U(e.parentNode)||e)==e&&le.scope||((s=e.getAttribute("id"))?s=ce.escapeSelector(s):e.setAttribute("id",s=S)),o=(l=Y(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+Q(l[o]);c=l.join(",")}try{return k.apply(n,f.querySelectorAll(c)),n}catch(e){h(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return re(t.replace(ve,"$1"),e,n,r)}function W(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function F(e){return e[S]=!0,e}function $(e){var t=T.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function B(t){return function(e){return fe(e,"input")&&e.type===t}}function _(t){return function(e){return(fe(e,"input")||fe(e,"button"))&&e.type===t}}function z(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&R(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function X(a){return F(function(o){return o=+o,F(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function U(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function V(e){var t,n=e?e.ownerDocument||e:ye;return n!=T&&9===n.nodeType&&n.documentElement&&(r=(T=n).documentElement,C=!ce.isXMLDoc(T),i=r.matches||r.webkitMatchesSelector||r.msMatchesSelector,r.msMatchesSelector&&ye!=T&&(t=T.defaultView)&&t.top!==t&&t.addEventListener("unload",M),le.getById=$(function(e){return r.appendChild(e).id=ce.expando,!T.getElementsByName||!T.getElementsByName(ce.expando).length}),le.disconnectedMatch=$(function(e){return i.call(e,"*")}),le.scope=$(function(){return T.querySelectorAll(":scope")}),le.cssHas=$(function(){try{return T.querySelector(":has(*,:jqfake)"),!1}catch(e){return!0}}),le.getById?(b.filter.ID=function(e){var t=e.replace(O,P);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(O,P);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&C){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):t.querySelectorAll(e)},b.find.CLASS=function(e,t){if("undefined"!=typeof t.getElementsByClassName&&C)return t.getElementsByClassName(e)},d=[],$(function(e){var t;r.appendChild(e).innerHTML="",e.querySelectorAll("[selected]").length||d.push("\\["+ge+"*(?:value|"+f+")"),e.querySelectorAll("[id~="+S+"-]").length||d.push("~="),e.querySelectorAll("a#"+S+"+*").length||d.push(".#.+[+~]"),e.querySelectorAll(":checked").length||d.push(":checked"),(t=T.createElement("input")).setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),r.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&d.push(":enabled",":disabled"),(t=T.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||d.push("\\["+ge+"*name"+ge+"*="+ge+"*(?:''|\"\")")}),le.cssHas||d.push(":has"),d=d.length&&new RegExp(d.join("|")),l=function(e,t){if(e===t)return a=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!le.sortDetached&&t.compareDocumentPosition(e)===n?e===T||e.ownerDocument==ye&&I.contains(ye,e)?-1:t===T||t.ownerDocument==ye&&I.contains(ye,t)?1:o?se.call(o,e)-se.call(o,t):0:4&n?-1:1)}),T}for(e in I.matches=function(e,t){return I(e,null,null,t)},I.matchesSelector=function(e,t){if(V(e),C&&!h[t+" "]&&(!d||!d.test(t)))try{var n=i.call(e,t);if(n||le.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){h(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(O,P),e[3]=(e[3]||e[4]||e[5]||"").replace(O,P),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||I.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&I.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return D.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&j.test(n)&&(t=Y(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(O,P).toLowerCase();return"*"===e?function(){return!0}:function(e){return fe(e,t)}},CLASS:function(e){var t=s[e+" "];return t||(t=new RegExp("(^|"+ge+")"+e+"("+ge+"|$)"))&&s(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=I.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function T(e,n,r){return v(n)?ce.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?ce.grep(e,function(e){return e===n!==r}):"string"!=typeof n?ce.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(ce.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||k,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:S.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof ce?t[0]:t,ce.merge(this,ce.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:C,!0)),w.test(r[1])&&ce.isPlainObject(t))for(r in t)v(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=C.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):v(e)?void 0!==n.ready?n.ready(e):e(ce):ce.makeArray(e,this)}).prototype=ce.fn,k=ce(C);var E=/^(?:parents|prev(?:Until|All))/,j={children:!0,contents:!0,next:!0,prev:!0};function A(e,t){while((e=e[t])&&1!==e.nodeType);return e}ce.fn.extend({has:function(e){var t=ce(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,Ce=/^$|^module$|\/(?:java|ecma)script/i;xe=C.createDocumentFragment().appendChild(C.createElement("div")),(be=C.createElement("input")).setAttribute("type","radio"),be.setAttribute("checked","checked"),be.setAttribute("name","t"),xe.appendChild(be),le.checkClone=xe.cloneNode(!0).cloneNode(!0).lastChild.checked,xe.innerHTML="",le.noCloneChecked=!!xe.cloneNode(!0).lastChild.defaultValue,xe.innerHTML="",le.option=!!xe.lastChild;var ke={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function Se(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&fe(e,t)?ce.merge([e],n):n}function Ee(e,t){for(var n=0,r=e.length;n",""]);var je=/<|&#?\w+;/;function Ae(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function Re(e,t){return fe(e,"table")&&fe(11!==t.nodeType?t:t.firstChild,"tr")&&ce(e).children("tbody")[0]||e}function Ie(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function We(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Fe(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(_.hasData(e)&&(s=_.get(e).events))for(i in _.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),C.head.appendChild(r[0])},abort:function(){i&&i()}}});var Jt,Kt=[],Zt=/(=)\?(?=&|$)|\?\?/;ce.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Kt.pop()||ce.expando+"_"+jt.guid++;return this[e]=!0,e}}),ce.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Zt.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Zt.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=v(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Zt,"$1"+r):!1!==e.jsonp&&(e.url+=(At.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||ce.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=ie[r],ie[r]=function(){o=arguments},n.always(function(){void 0===i?ce(ie).removeProp(r):ie[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Kt.push(r)),o&&v(i)&&i(o[0]),o=i=void 0}),"script"}),le.createHTMLDocument=((Jt=C.implementation.createHTMLDocument("").body).innerHTML="
",2===Jt.childNodes.length),ce.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(le.createHTMLDocument?((r=(t=C.implementation.createHTMLDocument("")).createElement("base")).href=C.location.href,t.head.appendChild(r)):t=C),o=!n&&[],(i=w.exec(e))?[t.createElement(i[1])]:(i=Ae([e],t,o),o&&o.length&&ce(o).remove(),ce.merge([],i.childNodes)));var r,i,o},ce.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(ce.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},ce.expr.pseudos.animated=function(t){return ce.grep(ce.timers,function(e){return t===e.elem}).length},ce.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=ce.css(e,"position"),c=ce(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=ce.css(e,"top"),u=ce.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),v(t)&&(t=t.call(e,n,ce.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},ce.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){ce.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===ce.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===ce.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=ce(e).offset()).top+=ce.css(e,"borderTopWidth",!0),i.left+=ce.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-ce.css(r,"marginTop",!0),left:t.left-i.left-ce.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===ce.css(e,"position"))e=e.offsetParent;return e||J})}}),ce.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;ce.fn[t]=function(e){return M(this,function(e,t,n){var r;if(y(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),ce.each(["top","left"],function(e,n){ce.cssHooks[n]=Ye(le.pixelPosition,function(e,t){if(t)return t=Ge(e,n),_e.test(t)?ce(e).position()[n]+"px":t})}),ce.each({Height:"height",Width:"width"},function(a,s){ce.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){ce.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return M(this,function(e,t,n){var r;return y(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?ce.css(e,t,i):ce.style(e,t,n,i)},s,n?e:void 0,n)}})}),ce.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){ce.fn[t]=function(e){return this.on(t,e)}}),ce.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.on("mouseenter",e).on("mouseleave",t||e)}}),ce.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){ce.fn[n]=function(e,t){return 0 +
+ + .. + + + + `; +} + +function tpl_dir(name, time, path) { + return ` +
+ + ` + name + ` +
` + time + `
+
+ + +
`; +} + +function tpl_file(name, time, path, canDel) { + return ` +
+ + ` + name + ` +
` + time + `
+
+
`; +} + +function clear_list() { + $('#file_list').html(''); +} + +function add_file(node) { + $('#file_list').append(node); +} + +function selectFile(path, canDel) { + current_file = path; + if (canDel) $("#delFileBtn").show(); + else $("#delFileBtn").hide(); + $("#fileUrl").html("file:/" + current_file); + $("#fileInfoDialog").show(); +} + +function pushFile(yes) { + hideFileInfo(); + if (yes == 1) { + file("file:/" + current_file); + } +} + +function hideFileInfo() { + $("#fileInfoDialog").hide(); +} + +function listFile(path) { + $('#loadingToast').show(); + $.get('/file' + path, function (res) { + let info = JSON.parse(res); + let parent = info.parent; + let canDel = info.parent != '.'; + current_root = path; + current_parent = parent; + let array = info.files; + if (path === '' && array.length == 0) { + warnToast('可能沒有存儲權限'); + } + clear_list(); + if (parent !== '.') { + add_file(tpl_top(parent)); + } + if (canDel) { + $('#delCurFolder').show(); + } else { + $('#delCurFolder').hide(); + } + array.forEach(node => { + if (node.dir === 1) { + add_file(tpl_dir(node.name, node.time, node.path)); + } else { + add_file(tpl_file(node.name, node.time, node.path, canDel)); + } + }); + $('#loadingToast').hide(); + }) +} + + +function uploadFile() { + $('#file_uploader').click(); +} + +function uploadTip() { + let files = $('#file_uploader')[0].files; + if (files.length <= 0) return false; + let tip = ''; + for (var i = 0; i < files.length; i++) { + tip += (files[i].name) + ','; + } + tip = tip.substring(0, tip.length - 1); + $('#uploadTipContent').html(tip); + $('#uploadTip').show(); +} + +function doUpload(yes) { + $('#uploadTip').hide(); + if (yes == 1) { + let files = $('#file_uploader')[0].files; + if (files.length <= 0) return false; + var formData = new FormData(); + formData.append('path', current_root); + for (i = 0; i < files.length; i++) { + formData.append("files-" + i, files[i]); + } + $('#loadingToast').show(); + $.ajax({ + url: '/upload', + type: 'post', + data: formData, + processData: false, + contentType: false, + complete: function () { + $('#loadingToast').hide(); + listFile(current_root); + } + }); + } +} + +function newFolder() { + $('#newFolder').show(); +} + +function doNewFolder(yes) { + $('#newFolder').hide(); + if (yes == 1) { + let name = $('#newFolderContent')[0].value.trim(); + if (name.length <= 0) return false; + $('#loadingToast').show(); + $.post('/newFolder', { path: current_root, name: name }, function (data) { + $('#loadingToast').hide(); + listFile(current_root); + }); + } +} + +function delFolder() { + $('#delFolderContent').html('是否刪除 ' + current_root); + $('#delFolder').show(); +} + +function doDelFolder(yes) { + $('#delFolder').hide(); + if (yes == 1) { + $('#loadingToast').show(); + $.post('/delFolder', { path: current_root }, function (data) { + $('#loadingToast').hide(); + listFile(current_parent); + }); + } +} + +function delFile() { + $('#delFileContent').html('是否刪除 ' + current_file); + $('#delFile').show(); + hideFileInfo(); +} + +function doDelFile(yes) { + $('#delFile').hide(); + if (yes == 1) { + $('#loadingToast').show(); + $.post('/delFile', { path: current_file }, function (data) { + $('#loadingToast').hide(); + listFile(current_root); + }); + } +} + +function warnToast(msg) { + $('#warnToastContent').html(msg); + $('#warnToast').show(); + setTimeout(() => { + $('#warnToast').hide(); + }, 1000); +} + +function showPanel(id) { + let tab = $('#tab' + id); + $(tab).attr('aria-selected', 'true').addClass('weui-bar__item_on'); + $(tab).siblings('.weui-bar__item_on').removeClass('weui-bar__item_on').attr('aria-selected', 'false'); + var panelId = '#' + $(tab).attr('aria-controls'); + $(panelId).css('display', 'block'); + $(panelId).siblings('.weui-tab__panel').css('display', 'none'); + if (id === 4) listFile('') +} + +$(function () { + $('.weui-tabbar__item').on('click', function () { + showPanel(parseInt($(this).attr('id').substr(3))); + }); +}); + +$(document).ready(function () { + var url = window.location.search; + if (url.indexOf('tab=2') > 0) { + showPanel(2); + } else if (url.indexOf('tab=3') > 0) { + showPanel(3); + } else if (url.indexOf('tab=4') > 0) { + showPanel(4); + } else { + showPanel(1); + } +}); diff --git a/app/src/main/assets/parse.html b/app/src/main/assets/parse.html new file mode 100644 index 00000000..c73b0a8f --- /dev/null +++ b/app/src/main/assets/parse.html @@ -0,0 +1,26 @@ + + + + + + + 解析 + + + +
+ + + + \ No newline at end of file diff --git a/app/src/main/java/com/fongmi/android/tv/App.java b/app/src/main/java/com/fongmi/android/tv/App.java new file mode 100644 index 00000000..5d88da44 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/App.java @@ -0,0 +1,168 @@ +package com.fongmi.android.tv; + +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.os.HandlerCompat; + +import com.fongmi.android.tv.event.EventIndex; +import com.fongmi.android.tv.ui.activity.CrashActivity; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.hook.Hook; +import com.github.catvod.Init; +import com.github.catvod.bean.Doh; +import com.github.catvod.net.OkHttp; +import com.google.gson.Gson; +import com.orhanobut.logger.AndroidLogAdapter; +import com.orhanobut.logger.LogAdapter; +import com.orhanobut.logger.Logger; +import com.orhanobut.logger.PrettyFormatStrategy; + +import org.greenrobot.eventbus.EventBus; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import cat.ereza.customactivityoncrash.config.CaocConfig; + +public class App extends Application { + + private final ExecutorService executor; + private final Handler handler; + private static App instance; + private Activity activity; + private final Gson gson; + private final long time; + private Hook hook; + + public App() { + instance = this; + executor = Executors.newFixedThreadPool(Constant.THREAD_POOL); + handler = HandlerCompat.createAsync(Looper.getMainLooper()); + time = System.currentTimeMillis(); + gson = new Gson(); + } + + public static App get() { + return instance; + } + + public static Gson gson() { + return get().gson; + } + + public static long time() { + return get().time; + } + + public static Activity activity() { + return get().activity; + } + + public static void execute(Runnable runnable) { + get().executor.execute(runnable); + } + + public static void post(Runnable runnable) { + get().handler.post(runnable); + } + + public static void post(Runnable runnable, long delayMillis) { + get().handler.removeCallbacks(runnable); + if (delayMillis >= 0) get().handler.postDelayed(runnable, delayMillis); + } + + public static void removeCallbacks(Runnable runnable) { + get().handler.removeCallbacks(runnable); + } + + public static void removeCallbacks(Runnable... runnable) { + for (Runnable r : runnable) get().handler.removeCallbacks(r); + } + + public void setHook(Hook hook) { + this.hook = hook; + } + + private void setActivity(Activity activity) { + this.activity = activity; + } + + private LogAdapter getLogAdapter() { + return new AndroidLogAdapter(PrettyFormatStrategy.newBuilder().methodCount(0).showThreadInfo(false).tag("").build()) { + @Override + public boolean isLoggable(int priority, String tag) { + return true; + } + }; + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + Init.set(base); + } + + @Override + public void onCreate() { + super.onCreate(); + Notify.createChannel(); + Logger.addLogAdapter(getLogAdapter()); + OkHttp.get().setProxy(Setting.getProxy()); + OkHttp.get().setDoh(Doh.objectFrom(Setting.getDoh())); + EventBus.builder().addIndex(new EventIndex()).installDefaultEventBus(); + CaocConfig.Builder.create().backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT).errorActivity(CrashActivity.class).apply(); + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) { + if (activity != activity()) setActivity(activity); + } + + @Override + public void onActivityStarted(@NonNull Activity activity) { + if (activity != activity()) setActivity(activity); + } + + @Override + public void onActivityResumed(@NonNull Activity activity) { + if (activity != activity()) setActivity(activity); + } + + @Override + public void onActivityPaused(@NonNull Activity activity) { + if (activity == activity()) setActivity(null); + } + + @Override + public void onActivityStopped(@NonNull Activity activity) { + if (activity == activity()) setActivity(null); + } + + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + if (activity == activity()) setActivity(null); + } + + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { + } + }); + } + + @Override + public PackageManager getPackageManager() { + return hook != null ? hook : getBaseContext().getPackageManager(); + } + + @Override + public String getPackageName() { + return hook != null ? hook.getPackageName() : getBaseContext().getPackageName(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fongmi/android/tv/Constant.java b/app/src/main/java/com/fongmi/android/tv/Constant.java new file mode 100644 index 00000000..d8c4e856 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/Constant.java @@ -0,0 +1,23 @@ +package com.fongmi.android.tv; + +import java.util.concurrent.TimeUnit; + +public class Constant { + + public static final long INTERVAL_SEEK = TimeUnit.SECONDS.toMillis(10); + public static final long INTERVAL_HIDE = TimeUnit.SECONDS.toMillis(5); + public static final long INTERVAL_TRAFFIC = TimeUnit.SECONDS.toMillis(1); + public static final long TIMEOUT_VOD = TimeUnit.SECONDS.toMillis(30); + public static final long TIMEOUT_LIVE = TimeUnit.SECONDS.toMillis(30); + public static final long TIMEOUT_EPG = TimeUnit.SECONDS.toMillis(5); + public static final long TIMEOUT_XML = TimeUnit.SECONDS.toMillis(15); + public static final long TIMEOUT_PLAY = TimeUnit.SECONDS.toMillis(15); + public static final long TIMEOUT_SYNC = TimeUnit.SECONDS.toMillis(2); + public static final long TIMEOUT_DANMAKU = TimeUnit.SECONDS.toMillis(30); + public static final long TIMEOUT_PARSE_DEF = TimeUnit.SECONDS.toMillis(15); + public static final long TIMEOUT_PARSE_WEB = TimeUnit.SECONDS.toMillis(15); + public static final long TIMEOUT_PARSE_LIVE = TimeUnit.SECONDS.toMillis(10); + public static final long HISTORY_TIME = TimeUnit.DAYS.toMillis(60); + public static final long OPED_LIMIT = TimeUnit.MINUTES.toMillis(5); + public static final int THREAD_POOL = 10; +} diff --git a/app/src/main/java/com/fongmi/android/tv/Setting.java b/app/src/main/java/com/fongmi/android/tv/Setting.java new file mode 100644 index 00000000..84b4eee7 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/Setting.java @@ -0,0 +1,303 @@ +package com.fongmi.android.tv; + + +import android.content.Intent; +import android.provider.Settings; + +import com.fongmi.android.tv.player.Players; +import com.github.catvod.utils.Prefers; + +public class Setting { + + public static String getDoh() { + return Prefers.getString("doh"); + } + + public static void putDoh(String doh) { + Prefers.put("doh", doh); + } + + public static String getProxy() { + return Prefers.getString("proxy"); + } + + public static void putProxy(String proxy) { + Prefers.put("proxy", proxy); + } + + public static String getKeyword() { + return Prefers.getString("keyword"); + } + + public static void putKeyword(String keyword) { + Prefers.put("keyword", keyword); + } + + public static String getHot() { + return Prefers.getString("hot"); + } + + public static void putHot(String hot) { + Prefers.put("hot", hot); + } + + public static String getUa() { + return Prefers.getString("ua"); + } + + public static void putUa(String ua) { + Prefers.put("ua", ua); + } + + public static int getWall() { + return Prefers.getInt("wall", 4); + } + + public static void putWall(int wall) { + Prefers.put("wall", wall); + } + + public static int getReset() { + return Prefers.getInt("reset", 0); + } + + public static void putReset(int reset) { + Prefers.put("reset", reset); + } + + public static int getDecode() { + return Prefers.getInt("decode", Players.HARD); + } + + public static void putDecode(int decode) { + Prefers.put("decode", decode); + } + + public static int getRender() { + return Prefers.getInt("render", 0); + } + + public static void putRender(int render) { + Prefers.put("render", render); + } + + public static int getQuality() { + return Prefers.getInt("quality", 2); + } + + public static void putQuality(int quality) { + Prefers.put("quality", quality); + } + + public static int getSize() { + return Prefers.getInt("size", 2); + } + + public static void putSize(int size) { + Prefers.put("size", size); + } + + public static int getViewType(int viewType) { + return Prefers.getInt("viewType", viewType); + } + + public static void putViewType(int viewType) { + Prefers.put("viewType", viewType); + } + + public static int getScale() { + return Prefers.getInt("scale"); + } + + public static void putScale(int scale) { + Prefers.put("scale", scale); + } + + public static int getLiveScale() { + return Prefers.getInt("scale_live", getScale()); + } + + public static void putLiveScale(int scale) { + Prefers.put("scale_live", scale); + } + + public static int getBuffer() { + return Math.min(Math.max(Prefers.getInt("buffer"), 1), 10); + } + + public static void putBuffer(int buffer) { + Prefers.put("buffer", buffer); + } + + public static int getBackground() { + return Prefers.getInt("background", 0); + } + + public static void putBackground(int background) { + Prefers.put("background", background); + } + + public static int getSiteMode() { + return Prefers.getInt("site_mode"); + } + + public static void putSiteMode(int mode) { + Prefers.put("site_mode", mode); + } + + public static int getSyncMode() { + return Prefers.getInt("sync_mode"); + } + + public static void putSyncMode(int mode) { + Prefers.put("sync_mode", mode); + } + + public static boolean isIncognito() { + return Prefers.getBoolean("incognito"); + } + + public static void putIncognito(boolean incognito) { + Prefers.put("incognito", incognito); + } + + public static boolean isBootLive() { + return Prefers.getBoolean("boot_live"); + } + + public static void putBootLive(boolean boot) { + Prefers.put("boot_live", boot); + } + + public static boolean isInvert() { + return Prefers.getBoolean("invert"); + } + + public static void putInvert(boolean invert) { + Prefers.put("invert", invert); + } + + public static boolean isAcross() { + return Prefers.getBoolean("across", true); + } + + public static void putAcross(boolean across) { + Prefers.put("across", across); + } + + public static boolean isChange() { + return Prefers.getBoolean("change", true); + } + + public static void putChange(boolean change) { + Prefers.put("change", change); + } + + public static boolean getUpdate() { + return Prefers.getBoolean("update", true); + } + + public static void putUpdate(boolean update) { + Prefers.put("update", update); + } + + public static boolean isCaption() { + return Prefers.getBoolean("caption"); + } + + public static void putCaption(boolean caption) { + Prefers.put("caption", caption); + } + + public static boolean isTunnel() { + return Prefers.getBoolean("tunnel"); + } + + public static void putTunnel(boolean tunnel) { + Prefers.put("tunnel", tunnel); + } + + public static boolean isAudioPrefer() { + return Prefers.getBoolean("audio_prefer"); + } + + public static void putAudioPrefer(boolean audioPrefer) { + Prefers.put("audio_prefer", audioPrefer); + } + + public static boolean isPreferAAC() { + return Prefers.getBoolean("prefer_aac"); + } + + public static void putPreferAAC(boolean preferAAC) { + Prefers.put("prefer_aac", preferAAC); + } + + public static boolean isDanmakuLoad() { + return Prefers.getBoolean("danmaku_load"); + } + + public static void putDanmakuLoad(boolean danmakuLoad) { + Prefers.put("danmaku_load", danmakuLoad); + } + + public static boolean isDanmakuShow() { + return Prefers.getBoolean("danmaku_show"); + } + + public static void putDanmakuShow(boolean danmakuShow) { + Prefers.put("danmaku_show", danmakuShow); + } + + public static boolean isZhuyin() { + return Prefers.getBoolean("zhuyin"); + } + + public static void putZhuyin(boolean zhuyin) { + Prefers.put("zhuyin", zhuyin); + } + + public static float getSpeed() { + return Math.min(Math.max(Prefers.getFloat("speed", 3), 2), 5); + } + + public static void putSpeed(float speed) { + Prefers.put("speed", speed); + } + + public static float getSubtitleTextSize() { + return Prefers.getFloat("subtitle_text_size"); + } + + public static void putSubtitleTextSize(float value) { + Prefers.put("subtitle_text_size", value); + } + + public static float getSubtitlePosition() { + return Prefers.getFloat("subtitle_position"); + } + + public static void putSubtitlePosition(float value) { + Prefers.put("subtitle_position", value); + } + + public static float getThumbnail() { + return 0.3f * getQuality() + 0.4f; + } + + public static boolean isBackgroundOff() { + return getBackground() == 0; + } + + public static boolean isBackgroundOn() { + return getBackground() == 1 || getBackground() == 2; + } + + public static boolean isBackgroundPiP() { + return getBackground() == 2; + } + + public static boolean hasCaption() { + return new Intent(Settings.ACTION_CAPTIONING_SETTINGS).resolveActivity(App.get().getPackageManager()) != null; + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/Decoder.java b/app/src/main/java/com/fongmi/android/tv/api/Decoder.java new file mode 100644 index 00000000..8dc71394 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/Decoder.java @@ -0,0 +1,86 @@ +package com.fongmi.android.tv.api; + +import android.util.Base64; + +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Util; + +import java.nio.charset.StandardCharsets; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import okhttp3.HttpUrl; +import okhttp3.Response; + +public class Decoder { + + private static final Pattern JS_URI = Pattern.compile("\"(\\.|\\.\\.)/(.?|.+?)\\.js\\?(.?|.+?)\""); + + public static String getJson(String url, String tag) throws Exception { + try (Response res = OkHttp.newCall(url, tag).execute()) { + HttpUrl httpUrl = res.request().url(); + int size = HttpUrl.parse(url).querySize(); + if (httpUrl.querySize() == size) url = httpUrl.toString(); + return verify(url, res.body().string()); + } + } + + private static String verify(String url, String data) throws Exception { + if (data.isEmpty()) throw new Exception(); + if (Json.isObj(data)) return fix(url, data); + if (data.contains("**")) data = base64(data); + if (data.startsWith("2423")) data = cbc(data); + return fix(url, data); + } + + private static String fix(String url, String data) { + Matcher matcher = JS_URI.matcher(data); + while (matcher.find()) data = replace(url, data, matcher.group()); + if (data.contains("../")) data = data.replace("../", UrlUtil.resolve(url, "../")); + if (data.contains("./")) data = data.replace("./", UrlUtil.resolve(url, "./")); + if (data.contains("__JS1__")) data = data.replace("__JS1__", "./"); + if (data.contains("__JS2__")) data = data.replace("__JS2__", "../"); + return data; + } + + private static String replace(String url, String data, String ext) { + String t = ext.replace("\"./", "\"" + UrlUtil.resolve(url, "./")); + t = t.replace("\"../", "\"" + UrlUtil.resolve(url, "../")); + t = t.replace("./", "__JS1__").replace("../", "__JS2__"); + return data.replace(ext, t); + } + + private static String cbc(String data) throws Exception { + String decode = new String(Util.hex2byte(data)).toLowerCase(); + String key = padEnd(decode.substring(decode.indexOf("$#") + 2, decode.indexOf("#$"))); + String iv = padEnd(decode.substring(decode.length() - 13)); + SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(), "AES"); + IvParameterSpec ivSpec = new IvParameterSpec(iv.getBytes()); + Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); + data = data.substring(data.indexOf("2324") + 4, data.length() - 26); + byte[] decryptData = cipher.doFinal(Util.hex2byte(data)); + return new String(decryptData, StandardCharsets.UTF_8); + } + + private static String base64(String data) { + String extract = extract(data); + if (extract.isEmpty()) return data; + return new String(Base64.decode(extract, Base64.DEFAULT)); + } + + private static String extract(String data) { + Matcher matcher = Pattern.compile("[A-Za-z0-9]{8}\\*\\*").matcher(data); + return matcher.find() ? data.substring(data.indexOf(matcher.group()) + 10) : ""; + } + + private static String padEnd(String key) { + return key + "0000000000000000".substring(key.length()); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/EpgParser.java b/app/src/main/java/com/fongmi/android/tv/api/EpgParser.java new file mode 100644 index 00000000..d65e1a1b --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/EpgParser.java @@ -0,0 +1,129 @@ +package com.fongmi.android.tv.api; + +import android.net.Uri; + +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Epg; +import com.fongmi.android.tv.bean.EpgData; +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Tv; +import com.fongmi.android.tv.utils.Download; +import com.fongmi.android.tv.utils.FileUtil; +import com.github.catvod.utils.Path; +import com.github.catvod.utils.Trans; + +import org.simpleframework.xml.core.Persister; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +public class EpgParser { + + private static final SimpleDateFormat formatTime = new SimpleDateFormat("HH:mm", Locale.getDefault()); + private static final SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + private static final SimpleDateFormat formatFull = new SimpleDateFormat("yyyyMMddHHmmss Z", Locale.getDefault()); + + public static boolean start(Live live, String url) throws Exception { + File file = Path.epg(Uri.parse(url).getLastPathSegment()); + if (shouldDownload(file)) Download.create(url, file).start(); + if (file.getName().endsWith(".gz")) readGzip(live, file); + else readXml(live, file); + return true; + } + + private static boolean shouldDownload(File file) { + return !file.exists() || !isToday(file.lastModified()) || System.currentTimeMillis() - file.lastModified() > TimeUnit.HOURS.toMillis(6); + } + + private static boolean isToday(long millis) { + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(millis); + return calendar.get(Calendar.DAY_OF_MONTH) == Calendar.getInstance().get(Calendar.DAY_OF_MONTH); + } + + private static void readGzip(Live live, File file) throws Exception { + File xml = Path.epg(file.getName().replace(".gz", "")); + if (!xml.exists()) FileUtil.gzipDecompress(file, xml); + readXml(live, xml); + } + + private static void readXml(Live live, File file) throws Exception { + Set exist = new HashSet<>(); + Map epgMap = new HashMap<>(); + Map srcMap = new HashMap<>(); + Map mapping = new HashMap<>(); + String today = formatDate.format(new Date()); + Tv tv = new Persister().read(Tv.class, Path.read(file), false); + for (Group group : live.getGroups()) for (Channel channel : group.getChannel()) exist.add(channel.getTvgId()); + for (Tv.Channel channel : tv.getChannel()) mapping.put(channel.getId(), channel); + for (Tv.Programme programme : tv.getProgramme()) { + String key = programme.getChannel(); + Tv.Channel channel = mapping.get(key); + if (!exist.contains(key)) key = find(exist, channel); + Date startDate = parse(formatFull, programme.getStart()); + Date endDate = parse(formatFull, programme.getStop()); + if (!exist.contains(key) || !isToday(startDate.getTime())) continue; + if (!epgMap.containsKey(key)) epgMap.put(key, Epg.create(key, today)); + epgMap.get(key).getList().add(getEpgData(startDate, endDate, programme)); + if (channel != null && channel.hasSrc()) srcMap.put(key, channel.getSrc()); + } + for (Group group : live.getGroups()) { + for (Channel channel : group.getChannel()) { + if (epgMap.containsKey(channel.getTvgId())) channel.setData(epgMap.get(channel.getTvgId())); + if (srcMap.containsKey(channel.getTvgId())) channel.setLogo(srcMap.get(channel.getTvgId())); + } + } + } + + private static String find(Set exist, Tv.Channel channel) { + if (channel == null) return ""; + for (Tv.DisplayName name : channel.getDisplayName()) if (exist.contains(name.getText())) return name.getText(); + return ""; + } + + public static Epg getEpg(String xml, String key) throws Exception { + Tv tv = new Persister().read(Tv.class, xml, false); + Epg epg = Epg.create(key, formatDate.format(parse(formatFull, tv.getDate()))); + for (Tv.Programme programme : tv.getProgramme()) epg.getList().add(getEpgData(programme)); + return epg; + } + + private static EpgData getEpgData(Tv.Programme programme) { + Date startDate = parse(formatFull, programme.getStart()); + Date endDate = parse(formatFull, programme.getStop()); + return getEpgData(startDate, endDate, programme); + } + + private static EpgData getEpgData(Date startDate, Date endDate, Tv.Programme programme) { + try { + EpgData epgData = new EpgData(); + epgData.setTitle(Trans.s2t(programme.getTitle())); + epgData.setStart(formatTime.format(startDate)); + epgData.setEnd(formatTime.format(endDate)); + epgData.setStartTime(startDate.getTime()); + epgData.setEndTime(endDate.getTime()); + return epgData; + } catch (Exception e) { + return new EpgData(); + } + } + + private static Date parse(SimpleDateFormat format, String source) { + try { + return format.parse(source); + } catch (Exception e) { + Date date = new Date(); + date.setTime(0); + return date; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java b/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java new file mode 100644 index 00000000..f3b80de8 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java @@ -0,0 +1,317 @@ +package com.fongmi.android.tv.api; + +import androidx.media3.common.MimeTypes; + +import com.fongmi.android.tv.bean.Catchup; +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.ClearKey; +import com.fongmi.android.tv.bean.Drm; +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Json; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LiveParser { + + private static final Pattern M3U = Pattern.compile("^(?!.*#genre#).*#EXT(?:M3U|INF).*", Pattern.MULTILINE); + private static final Pattern CATCHUP_REPLACE = Pattern.compile(".*catchup-replace=\"(.?|.+?)\".*"); + private static final Pattern CATCHUP_SOURCE = Pattern.compile(".*catchup-source=\"(.?|.+?)\".*"); + private static final Pattern CATCHUP = Pattern.compile(".*catchup=\"(.?|.+?)\".*"); + private static final Pattern TVG_CHNO = Pattern.compile(".*tvg-chno=\"(.?|.+?)\".*"); + private static final Pattern TVG_LOGO = Pattern.compile(".*tvg-logo=\"(.?|.+?)\".*"); + private static final Pattern TVG_NAME = Pattern.compile(".*tvg-name=\"(.?|.+?)\".*"); + private static final Pattern TVG_URL = Pattern.compile(".*tvg-url=\"(.?|.+?)\".*"); + private static final Pattern TVG_ID = Pattern.compile(".*tvg-id=\"(.?|.+?)\".*"); + private static final Pattern URL_TVG = Pattern.compile(".*url-tvg=\"(.?|.+?)\".*"); + private static final Pattern GROUP = Pattern.compile(".*group-title=\"(.?|.+?)\".*"); + private static final Pattern NAME = Pattern.compile(".*,(.+?)$"); + + private static String extract(String line, Pattern pattern) { + Matcher matcher = pattern.matcher(line.trim()); + if (matcher.matches()) return matcher.group(1).trim(); + return ""; + } + + private static String extract(String line, String... keywords) { + String[] splits = line.split(" "); + for (String split : splits) for (String keyword : keywords) if (split.contains(keyword)) return split.split("=")[1].replace("\"", ""); + return ""; + } + + public static void start(Live live) throws Exception { + if (!live.getGroups().isEmpty()) return; + String text = getText(live); + if (Json.isArray(text)) json(live, text); + else text(live, text); + } + + private static String getText(Live live) throws Exception { + if (!live.getApi().isEmpty()) return live.spider().liveContent(live.getUrl()); + return OkHttp.string(UrlUtil.convert(live.getUrl()), live.getHeaders()); + } + + public static void text(Live live, String text) { + int number = 0; + if (!live.getGroups().isEmpty()) return; + if (M3U.matcher(text).find()) m3u(live, text); else txt(live, text); + for (Group group : live.getGroups()) { + for (Channel channel : group.getChannel()) { + if (channel.getNumber().isEmpty()) channel.setNumber(++number); + channel.live(live); + } + } + } + + private static void json(Live live, String text) { + int number = 0; + live.getGroups().addAll(Group.arrayFrom(text)); + for (Group group : live.getGroups()) { + for (Channel channel : group.getChannel()) { + if (channel.getNumber().isEmpty()) channel.setNumber(++number); + channel.live(live); + } + } + } + + private static void m3u(Live live, String text) { + Setting setting = Setting.create(); + Catchup catchup = Catchup.create(); + Channel channel = Channel.create(""); + text = text.replace("\r\n", "\n").replace("\r", ""); + for (String line : text.split("\n")) { + if (Thread.interrupted()) break; + if (setting.find(line)) { + setting.check(line); + } else if (line.startsWith("#EXTM3U")) { + catchup.setType(extract(line, CATCHUP)); + catchup.setSource(extract(line, CATCHUP_SOURCE)); + catchup.setReplace(extract(line, CATCHUP_REPLACE)); + if (live.getEpg().isEmpty()) live.setEpg(extract(line, TVG_URL).replace("\"", "")); + if (live.getEpg().isEmpty()) live.setEpg(extract(line, URL_TVG).replace("\"", "")); + if (live.getEpg().isEmpty()) live.setEpg(extract(line, "tvg-url=", "url-tvg=")); + } else if (line.startsWith("#EXTINF:")) { + Group group = live.find(Group.create(extract(line, GROUP), live.isPass())); + channel = group.find(Channel.create(extract(line, NAME))); + channel.setTvgName(extract(line, TVG_NAME)); + channel.setNumber(extract(line, TVG_CHNO)); + channel.setLogo(extract(line, TVG_LOGO)); + channel.setTvgId(extract(line, TVG_ID)); + Catchup unknown = Catchup.create(); + unknown.setType(extract(line, CATCHUP)); + unknown.setSource(extract(line, CATCHUP_SOURCE)); + unknown.setReplace(extract(line, CATCHUP_REPLACE)); + channel.setCatchup(Catchup.decide(unknown, catchup)); + } else if (!line.startsWith("#") && line.contains("://")) { + String[] split = line.split("\\|"); + if (split.length > 1) setting.headers(Arrays.copyOfRange(split, 1, split.length)); + channel.getUrls().add(split[0]); + setting.copy(channel).clear(); + } + } + } + + private static void txt(Live live, String text) { + Setting setting = Setting.create(); + text = text.replace("\r\n", "\n").replace("\r", ""); + for (String line : text.split("\n")) { + if (Thread.interrupted()) break; + String[] split = line.split(",", 2); + if (setting.find(line)) setting.check(line); + if (line.contains("#genre#")) setting.clear(); + if (line.contains("#genre#")) live.getGroups().add(Group.create(split[0], live.isPass())); + if (split.length > 1 && live.getGroups().isEmpty()) live.getGroups().add(Group.create()); + if (split.length > 1 && split[1].contains("://")) { + Group group = live.getGroups().get(live.getGroups().size() - 1); + Channel channel = group.find(Channel.create(split[0])); + channel.addUrls(split[1].split("#")); + setting.copy(channel); + } + } + } + + private static class Setting { + + private String ua; + private String key; + private String type; + private String click; + private String format; + private String origin; + private String referer; + private Integer parse; + private Map header; + + public static Setting create() { + return new Setting(); + } + + public boolean find(String line) { + return line.startsWith("ua") || line.startsWith("parse") || line.startsWith("click") || line.startsWith("header") || line.startsWith("format") || line.startsWith("origin") || line.startsWith("referer") || line.startsWith("#EXTHTTP:") || line.startsWith("#EXTVLCOPT:") || line.startsWith("#KODIPROP:"); + } + + public void check(String line) { + if (line.startsWith("ua")) ua(line); + else if (line.startsWith("parse")) parse(line); + else if (line.startsWith("click")) click(line); + else if (line.startsWith("header")) header(line); + else if (line.startsWith("format")) format(line); + else if (line.startsWith("origin")) origin(line); + else if (line.startsWith("referer")) referer(line); + else if (line.startsWith("#EXTHTTP:")) header(line); + else if (line.startsWith("#EXTVLCOPT:http-origin")) origin(line); + else if (line.startsWith("#EXTVLCOPT:http-user-agent")) ua(line); + else if (line.startsWith("#EXTVLCOPT:http-referrer")) referer(line); + else if (line.startsWith("#KODIPROP:inputstream.adaptive.license_key")) key(line); + else if (line.startsWith("#KODIPROP:inputstream.adaptive.license_type")) type(line); + else if (line.startsWith("#KODIPROP:inputstream.adaptive.drm_legacy")) drmLegacy(line); + else if (line.startsWith("#KODIPROP:inputstream.adaptive.manifest_type")) format(line); + else if (line.startsWith("#KODIPROP:inputstream.adaptive.stream_headers")) headers(line); + else if (line.startsWith("#KODIPROP:inputstream.adaptive.common_headers")) headers(line); + } + + public Setting copy(Channel channel) { + if (ua != null) channel.setUa(ua); + if (parse != null) channel.setParse(parse); + if (click != null) channel.setClick(click); + if (format != null) channel.setFormat(format); + if (origin != null) channel.setOrigin(origin); + if (referer != null) channel.setReferer(referer); + if (header != null) channel.setHeader(Json.toObject(header)); + if (key != null && type != null) channel.setDrm(Drm.create(key, type)); + return this; + } + + private void ua(String line) { + try { + if (line.contains("user-agent=")) ua = line.split("(?i)user-agent=")[1].trim().replace("\"", ""); + if (line.contains("ua=")) ua = line.split("ua=")[1].trim().replace("\"", ""); + } catch (Exception e) { + ua = null; + } + } + + private void referer(String line) { + try { + referer = line.split("(?i)referer=")[1].trim().replace("\"", ""); + } catch (Exception e) { + referer = null; + } + } + + private void parse(String line) { + try { + parse = Integer.parseInt(line.split("parse=")[1].trim()); + } catch (Exception e) { + parse = null; + } + } + + private void click(String line) { + try { + click = line.split("click=")[1].trim(); + } catch (Exception e) { + click = null; + } + } + + private void format(String line) { + try { + if (line.startsWith("format=")) format = line.split("format=")[1].trim(); + if (line.contains("manifest_type=")) format = line.split("manifest_type=")[1].trim(); + if ("mpd".equals(format) || "dash".equals(format)) format = MimeTypes.APPLICATION_MPD; + if ("hls".equals(format)) format = MimeTypes.APPLICATION_M3U8; + } catch (Exception e) { + format = null; + } + } + + private void origin(String line) { + try { + origin = line.split("(?i)origin=")[1].trim(); + } catch (Exception e) { + origin = null; + } + } + + private void key(String line) { + try { + key = line.split("license_key=")[1].trim(); + if (!key.startsWith("http")) convert(); + } catch (Exception e) { + key = null; + } + } + + private void type(String line) { + try { + type = line.split("license_type=")[1].trim(); + } catch (Exception e) { + type = null; + } + } + + public void drmLegacy(String line) { + try { + line = line.split("drm_legacy=")[1].trim(); + type = line.split("\\|")[0].trim(); + key = line.split("\\|")[1].trim(); + if (!key.startsWith("http")) convert(); + } catch (Exception e) { + type = null; + key = null; + } + } + + private void header(String line) { + try { + if (line.contains("#EXTHTTP:")) header = Json.toMap(Json.parse(line.split("#EXTHTTP:")[1].trim())); + if (line.contains("header=")) header = Json.toMap(Json.parse(line.split("header=")[1].trim())); + } catch (Exception e) { + header = null; + } + } + + private void headers(String line) { + try { + headers(line.split("headers=")[1].trim().split("&")); + } catch (Exception ignored) { + } + } + + private void headers(String[] params) { + if (header == null) header = new HashMap<>(); + for (String param : params) { + if (!param.contains("=")) continue; + String[] a = param.split("=", 2); + header.put(a[0].trim(), a[1].trim().replace("\"", "")); + } + } + + private void convert() { + try { + ClearKey.objectFrom(key); + } catch (Exception e) { + key = ClearKey.get(key.replace("\"", "").replace("{", "").replace("}", "")).toString(); + } + } + + private void clear() { + ua = null; + key = null; + type = null; + parse = null; + click = null; + header = null; + format = null; + origin = null; + referer = null; + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java new file mode 100644 index 00000000..4c5a81a2 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java @@ -0,0 +1,320 @@ +package com.fongmi.android.tv.api.config; + +import android.net.Uri; +import android.text.TextUtils; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.Decoder; +import com.fongmi.android.tv.api.LiveParser; +import com.fongmi.android.tv.api.loader.BaseLoader; +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Depot; +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Rule; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.activity.LiveActivity; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Json; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LiveConfig { + + private List lives; + private List rules; + private List ads; + private Config config; + private boolean sync; + private Live home; + + private static class Loader { + static volatile LiveConfig INSTANCE = new LiveConfig(); + } + + public static LiveConfig get() { + return Loader.INSTANCE; + } + + public static String getUrl() { + return get().getConfig().getUrl(); + } + + public static String getDesc() { + return get().getConfig().getDesc(); + } + + public static String getResp() { + return get().getHome().getCore().getResp(); + } + + public static int getHomeIndex() { + return get().getLives().indexOf(get().getHome()); + } + + public static boolean isOnly() { + return get().getLives().size() == 1; + } + + public static boolean isEmpty() { + return get().getHome().isEmpty(); + } + + public static boolean hasUrl() { + return getUrl() != null && !getUrl().isEmpty(); + } + + public static void load(Config config, Callback callback) { + get().clear().config(config).load(callback); + } + + public LiveConfig init() { + this.home = null; + this.ads = new ArrayList<>(); + this.rules = new ArrayList<>(); + this.lives = new ArrayList<>(); + return config(Config.live()); + } + + public LiveConfig config(Config config) { + this.config = config; + if (config.getUrl() == null) return this; + this.sync = config.getUrl().equals(VodConfig.getUrl()); + return this; + } + + public LiveConfig clear() { + this.home = null; + this.ads.clear(); + this.rules.clear(); + this.lives.clear(); + return this; + } + + public void load() { + if (isEmpty()) load(new Callback()); + } + + public void load(Callback callback) { + App.execute(() -> loadConfig(callback)); + } + + private void loadConfig(Callback callback) { + try { + OkHttp.cancel("live"); + parseConfig(Decoder.getJson(UrlUtil.convert(config.getUrl()), "live"), callback); + } catch (Throwable e) { + if (TextUtils.isEmpty(config.getUrl())) App.post(() -> callback.error("")); + else App.post(() -> callback.error(Notify.getError(R.string.error_config_get, e))); + e.printStackTrace(); + } + } + + private void parseConfig(String text, Callback callback) { + if (!Json.isObj(text)) { + parseText(text, callback); + } else { + checkJson(Json.parse(text).getAsJsonObject(), callback); + } + } + + private void parseText(String text, Callback callback) { + Live live = new Live(parseName(config.getUrl()), config.getUrl()).sync(); + LiveParser.text(live, text); + lives.add(live); + setHome(live, true); + App.post(callback::success); + } + + private String parseName(String url) { + Uri uri = Uri.parse(url); + if ("file".equals(uri.getScheme())) return new File(url).getName(); + if (uri.getLastPathSegment() != null) return uri.getLastPathSegment(); + if (uri.getQuery() != null) return uri.getQuery(); + if (uri.getHost() != null) return uri.getHost(); + return url; + } + + private void checkJson(JsonObject object, Callback callback) { + if (object.has("msg")) { + App.post(() -> callback.error(object.get("msg").getAsString())); + } else if (object.has("urls")) { + parseDepot(object, callback); + } else { + parseConfig(object, callback); + } + } + + private void parseDepot(JsonObject object, Callback callback) { + List items = Depot.arrayFrom(object.getAsJsonArray("urls").toString()); + List configs = new ArrayList<>(); + for (Depot item : items) configs.add(Config.find(item, 1)); + Config.delete(config.getUrl()); + config = configs.get(0); + loadConfig(callback); + } + + private void parseConfig(JsonObject object, Callback callback) { + try { + initLive(object); + initOther(object); + } catch (Throwable e) { + e.printStackTrace(); + } finally { + if (callback != null) App.post(callback::success); + } + } + + private void initLive(JsonObject object) { + String spider = Json.safeString(object, "spider"); + BaseLoader.get().parseJar(spider, false); + for (JsonElement element : Json.safeListElement(object, "lives")) { + Live live = Live.objectFrom(element); + if (lives.contains(live)) continue; + live.setApi(UrlUtil.convert(live.getApi())); + live.setExt(UrlUtil.convert(live.getExt())); + live.setJar(parseJar(live, spider)); + lives.add(live.sync()); + } + for (Live live : lives) { + if (live.getName().equals(config.getHome())) { + setHome(live, true); + } + } + } + + private void initOther(JsonObject object) { + if (home == null) setHome(lives.isEmpty() ? new Live() : lives.get(0), true); + setRules(Rule.arrayFrom(object.getAsJsonArray("rules"))); + setHeaders(Json.safeListElement(object, "headers")); + setHosts(Json.safeListString(object, "hosts")); + setProxy(Json.safeListString(object, "proxy")); + setAds(Json.safeListString(object, "ads")); + } + + private String parseJar(Live live, String spider) { + return live.getJar().isEmpty() ? spider : live.getJar(); + } + + private void bootLive() { + Setting.putBootLive(false); + LiveActivity.start(App.get()); + } + + public void parse(JsonObject object) { + parseConfig(object, null); + } + + public void setKeep(Channel channel) { + if (home != null && !channel.getGroup().isHidden()) home.keep(channel).save(); + } + + public void setKeep(List items) { + List key = new ArrayList<>(); + for (Keep keep : Keep.getLive()) key.add(keep.getKey()); + for (Group group : items) { + if (group.isKeep()) continue; + for (Channel channel : group.getChannel()) { + if (key.contains(channel.getName())) { + items.get(0).add(channel); + } + } + } + } + + public int[] find(List items) { + String[] splits = getHome().getKeep().split(AppDatabase.SYMBOL); + if (splits.length < 2) return new int[]{1, 0}; + for (int i = 0; i < items.size(); i++) { + Group group = items.get(i); + if (group.getName().equals(splits[0])) { + int j = group.find(splits[1]); + if (j != -1 && splits.length > 2) group.getChannel().get(j).setLine(splits[2]); + if (j != -1) return new int[]{i, j}; + } + } + return new int[]{1, 0}; + } + + public int[] find(String number, List items) { + for (int i = 0; i < items.size(); i++) { + int j = items.get(i).find(Integer.parseInt(number)); + if (j != -1) return new int[]{i, j}; + } + return new int[]{-1, -1}; + } + + public boolean needSync(String url) { + return sync || TextUtils.isEmpty(config.getUrl()) || url.equals(config.getUrl()); + } + + public List getRules() { + return rules == null ? Collections.emptyList() : rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public void setHeaders(List items) { + OkHttp.responseInterceptor().setHeaders(items); + } + + public void setHosts(List hosts) { + OkHttp.dns().addAll(hosts); + } + + public void setProxy(List hosts) { + OkHttp.selector().addAll(hosts); + } + + public List getAds() { + return ads == null ? Collections.emptyList() : ads; + } + + private void setAds(List ads) { + this.ads = ads; + } + + public List getLives() { + return lives == null ? lives = new ArrayList<>() : lives; + } + + public Config getConfig() { + return config == null ? Config.live() : config; + } + + public Live getHome() { + return home == null ? new Live() : home; + } + + public Live getLive(String key) { + int index = getLives().indexOf(Live.get(key)); + return index == -1 ? new Live() : getLives().get(index); + } + + public void setHome(Live home) { + setHome(home, false); + } + + private void setHome(Live home, boolean check) { + this.home = home; + this.home.setActivated(true); + config.home(home.getName()).update(); + for (Live item : getLives()) item.setActivated(home); + if (App.activity() != null && App.activity() instanceof LiveActivity) return; + if (check) if (home.isBoot() || Setting.isBootLive()) App.post(this::bootLive); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java new file mode 100644 index 00000000..bc064f26 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/config/VodConfig.java @@ -0,0 +1,332 @@ +package com.fongmi.android.tv.api.config; + +import android.text.TextUtils; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.Decoder; +import com.fongmi.android.tv.api.loader.BaseLoader; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Depot; +import com.fongmi.android.tv.bean.Parse; +import com.fongmi.android.tv.bean.Rule; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.bean.Doh; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Json; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class VodConfig { + + private List doh; + private List rules; + private List sites; + private List parses; + private List flags; + private List ads; + private boolean loadLive; + private Config config; + private Parse parse; + private String wall; + private Site home; + + private static class Loader { + static volatile VodConfig INSTANCE = new VodConfig(); + } + + public static VodConfig get() { + return Loader.INSTANCE; + } + + public static int getCid() { + return get().getConfig().getId(); + } + + public static String getUrl() { + return get().getConfig().getUrl(); + } + + public static String getDesc() { + return get().getConfig().getDesc(); + } + + public static int getHomeIndex() { + return get().getSites().indexOf(get().getHome()); + } + + public static boolean hasParse() { + return !get().getParses().isEmpty(); + } + + public static void load(Config config, Callback callback) { + get().clear().config(config).load(callback); + } + + public VodConfig init() { + this.wall = null; + this.home = null; + this.parse = null; + this.config = Config.vod(); + this.ads = new ArrayList<>(); + this.doh = new ArrayList<>(); + this.rules = new ArrayList<>(); + this.sites = new ArrayList<>(); + this.flags = new ArrayList<>(); + this.parses = new ArrayList<>(); + this.loadLive = false; + return this; + } + + public VodConfig config(Config config) { + this.config = config; + return this; + } + + public VodConfig clear() { + this.wall = null; + this.home = null; + this.parse = null; + this.ads.clear(); + this.doh.clear(); + this.rules.clear(); + this.sites.clear(); + this.flags.clear(); + this.parses.clear(); + this.loadLive = true; + BaseLoader.get().clear(); + return this; + } + + public void load(Callback callback) { + App.execute(() -> loadConfig(callback)); + } + + private void loadConfig(Callback callback) { + try { + OkHttp.cancel("vod"); + checkJson(Json.parse(Decoder.getJson(UrlUtil.convert(config.getUrl()), "vod")).getAsJsonObject(), callback); + } catch (Throwable e) { + if (TextUtils.isEmpty(config.getUrl())) App.post(() -> callback.error("")); + else loadCache(callback, e); + e.printStackTrace(); + } + } + + private void loadCache(Callback callback, Throwable e) { + if (!TextUtils.isEmpty(config.getJson())) checkJson(Json.parse(config.getJson()).getAsJsonObject(), callback); + else App.post(() -> callback.error(Notify.getError(R.string.error_config_get, e))); + } + + private void checkJson(JsonObject object, Callback callback) { + if (object.has("msg")) { + App.post(() -> callback.error(object.get("msg").getAsString())); + } else if (object.has("urls")) { + parseDepot(object, callback); + } else { + parseConfig(object, callback); + } + } + + private void parseDepot(JsonObject object, Callback callback) { + List items = Depot.arrayFrom(object.getAsJsonArray("urls").toString()); + List configs = new ArrayList<>(); + for (Depot item : items) configs.add(Config.find(item, 0)); + Config.delete(config.getUrl()); + config = configs.get(0); + loadConfig(callback); + } + + private void parseConfig(JsonObject object, Callback callback) { + try { + initSite(object); + initParse(object); + initOther(object); + if (loadLive && object.has("lives")) initLive(object); + String notice = Json.safeString(object, "notice"); + config.logo(Json.safeString(object, "logo")); + App.post(() -> callback.success(notice)); + config.json(object.toString()).update(); + App.post(callback::success); + } catch (Throwable e) { + e.printStackTrace(); + App.post(() -> callback.error(Notify.getError(R.string.error_config_parse, e))); + } + } + + private void initSite(JsonObject object) { + if (object.has("video")) { + initSite(object.getAsJsonObject("video")); + return; + } + String spider = Json.safeString(object, "spider"); + BaseLoader.get().parseJar(spider, true); + for (JsonElement element : Json.safeListElement(object, "sites")) { + Site site = Site.objectFrom(element); + if (sites.contains(site)) continue; + site.setApi(UrlUtil.convert(site.getApi())); + site.setExt(UrlUtil.convert(site.getExt())); + site.setJar(parseJar(site, spider)); + sites.add(site.trans().sync()); + } + for (Site site : sites) { + if (site.getKey().equals(config.getHome())) { + setHome(site); + } + } + } + + private void initLive(JsonObject object) { + Config temp = Config.find(config, 1).save(); + boolean sync = LiveConfig.get().needSync(config.getUrl()); + if (sync) LiveConfig.get().clear().config(temp).parse(object); + } + + private void initParse(JsonObject object) { + for (JsonElement element : Json.safeListElement(object, "parses")) { + Parse parse = Parse.objectFrom(element); + if (parse.getName().equals(config.getParse()) && parse.getType() > 1) setParse(parse); + if (!parses.contains(parse)) parses.add(parse); + } + } + + private void initOther(JsonObject object) { + if (!parses.isEmpty()) parses.add(0, Parse.god()); + if (home == null) setHome(sites.isEmpty() ? new Site() : sites.get(0)); + if (parse == null) setParse(parses.isEmpty() ? new Parse() : parses.get(0)); + setRules(Rule.arrayFrom(object.getAsJsonArray("rules"))); + setDoh(Doh.arrayFrom(object.getAsJsonArray("doh"))); + setHeaders(Json.safeListElement(object, "headers")); + setFlags(Json.safeListString(object, "flags")); + setHosts(Json.safeListString(object, "hosts")); + setProxy(Json.safeListString(object, "proxy")); + setWall(Json.safeString(object, "wallpaper")); + setAds(Json.safeListString(object, "ads")); + } + + private String parseJar(Site site, String spider) { + return site.getJar().isEmpty() ? spider : site.getJar(); + } + + public List getDoh() { + List items = Doh.get(App.get()); + if (doh == null) return items; + items.removeAll(doh); + items.addAll(doh); + return items; + } + + public void setDoh(List doh) { + this.doh = doh; + } + + public List getRules() { + return rules == null ? Collections.emptyList() : rules; + } + + public void setRules(List rules) { + this.rules = rules; + } + + public List getSites() { + return sites == null ? Collections.emptyList() : sites; + } + + public List getParses() { + return parses == null ? Collections.emptyList() : parses; + } + + public List getParses(int type) { + List items = new ArrayList<>(); + for (Parse item : getParses()) if (item.getType() == type) items.add(item); + return items; + } + + public List getParses(int type, String flag) { + List items = new ArrayList<>(); + for (Parse item : getParses(type)) if (item.getExt().getFlag().contains(flag)) items.add(item); + if (items.isEmpty()) items.addAll(getParses(type)); + return items; + } + + public void setHeaders(List items) { + OkHttp.responseInterceptor().setHeaders(items); + } + + public List getFlags() { + return flags == null ? Collections.emptyList() : flags; + } + + private void setFlags(List flags) { + this.flags.addAll(flags); + } + + public void setHosts(List hosts) { + OkHttp.dns().addAll(hosts); + } + + public void setProxy(List hosts) { + OkHttp.selector().addAll(hosts); + } + + public List getAds() { + return ads == null ? Collections.emptyList() : ads; + } + + private void setAds(List ads) { + this.ads = ads; + } + + public Config getConfig() { + return config == null ? Config.vod() : config; + } + + public Parse getParse() { + return parse == null ? new Parse() : parse; + } + + public Site getHome() { + return home == null ? new Site() : home; + } + + public String getWall() { + return TextUtils.isEmpty(wall) ? "" : wall; + } + + public Parse getParse(String name) { + int index = getParses().indexOf(Parse.get(name)); + return index == -1 ? null : getParses().get(index); + } + + public Site getSite(String key) { + int index = getSites().indexOf(Site.get(key)); + return index == -1 ? new Site() : getSites().get(index); + } + + public void setParse(Parse parse) { + this.parse = parse; + this.parse.setActivated(true); + config.parse(parse.getName()).save(); + for (Parse item : getParses()) item.setActivated(parse); + } + + public void setHome(Site home) { + this.home = home; + this.home.setActivated(true); + config.home(home.getKey()).save(); + for (Site item : getSites()) item.setActivated(home); + } + + private void setWall(String wall) { + this.wall = wall; + boolean load = !TextUtils.isEmpty(wall) && WallConfig.get().needSync(wall); + if (load) WallConfig.get().config(Config.find(wall, config.getName(), 2).update()); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java new file mode 100644 index 00000000..abfb397a --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java @@ -0,0 +1,103 @@ +package com.fongmi.android.tv.api.config; + +import android.graphics.Bitmap; +import android.text.TextUtils; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Path; + +import java.io.File; +import java.io.FileOutputStream; + +public class WallConfig { + + private Config config; + private boolean sync; + + private static class Loader { + static volatile WallConfig INSTANCE = new WallConfig(); + } + + public static WallConfig get() { + return Loader.INSTANCE; + } + + public static String getUrl() { + return get().getConfig().getUrl(); + } + + public static String getDesc() { + return get().getConfig().getDesc(); + } + + public static void load(Config config, Callback callback) { + get().clear().config(config).load(callback); + } + + public WallConfig init() { + return config(Config.wall()); + } + + public WallConfig config(Config config) { + this.config = config; + if (config.getUrl() == null) return this; + this.sync = config.getUrl().equals(VodConfig.get().getWall()); + return this; + } + + public WallConfig clear() { + this.config = null; + return this; + } + + public Config getConfig() { + return config == null ? Config.wall() : config; + } + + public void load(Callback callback) { + App.execute(() -> loadConfig(callback)); + } + + private void loadConfig(Callback callback) { + try { + File file = write(FileUtil.getWall(0)); + if (file.exists() && file.length() > 0) refresh(0); + else config(Config.find(VodConfig.get().getWall(), 2)); + App.post(callback::success); + config.update(); + } catch (Throwable e) { + App.post(() -> callback.error(Notify.getError(R.string.error_config_parse, e))); + config(Config.find(VodConfig.get().getWall(), 2)); + e.printStackTrace(); + } + } + + private File write(File file) throws Exception { + Path.write(file, OkHttp.bytes(UrlUtil.convert(getUrl()))); + Bitmap bitmap = Glide.with(App.get()).asBitmap().load(file).centerCrop().override(ResUtil.getScreenWidth(), ResUtil.getScreenHeight()).skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.NONE).submit().get(); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, new FileOutputStream(file)); + bitmap.recycle(); + return file; + } + + public boolean needSync(String url) { + return sync || TextUtils.isEmpty(config.getUrl()) || url.equals(config.getUrl()); + } + + public static void refresh(int index) { + Setting.putWall(index); + RefreshEvent.wall(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/loader/BaseLoader.java b/app/src/main/java/com/fongmi/android/tv/api/loader/BaseLoader.java new file mode 100644 index 00000000..c4c8eb15 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/loader/BaseLoader.java @@ -0,0 +1,102 @@ +package com.fongmi.android.tv.api.loader; + +import android.text.TextUtils; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Site; +import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.SpiderNull; +import com.github.catvod.utils.Util; + +import org.json.JSONObject; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +import dalvik.system.DexClassLoader; + +public class BaseLoader { + + private final JarLoader jarLoader; + private final PyLoader pyLoader; + private final JsLoader jsLoader; + + private static class Loader { + static volatile BaseLoader INSTANCE = new BaseLoader(); + } + + public static BaseLoader get() { + return Loader.INSTANCE; + } + + private BaseLoader() { + this.jarLoader = new JarLoader(); + this.pyLoader = new PyLoader(); + this.jsLoader = new JsLoader(); + } + + public void clear() { + this.jarLoader.clear(); + this.pyLoader.clear(); + this.jsLoader.clear(); + } + + public Spider getSpider(String key, String api, String ext, String jar) { + boolean js = api.contains(".js"); + boolean py = api.contains(".py"); + boolean csp = api.startsWith("csp_"); + if (py) return pyLoader.getSpider(key, api, ext); + else if (js) return jsLoader.getSpider(key, api, ext, jar); + else if (csp) return jarLoader.getSpider(key, api, ext, jar); + else return new SpiderNull(); + } + + public Spider getSpider(Map params) { + if (!params.containsKey("siteKey")) return new SpiderNull(); + Live live = LiveConfig.get().getLive(params.get("siteKey")); + Site site = VodConfig.get().getSite(params.get("siteKey")); + if (!site.isEmpty()) return site.spider(); + if (!live.isEmpty()) return live.spider(); + return new SpiderNull(); + } + + public void setRecent(String key, String api, String jar) { + boolean js = api.contains(".js"); + boolean py = api.contains(".py"); + boolean csp = api.startsWith("csp_"); + if (js) jsLoader.setRecent(key); + else if (py) pyLoader.setRecent(key); + else if (csp) jarLoader.setRecent(Util.md5(jar)); + } + + public Object[] proxyLocal(Map params) { + if ("js".equals(params.get("do"))) { + return jsLoader.proxyInvoke(params); + } else if ("py".equals(params.get("do"))) { + return pyLoader.proxyInvoke(params); + } else { + return jarLoader.proxyInvoke(params); + } + } + + public void parseJar(String jar, boolean recent) { + if (TextUtils.isEmpty(jar)) return; + jarLoader.parseJar(Util.md5(jar), jar); + if (recent) jarLoader.setRecent(Util.md5(jar)); + } + + public DexClassLoader dex(String jar) { + return jarLoader.dex(jar); + } + + public JSONObject jsonExt(String key, LinkedHashMap jxs, String url) throws Throwable { + return jarLoader.jsonExt(key, jxs, url); + } + + public JSONObject jsonExtMix(String flag, String key, String name, LinkedHashMap> jxs, String url) throws Throwable { + return jarLoader.jsonExtMix(flag, key, name, jxs, url); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java b/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java new file mode 100644 index 00000000..490ef702 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/loader/JarLoader.java @@ -0,0 +1,165 @@ +package com.fongmi.android.tv.api.loader; + +import android.content.Context; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.SpiderNull; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Path; +import com.github.catvod.utils.Util; + +import org.json.JSONObject; + +import java.io.File; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import dalvik.system.DexClassLoader; + +public class JarLoader { + + private final ConcurrentHashMap loaders; + private final ConcurrentHashMap methods; + private final ConcurrentHashMap spiders; + private String recent; + + public JarLoader() { + loaders = new ConcurrentHashMap<>(); + methods = new ConcurrentHashMap<>(); + spiders = new ConcurrentHashMap<>(); + } + + public void clear() { + for (Spider spider : spiders.values()) App.execute(spider::destroy); + loaders.clear(); + methods.clear(); + spiders.clear(); + } + + public void setRecent(String recent) { + this.recent = recent; + } + + private void load(String key, File file) { + if (!file.setReadOnly()) return; + loaders.put(key, dex(file)); + invokeInit(key); + putProxy(key); + } + + private DexClassLoader dex(File file) { + return new DexClassLoader(file.getAbsolutePath(), Path.jar().getAbsolutePath(), null, App.get().getClassLoader()); + } + + private void invokeInit(String key) { + try { + Class clz = loaders.get(key).loadClass("com.github.catvod.spider.Init"); + Method method = clz.getMethod("init", Context.class); + method.invoke(clz, App.get()); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void putProxy(String key) { + try { + Class clz = loaders.get(key).loadClass("com.github.catvod.spider.Proxy"); + Method method = clz.getMethod("proxy", Map.class); + methods.put(key, method); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private File download(String url) { + try { + return Path.write(Path.jar(url), OkHttp.bytes(url)); + } catch (Exception e) { + return Path.jar(url); + } + } + + public synchronized void parseJar(String key, String jar) { + if (loaders.containsKey(key)) return; + String[] texts = jar.split(";md5;"); + String md5 = texts.length > 1 ? texts[1].trim() : ""; + if (md5.startsWith("http")) md5 = OkHttp.string(md5).trim(); + jar = texts[0]; + if (!md5.isEmpty() && Util.equals(jar, md5)) { + load(key, Path.jar(jar)); + } else if (jar.startsWith("http")) { + load(key, download(jar)); + } else if (jar.startsWith("file")) { + load(key, Path.local(jar)); + } else if (jar.startsWith("assets")) { + parseJar(key, UrlUtil.convert(jar)); + } + } + + public DexClassLoader dex(String jar) { + try { + String jaKey = Util.md5(jar); + if (!loaders.containsKey(jaKey)) parseJar(jaKey, jar); + return loaders.get(jaKey); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } + + public Spider getSpider(String key, String api, String ext, String jar) { + try { + String jaKey = Util.md5(jar); + String spKey = jaKey + key; + if (spiders.containsKey(spKey)) return spiders.get(spKey); + if (!loaders.containsKey(jaKey)) parseJar(jaKey, jar); + Spider spider = (Spider) loaders.get(jaKey).loadClass("com.github.catvod.spider." + api.split("csp_")[1]).newInstance(); + spider.init(App.get(), ext); + spiders.put(spKey, spider); + return spider; + } catch (Throwable e) { + e.printStackTrace(); + return new SpiderNull(); + } + } + + public JSONObject jsonExt(String key, LinkedHashMap jxs, String url) throws Throwable { + Class clz = loaders.get(recent).loadClass("com.github.catvod.parser.Json" + key); + Method method = clz.getMethod("parse", LinkedHashMap.class, String.class); + return (JSONObject) method.invoke(null, jxs, url); + } + + public JSONObject jsonExtMix(String flag, String key, String name, LinkedHashMap> jxs, String url) throws Throwable { + Class clz = loaders.get(recent).loadClass("com.github.catvod.parser.Mix" + key); + Method method = clz.getMethod("parse", LinkedHashMap.class, String.class, String.class, String.class); + return (JSONObject) method.invoke(null, jxs, name, flag, url); + } + + public Object[] proxyInvoke(Map params) { + Object[] result = proxyInvoke(methods.get(recent), params); + return result != null ? result : tryOthers(params); + } + + private Object[] tryOthers(Map params) { + for (Map.Entry entry : methods.entrySet()) { + if (entry.getKey().equals(recent)) continue; + Object[] result = proxyInvoke(entry.getValue(), params); + if (result != null) return result; + } + return null; + } + + private Object[] proxyInvoke(Method method, Map params) { + try { + return (Object[]) method.invoke(null, params); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/loader/JsLoader.java b/app/src/main/java/com/fongmi/android/tv/api/loader/JsLoader.java new file mode 100644 index 00000000..de503a8c --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/loader/JsLoader.java @@ -0,0 +1,50 @@ +package com.fongmi.android.tv.api.loader; + +import com.fongmi.android.tv.App; +import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.SpiderNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class JsLoader { + + private final ConcurrentHashMap spiders; + private String recent; + + public JsLoader() { + spiders = new ConcurrentHashMap<>(); + } + + public void clear() { + for (Spider spider : spiders.values()) App.execute(spider::destroy); + spiders.clear(); + } + + public void setRecent(String recent) { + this.recent = recent; + } + + public Spider getSpider(String key, String api, String ext, String jar) { + try { + if (spiders.containsKey(key)) return spiders.get(key); + Spider spider = new com.fongmi.quickjs.crawler.Spider(key, api, BaseLoader.get().dex(jar)); + spider.init(App.get(), ext); + spiders.put(key, spider); + return spider; + } catch (Throwable e) { + e.printStackTrace(); + return new SpiderNull(); + } + } + + public Object[] proxyInvoke(Map params) { + try { + if (!params.containsKey("siteKey")) return spiders.get(recent).proxyLocal(params); + return BaseLoader.get().getSpider(params).proxyLocal(params); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java b/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java new file mode 100644 index 00000000..613c62bb --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/loader/PyLoader.java @@ -0,0 +1,50 @@ +package com.fongmi.android.tv.api.loader; + +import com.fongmi.android.tv.App; +import com.github.catvod.crawler.Spider; +import com.github.catvod.crawler.SpiderNull; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class PyLoader { + + private final ConcurrentHashMap spiders; + private String recent; + + public PyLoader() { + spiders = new ConcurrentHashMap<>(); + } + + public void clear() { + for (Spider spider : spiders.values()) App.execute(spider::destroy); + spiders.clear(); + } + + public void setRecent(String recent) { + this.recent = recent; + } + + public Spider getSpider(String key, String api, String ext) { + try { + if (spiders.containsKey(key)) return spiders.get(key); + // Since we removed chaquo, return SpiderNull for now + Spider spider = new SpiderNull(); + spiders.put(key, spider); + return spider; + } catch (Throwable e) { + e.printStackTrace(); + return new SpiderNull(); + } + } + + public Object[] proxyInvoke(Map params) { + try { + if (!params.containsKey("siteKey")) return spiders.get(recent).proxyLocal(params); + return BaseLoader.get().getSpider(params).proxyLocal(params); + } catch (Throwable e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Backup.java b/app/src/main/java/com/fongmi/android/tv/bean/Backup.java new file mode 100644 index 00000000..702fa183 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Backup.java @@ -0,0 +1,117 @@ +package com.fongmi.android.tv.bean; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.db.AppDatabase; +import com.github.catvod.utils.Prefers; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.ToNumberPolicy; +import com.google.gson.annotations.SerializedName; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Backup { + + @SerializedName("site") + private List site; + @SerializedName("live") + private List live; + @SerializedName("keep") + private List keep; + @SerializedName("config") + private List config; + @SerializedName("history") + private List history; + @SerializedName("prefers") + private Map prefers; + + public static Backup create() { + Backup backup = new Backup(); + backup.setPrefers(Prefers.getPrefers().getAll()); + backup.setSite(AppDatabase.get().getSiteDao().findAll()); + backup.setLive(AppDatabase.get().getLiveDao().findAll()); + backup.setKeep(AppDatabase.get().getKeepDao().findAll()); + backup.setConfig(AppDatabase.get().getConfigDao().findAll()); + backup.setHistory(AppDatabase.get().getHistoryDao().findAll()); + return backup; + } + + public void restore() { + AppDatabase.get().clearAllTables(); + AppDatabase.get().getSiteDao().insertOrUpdate(getSite()); + AppDatabase.get().getLiveDao().insertOrUpdate(getLive()); + AppDatabase.get().getKeepDao().insertOrUpdate(getKeep()); + AppDatabase.get().getConfigDao().insertOrUpdate(getConfig()); + AppDatabase.get().getHistoryDao().insertOrUpdate(getHistory()); + for (Map.Entry entry : getPrefers().entrySet()) Prefers.put(entry.getKey(), entry.getValue()); + } + + public static Backup objectFrom(String json) { + try { + Gson gson = new GsonBuilder().setObjectToNumberStrategy(ToNumberPolicy.LAZILY_PARSED_NUMBER).create(); + Backup backup = gson.fromJson(json, Backup.class); + return backup == null ? new Backup() : backup; + } catch (Exception e) { + return new Backup(); + } + } + + public List getSite() { + return site == null ? Collections.emptyList() : site; + } + + public void setSite(List site) { + this.site = site; + } + + public List getLive() { + return live == null ? Collections.emptyList() : live; + } + + public void setLive(List live) { + this.live = live; + } + + public List getKeep() { + return keep == null ? Collections.emptyList() : keep; + } + + public void setKeep(List keep) { + this.keep = keep; + } + + public List getConfig() { + return config == null ? Collections.emptyList() : config; + } + + public void setConfig(List config) { + this.config = config; + } + + public List getHistory() { + return history == null ? Collections.emptyList() : history; + } + + public void setHistory(List history) { + this.history = history; + } + + public Map getPrefers() { + return prefers == null ? new HashMap<>() : prefers; + } + + public void setPrefers(Map prefers) { + this.prefers = prefers; + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java b/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java new file mode 100644 index 00000000..d352604a --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java @@ -0,0 +1,126 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import com.google.gson.annotations.SerializedName; + +import java.net.URI; +import java.text.SimpleDateFormat; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Catchup { + + @SerializedName("type") + private String type; + @SerializedName("days") + private String days; + @SerializedName("regex") + private String regex; + @SerializedName("source") + private String source; + @SerializedName("replace") + private String replace; + + public static Catchup PLTV() { + Catchup item = new Catchup(); + item.setDays("7"); + item.setType("append"); + item.setRegex("/PLTV/"); + item.setReplace("/PLTV/,/TVOD/"); + item.setSource("?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}"); + return item; + } + + public static Catchup create() { + return new Catchup(); + } + + public static Catchup decide(Catchup major, Catchup minor) { + if (!major.isEmpty()) return major; + if (!minor.isEmpty()) return minor; + return null; + } + + public String getType() { + return TextUtils.isEmpty(type) ? "" : type; + } + + public void setType(String type) { + this.type = type; + } + + public String getDays() { + return TextUtils.isEmpty(days) ? "" : days; + } + + public void setDays(String days) { + this.days = days; + } + + public String getRegex() { + return TextUtils.isEmpty(regex) ? "" : regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } + + public String getReplace() { + return TextUtils.isEmpty(replace) ? "" : replace; + } + + public void setReplace(String replace) { + this.replace = replace; + } + + public String getSource() { + return TextUtils.isEmpty(source) ? "" : source; + } + + public void setSource(String source) { + this.source = source; + } + + public boolean match(String url) { + return url.contains(getRegex()) || Pattern.compile(getRegex()).matcher(url).find(); + } + + public boolean isEmpty() { + return getSource().isEmpty(); + } + + private boolean isAppend() { + return getType().equals("append"); + } + + private boolean isDefault() { + return getType().equals("default"); + } + + private String append(String url, String result) { + String[] splits = getReplace().split(",", 2); + if (splits.length == 2) url = url.replaceAll(splits[0], splits[1]); + if (!TextUtils.isEmpty(URI.create(url).getQuery())) result = result.replace("?", "&"); + return url + result; + } + + public String format(String url, EpgData data) { + String result = getSource(); + Matcher matcher = Pattern.compile("(\\$?\\{[^}]*\\})").matcher(result); + while (matcher.find()) result = result.replace(matcher.group(1), format(matcher.group(1), data.getStartTime(), data.getEndTime())); + return isDefault() ? result : append(url, result); + } + + private String format(String group, long start, long end) { + Matcher matcher = Pattern.compile("\\{([^}]+)\\}").matcher(group); + if (!matcher.find()) return ""; + String tag = matcher.group(1); + if (tag.startsWith("(b")) return new SimpleDateFormat(tag.split("\\)")[1], Locale.getDefault()).format(start); + if (tag.startsWith("(e")) return new SimpleDateFormat(tag.split("\\)")[1], Locale.getDefault()).format(end); + if (tag.startsWith("utcend:")) return String.valueOf(end / 1000); + if (tag.startsWith("utc:")) return String.valueOf(start / 1000); + return ""; + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Cate.java b/app/src/main/java/com/fongmi/android/tv/bean/Cate.java new file mode 100644 index 00000000..6b8e22bc --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Cate.java @@ -0,0 +1,67 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.google.gson.annotations.SerializedName; + +public class Cate implements Parcelable { + + @SerializedName("land") + private int land; + + @SerializedName("circle") + private int circle; + + @SerializedName("ratio") + private float ratio; + + public Cate() { + } + + public int getLand() { + return land; + } + + public int getCircle() { + return circle; + } + + public float getRatio() { + return ratio; + } + + public Style getStyle() { + return Style.get(getLand(), getCircle(), getRatio()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(this.land); + dest.writeInt(this.circle); + dest.writeFloat(this.ratio); + } + + protected Cate(Parcel in) { + this.land = in.readInt(); + this.circle = in.readInt(); + this.ratio = in.readFloat(); + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Cate createFromParcel(Parcel source) { + return new Cate(source); + } + + @Override + public Cate[] newArray(int size) { + return new Cate[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Channel.java b/app/src/main/java/com/fongmi/android/tv/bean/Channel.java new file mode 100644 index 00000000..261a985a --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Channel.java @@ -0,0 +1,399 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; +import android.view.View; +import android.widget.ImageView; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.utils.Json; +import com.google.common.net.HttpHeaders; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; + +public class Channel { + + @SerializedName("urls") + private List urls; + @SerializedName("number") + private String number; + @SerializedName("logo") + private String logo; + @SerializedName("epg") + private String epg; + @SerializedName("name") + private String name; + @SerializedName("ua") + private String ua; + @SerializedName("click") + private String click; + @SerializedName("format") + private String format; + @SerializedName("origin") + private String origin; + @SerializedName("referer") + private String referer; + @SerializedName("tvgId") + private String tvgId; + @SerializedName("tvgName") + private String tvgName; + @SerializedName("catchup") + private Catchup catchup; + @SerializedName("header") + private JsonElement header; + @SerializedName("parse") + private Integer parse; + @SerializedName("drm") + private Drm drm; + + private boolean selected; + private Group group; + private String url; + private String msg; + private Epg data; + private int line; + + public static Channel objectFrom(JsonElement element) { + return App.gson().fromJson(element, Channel.class); + } + + public static Channel create(int number) { + return new Channel().setNumber(number); + } + + public static Channel create(String name) { + return new Channel(name); + } + + public static Channel create(Channel channel) { + return new Channel().copy(channel); + } + + public static Channel error(String msg) { + Channel result = new Channel(); + result.setMsg(msg); + return result; + } + + public Channel() { + } + + public Channel(String name) { + this.name = name; + } + + public List getUrls() { + return urls = urls == null ? new ArrayList<>() : urls; + } + + public void setUrls(List urls) { + this.urls = urls; + } + + public String getNumber() { + return TextUtils.isEmpty(number) ? "" : number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getLogo() { + return TextUtils.isEmpty(logo) ? "" : logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + public String getEpg() { + return TextUtils.isEmpty(epg) ? "" : epg; + } + + public void setEpg(String epg) { + this.epg = epg; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUa() { + return TextUtils.isEmpty(ua) ? "" : ua; + } + + public void setUa(String ua) { + this.ua = ua; + } + + public String getClick() { + return TextUtils.isEmpty(click) ? "" : click; + } + + public void setClick(String click) { + this.click = click; + } + + public String getFormat() { + return format; + } + + public void setFormat(String format) { + this.format = format; + } + + public String getOrigin() { + return TextUtils.isEmpty(origin) ? "" : origin; + } + + public void setOrigin(String origin) { + this.origin = origin; + } + + public String getReferer() { + return TextUtils.isEmpty(referer) ? "" : referer; + } + + public void setReferer(String referer) { + this.referer = referer; + } + + public String getTvgId() { + return TextUtils.isEmpty(tvgId) ? getTvgName() : tvgId; + } + + public void setTvgId(String tvgId) { + this.tvgId = tvgId; + } + + public String getTvgName() { + return TextUtils.isEmpty(tvgName) ? getName() : tvgName; + } + + public void setTvgName(String tvgName) { + this.tvgName = tvgName; + } + + public Catchup getCatchup() { + return catchup == null ? new Catchup() : catchup; + } + + public void setCatchup(Catchup catchup) { + this.catchup = catchup; + } + + public JsonElement getHeader() { + return header; + } + + public void setHeader(JsonElement header) { + this.header = header; + } + + public Integer getParse() { + return parse == null ? 0 : parse; + } + + public void setParse(Integer parse) { + this.parse = parse; + } + + public Drm getDrm() { + return drm; + } + + public void setDrm(Drm drm) { + this.drm = drm; + } + + public Group getGroup() { + return group; + } + + public void setGroup(Group group) { + this.group = group; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getMsg() { + return TextUtils.isEmpty(msg) ? "" : msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public boolean hasMsg() { + return !getMsg().isEmpty(); + } + + public Epg getData() { + return data == null ? new Epg() : data; + } + + public void setData(Epg data) { + this.data = data; + } + + public int getLine() { + return line; + } + + public void setLine(int line) { + this.line = Math.max(line, 0); + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public void setSelected(Channel item) { + this.selected = item.equals(this); + } + + public int getLineVisible() { + return isOnly() ? View.GONE : View.VISIBLE; + } + + public void loadLogo(ImageView view) { + ImgUtil.loadLive(getLogo(), view); + } + + public void addUrls(String... urls) { + getUrls().addAll(new ArrayList<>(Arrays.asList(urls))); + } + + public void nextLine() { + setLine(getLine() < getUrls().size() - 1 ? getLine() + 1 : 0); + } + + public void prevLine() { + setLine(getLine() > 0 ? getLine() - 1 : getUrls().size() - 1); + } + + public String getCurrent() { + return getUrls().isEmpty() ? "" : getUrls().get(getLine()).split("\\$")[0]; + } + + public boolean isOnly() { + return getUrls().size() == 1; + } + + public boolean isLast() { + return getUrls().isEmpty() || getLine() == getUrls().size() - 1; + } + + public boolean hasCatchup() { + if (getCatchup().isEmpty() && getCurrent().contains("/PLTV/")) setCatchup(Catchup.PLTV()); + if (!getCatchup().getRegex().isEmpty()) return getCatchup().match(getCurrent()); + return !getCatchup().isEmpty(); + } + + public String getLineText() { + if (getUrls().size() <= 1) return ""; + String[] sp = getUrls().get(getLine()).split("\\$"); + if (sp.length > 1 && !sp[1].isEmpty()) return sp[1]; + return ResUtil.getString(R.string.live_line, getLine() + 1); + } + + public Channel setNumber(int number) { + setNumber(String.format(Locale.getDefault(), "%03d", number)); + return this; + } + + public Channel group(Group group) { + setGroup(group); + return this; + } + + public void live(Live live) { + if (!live.getUa().isEmpty() && getUa().isEmpty()) setUa(live.getUa()); + if (live.getHeader() != null && getHeader() == null) setHeader(live.getHeader()); + if (!live.getClick().isEmpty() && getClick().isEmpty()) setClick(live.getClick()); + if (!live.getOrigin().isEmpty() && getOrigin().isEmpty()) setOrigin(live.getOrigin()); + if (!live.getCatchup().isEmpty() && getCatchup().isEmpty()) setCatchup(live.getCatchup()); + if (!live.getReferer().isEmpty() && getReferer().isEmpty()) setReferer(live.getReferer()); + if (live.getEpg().contains("{") && !getEpg().startsWith("http")) setEpg(live.getEpgApi().replace("{id}", getTvgId()).replace("{name}", getTvgName()).replace("{epg}", getEpg())); + if (live.getLogo().contains("{") && !getLogo().startsWith("http")) setLogo(live.getLogo().replace("{id}", getTvgId()).replace("{name}", getTvgName()).replace("{logo}", getLogo())); + } + + public void setLine(String line) { + setLine(getUrls().indexOf(line)); + } + + public Map getHeaders() { + Map headers = Json.toMap(getHeader()); + if (!getUa().isEmpty()) headers.put(HttpHeaders.USER_AGENT, getUa()); + if (!getOrigin().isEmpty()) headers.put(HttpHeaders.ORIGIN, getOrigin()); + if (!getReferer().isEmpty()) headers.put(HttpHeaders.REFERER, getReferer()); + return headers; + } + + public Channel copy(Channel item) { + setCatchup(item.getCatchup()); + setReferer(item.getReferer()); + setTvgName(item.getTvgName()); + setHeader(item.getHeader()); + setNumber(item.getNumber()); + setOrigin(item.getOrigin()); + setFormat(item.getFormat()); + setParse(item.getParse()); + setClick(item.getClick()); + setTvgId(item.getTvgId()); + setLogo(item.getLogo()); + setName(item.getName()); + setUrls(item.getUrls()); + setData(item.getData()); + setDrm(item.getDrm()); + setEpg(item.getEpg()); + setUa(item.getUa()); + return this; + } + + public Result result() { + Result result = new Result(); + result.setClick(getClick()); + result.setUrl(Url.create().add(getUrl())); + result.setHeader(Json.toObject(getHeaders())); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Channel)) return false; + Channel it = (Channel) obj; + if (!getName().isEmpty()) return getName().equals(it.getName()); + if (!getNumber().isEmpty()) return getNumber().equals(it.getNumber()); + return getName().equals(it.getName()) && getNumber().equals(it.getNumber()); + } + + @Override + public int hashCode() { + return Objects.hash(getName(), getNumber()); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Class.java b/app/src/main/java/com/fongmi/android/tv/bean/Class.java new file mode 100644 index 00000000..46fe0167 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Class.java @@ -0,0 +1,192 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.fongmi.android.tv.App; +import com.github.catvod.utils.Trans; +import com.google.gson.annotations.SerializedName; + +import org.simpleframework.xml.Attribute; +import org.simpleframework.xml.Root; +import org.simpleframework.xml.Text; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +@Root(strict = false) +public class Class implements Parcelable { + + @Attribute(name = "id", required = false) + @SerializedName(value = "type_id", alternate = "id") + private String typeId; + + @Text + @SerializedName(value = "type_name", alternate = "name") + private String typeName; + + @SerializedName("type_flag") + private String typeFlag; + + @SerializedName("filters") + private List filters; + + @SerializedName("land") + private int land; + + @SerializedName("circle") + private int circle; + + @SerializedName("ratio") + private float ratio; + + private Boolean filter; + private boolean activated; + + public Class() { + } + + public static Class objectFrom(String json) { + return App.gson().fromJson(json, Class.class); + } + + public String getTypeId() { + return TextUtils.isEmpty(typeId) ? "" : typeId; + } + + public void setTypeId(String typeId) { + this.typeId = typeId; + } + + public String getTypeName() { + return TextUtils.isEmpty(typeName) ? "" : typeName; + } + + public void setTypeName(String typeName) { + this.typeName = typeName; + } + + public String getTypeFlag() { + return TextUtils.isEmpty(typeFlag) ? "" : typeFlag; + } + + public void setTypeFlag(String typeFlag) { + this.typeFlag = typeFlag; + } + + public List getFilters() { + return filters == null ? Collections.emptyList() : filters; + } + + public void setFilters(List filters) { + if (filters == null || filters.isEmpty()) return; + this.filters = filters; + this.setFilter(false); + } + + public int getLand() { + return land; + } + + public int getCircle() { + return circle; + } + + public float getRatio() { + return ratio; + } + + public void setFilter(Boolean filter) { + this.filter = filter; + } + + public Boolean getFilter() { + return filter; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public boolean toggleFilter() { + setFilter(!getFilter()); + return getFilter(); + } + + public boolean isHome() { + return "home".equals(getTypeId()); + } + + public void trans() { + if (Trans.pass()) return; + this.typeName = Trans.s2t(typeName); + } + + public Style getStyle() { + return Style.get(getLand(), getCircle(), getRatio()); + } + + public HashMap getExtend(boolean change) { + HashMap extend = new HashMap<>(); + for (Filter filter : getFilters()) if (filter.getInit() != null) extend.put(filter.getKey(), change ? filter.setActivated(filter.getInit()) : filter.getInit()); + return extend; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Class)) return false; + Class it = (Class) obj; + return getTypeId().equals(it.getTypeId()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.typeId); + dest.writeString(this.typeName); + dest.writeString(this.typeFlag); + dest.writeList(this.filters); + dest.writeValue(this.filter); + dest.writeInt(this.land); + dest.writeInt(this.circle); + dest.writeFloat(this.ratio); + dest.writeByte(this.activated ? (byte) 1 : (byte) 0); + } + + protected Class(Parcel in) { + this.typeId = in.readString(); + this.typeName = in.readString(); + this.typeFlag = in.readString(); + this.filters = new ArrayList<>(); + in.readList(this.filters, Filter.class.getClassLoader()); + this.filter = (Boolean) in.readValue(Boolean.class.getClassLoader()); + this.land = in.readInt(); + this.circle = in.readInt(); + this.ratio = in.readFloat(); + this.activated = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Class createFromParcel(Parcel source) { + return new Class(source); + } + + @Override + public Class[] newArray(int size) { + return new Class[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/ClearKey.java b/app/src/main/java/com/fongmi/android/tv/bean/ClearKey.java new file mode 100644 index 00000000..e306cd0c --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/ClearKey.java @@ -0,0 +1,66 @@ +package com.fongmi.android.tv.bean; + +import android.util.Base64; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.github.catvod.utils.Util; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class ClearKey { + + @SerializedName("keys") + private List keys; + @SerializedName("type") + private String type; + + public static ClearKey objectFrom(String str) throws Exception { + ClearKey item = App.gson().fromJson(str, ClearKey.class); + if (item.keys == null) throw new Exception(); + return item; + } + + public static ClearKey get(String line) { + ClearKey item = new ClearKey(); + item.keys = new ArrayList<>(); + item.type = "temporary"; + item.addKeys(line); + return item; + } + + private void addKeys(String line) { + int flags = Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP; + for (String s : line.split(",")) { + String[] a = s.split(":"); + String kid = Util.base64(Util.hex2byte(a[0].trim()), flags).replace("=", ""); + String k = Util.base64(Util.hex2byte(a[1].trim()), flags).replace("=", ""); + keys.add(new Keys(kid, k)); + } + } + + public static class Keys { + + @SerializedName("kty") + private String kty; + @SerializedName("k") + private String k; + @SerializedName("kid") + private String kid; + + public Keys(String kid, String k) { + this.kty = "oct"; + this.kid = kid; + this.k = k; + } + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Collect.java b/app/src/main/java/com/fongmi/android/tv/bean/Collect.java new file mode 100644 index 00000000..6d856aca --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Collect.java @@ -0,0 +1,92 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; + +import java.util.ArrayList; +import java.util.List; + +public class Collect implements Parcelable { + + private boolean activated; + private List list; + private Site site; + private int page; + + public static Collect all() { + Collect item = new Collect(Site.get("all", ResUtil.getString(R.string.all)), new ArrayList<>()); + item.setActivated(true); + return item; + } + + public static Collect create(List list) { + return new Collect(list.get(0).getSite(), list); + } + + public Collect() { + } + + public Collect(Site site, List list) { + this.site = site; + this.list = list; + } + + public Site getSite() { + return site == null ? new Site() : site; + } + + public List getList() { + return list == null ? new ArrayList<>() : list; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public int getPage() { + return Math.max(1, page); + } + + public void setPage(int page) { + this.page = page; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeByte(this.activated ? (byte) 1 : (byte) 0); + dest.writeTypedList(this.list); + dest.writeParcelable(this.site, flags); + dest.writeInt(this.page); + } + + protected Collect(Parcel in) { + this.activated = in.readByte() != 0; + this.list = in.createTypedArrayList(Vod.CREATOR); + this.site = in.readParcelable(Site.class.getClassLoader()); + this.page = in.readInt(); + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Collect createFromParcel(Parcel source) { + return new Collect(source); + } + + @Override + public Collect[] newArray(int size) { + return new Collect[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Config.java b/app/src/main/java/com/fongmi/android/tv/bean/Config.java new file mode 100644 index 00000000..2c2e2dfb --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Config.java @@ -0,0 +1,283 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.Index; +import androidx.room.PrimaryKey; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.utils.FileUtil; +import com.github.catvod.utils.Path; +import com.github.catvod.utils.Prefers; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +@Entity(indices = @Index(value = {"url", "type"}, unique = true)) +public class Config { + + @PrimaryKey(autoGenerate = true) + @SerializedName("id") + private int id; + @SerializedName("type") + private int type; + @SerializedName("time") + private long time; + @SerializedName("url") + private String url; + @SerializedName("json") + private String json; + @SerializedName("name") + private String name; + @SerializedName("logo") + private String logo; + @SerializedName("home") + private String home; + @SerializedName("parse") + private String parse; + + public static List arrayFrom(String str) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(str, listType); + return items == null ? Collections.emptyList() : items; + } + + public static Config objectFrom(String str) { + return App.gson().fromJson(str, Config.class); + } + + public static Config create(int type) { + return new Config().type(type); + } + + public static Config create(int type, String url) { + return new Config().type(type).url(url).insert(); + } + + public static Config create(int type, String url, String name) { + return new Config().type(type).url(url).name(name).insert(); + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getJson() { + return json; + } + + public void setJson(String json) { + this.json = json; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLogo() { + return logo; + } + + public void setLogo(String logo) { + this.logo = logo; + } + + public String getHome() { + return home; + } + + public void setHome(String home) { + this.home = home; + } + + public String getParse() { + return parse; + } + + public void setParse(String parse) { + this.parse = parse; + } + + public long getTime() { + return time; + } + + public void setTime(long time) { + this.time = time; + } + + public Config type(int type) { + setType(type); + return this; + } + + public Config url(String url) { + setUrl(url); + return this; + } + + public Config json(String json) { + setJson(json); + return this; + } + + public Config name(String name) { + setName(name); + return this; + } + + public Config logo(String logo) { + setLogo(logo); + return this; + } + + public Config home(String home) { + setHome(home); + return this; + } + + public Config parse(String parse) { + setParse(parse); + return this; + } + + public boolean isEmpty() { + return TextUtils.isEmpty(getUrl()); + } + + public String getDesc() { + if (!TextUtils.isEmpty(getName())) return getName(); + if (!TextUtils.isEmpty(getUrl())) return getUrl(); + return ""; + } + + public static List getAll(int type) { + return AppDatabase.get().getConfigDao().findByType(type); + } + + public static List findUrls() { + return AppDatabase.get().getConfigDao().findUrlByType(0); + } + + public static void delete(String url) { + AppDatabase.get().getConfigDao().delete(url); + } + + public static void delete(String url, int type) { + if (type == 2) Path.clear(FileUtil.getWall(0)); + if (type == 2) AppDatabase.get().getConfigDao().delete(type); + else AppDatabase.get().getConfigDao().delete(url, type); + } + + public static Config vod() { + Config item = AppDatabase.get().getConfigDao().findOne(0); + return item == null ? create(0) : item; + } + + public static Config live() { + Config item = AppDatabase.get().getConfigDao().findOne(1); + return item == null ? create(1) : item; + } + + public static Config wall() { + Config item = AppDatabase.get().getConfigDao().findOne(2); + return item == null ? create(2) : item; + } + + public static Config find(int id) { + return AppDatabase.get().getConfigDao().findById(id); + } + + public static Config find(String url, int type) { + Config item = AppDatabase.get().getConfigDao().find(url, type); + return item == null ? create(type, url) : item.type(type); + } + + public static Config find(String url, String name, int type) { + Config item = AppDatabase.get().getConfigDao().find(url, type); + return item == null ? create(type, url, name) : item.type(type).name(name); + } + + public static Config find(Config config) { + return find(config, config.getType()); + } + + public static Config find(Config config, int type) { + Config item = AppDatabase.get().getConfigDao().find(config.getUrl(), type); + return item == null ? create(type, config.getUrl(), config.getName()) : item.type(type).name(config.getName()); + } + + public static Config find(Depot depot, int type) { + Config item = AppDatabase.get().getConfigDao().find(depot.getUrl(), type); + return item == null ? create(type, depot.getUrl(), depot.getName()) : item.type(type).name(depot.getName()); + } + + public Config insert() { + if (isEmpty()) return this; + setId(Math.toIntExact(AppDatabase.get().getConfigDao().insert(this))); + return this; + } + + public Config save() { + if (isEmpty()) return this; + AppDatabase.get().getConfigDao().insertOrUpdate(this); + return this; + } + + public Config update() { + if (isEmpty()) return this; + setTime(System.currentTimeMillis()); + Prefers.put("config_" + getType(), getUrl()); + return save(); + } + + public void delete() { + AppDatabase.get().getConfigDao().delete(getUrl(), getType()); + History.delete(getId()); + Keep.delete(getId()); + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Config)) return false; + Config it = (Config) obj; + return getId() == it.getId(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Core.java b/app/src/main/java/com/fongmi/android/tv/bean/Core.java new file mode 100644 index 00000000..104684c2 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Core.java @@ -0,0 +1,85 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.utils.UrlUtil; +import com.fongmi.hook.Hook; +import com.github.catvod.net.OkHttp; +import com.google.gson.annotations.SerializedName; + +public class Core { + + @SerializedName("auth") + private String auth; + @SerializedName("name") + private String name; + @SerializedName("pass") + private String pass; + @SerializedName("broker") + private String broker; + @SerializedName("domain") + private String domain; + @SerializedName("resp") + private String resp; + @SerializedName("sign") + private String sign; + @SerializedName("pkg") + private String pkg; + @SerializedName("so") + private String so; + + public String getAuth() { + return !getResp().isEmpty() ? Server.get().getAddress("/tvbus") : TextUtils.isEmpty(auth) ? "" : UrlUtil.convert(auth); + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : getString(name); + } + + public String getPass() { + return TextUtils.isEmpty(pass) ? "" : getString(pass); + } + + public String getBroker() { + return TextUtils.isEmpty(broker) ? "" : UrlUtil.convert(broker); + } + + public String getDomain() { + return TextUtils.isEmpty(domain) ? "" : getString(domain); + } + + public String getResp() { + return TextUtils.isEmpty(resp) ? "" : getString(resp); + } + + public String getSign() { + return TextUtils.isEmpty(sign) ? "" : getString(sign); + } + + public String getPkg() { + return TextUtils.isEmpty(pkg) ? "" : getString(pkg); + } + + public String getSo() { + return TextUtils.isEmpty(so) ? "" : UrlUtil.convert(so); + } + + public Hook getHook() { + return !getPkg().isEmpty() && !getSign().isEmpty() ? new Hook(getSign(), getPkg()) : null; + } + + private String getString(String value) { + return (value = UrlUtil.convert(value)).startsWith("http") ? OkHttp.string(value) : value; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof Core)) return false; + Core it = (Core) obj; + return getSign().equals(it.getSign()); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Danmaku.java b/app/src/main/java/com/fongmi/android/tv/bean/Danmaku.java new file mode 100644 index 00000000..599d2073 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Danmaku.java @@ -0,0 +1,64 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import com.google.gson.annotations.SerializedName; + +public class Danmaku { + + @SerializedName("name") + private String name; + @SerializedName("url") + private String url; + + private boolean selected; + + public static Danmaku from(String path) { + Danmaku danmaku = new Danmaku(); + danmaku.setName(path); + danmaku.setUrl(path); + return danmaku; + } + + public static Danmaku empty() { + return new Danmaku(); + } + + public String getName() { + return TextUtils.isEmpty(name) ? getUrl() : name; + } + + public void setName(String name) { + this.name = name; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : url; + } + + public void setUrl(String url) { + this.url = url; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public boolean isEmpty() { + return getUrl().isEmpty(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof Danmaku)) return false; + Danmaku it = (Danmaku) obj; + return getUrl().equals(it.getUrl()); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fongmi/android/tv/bean/DanmakuData.java b/app/src/main/java/com/fongmi/android/tv/bean/DanmakuData.java new file mode 100644 index 00000000..b9cff539 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/DanmakuData.java @@ -0,0 +1,55 @@ +package com.fongmi.android.tv.bean; + +import android.graphics.Color; +import android.text.TextUtils; + +import java.util.regex.Matcher; + +public class DanmakuData { + + private final String text; + private int type; + private int color; + private int shadow; + private long time; + private float size; + + public DanmakuData(Matcher matcher, float density) throws Exception { + this.param(matcher.group(1), density); + this.text = matcher.group(2); + } + + private void param(String param, float density) throws Exception { + String[] params = param.split(","); + if (params.length < 4) throw new Exception(); + this.type = Integer.parseInt(params[1]); + this.time = (long) (Float.parseFloat(params[0]) * 1000); + this.size = Float.parseFloat(params[2]) * (density - 0.6f); + this.color = (int) ((0x00000000FF000000L | Long.parseLong(params[3])) & 0x00000000FFFFFFFFL); + this.shadow = color <= Color.BLACK ? Color.WHITE : Color.BLACK; + } + + public int getType() { + return type; + } + + public int getShadow() { + return shadow; + } + + public int getColor() { + return color; + } + + public long getTime() { + return time; + } + + public float getSize() { + return size; + } + + public String getText() { + return TextUtils.isEmpty(text) ? "" : text.replace("&", "&").replace(""", "\"").replace(">", ">").replace("<", "<"); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Depot.java b/app/src/main/java/com/fongmi/android/tv/bean/Depot.java new file mode 100644 index 00000000..49d7bf52 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Depot.java @@ -0,0 +1,33 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import com.fongmi.android.tv.App; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +public class Depot { + + @SerializedName("url") + private String url; + @SerializedName("name") + private String name; + + public static List arrayFrom(String str) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(str, listType); + return items == null ? Collections.emptyList() : items; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : url; + } + + public String getName() { + return TextUtils.isEmpty(name) ? getUrl() : name; + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Device.java b/app/src/main/java/com/fongmi/android/tv/bean/Device.java new file mode 100644 index 00000000..c0aa013c --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Device.java @@ -0,0 +1,192 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.Index; +import androidx.room.PrimaryKey; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.utils.DeviceUtils; +import com.fongmi.android.tv.utils.UrlUtil; +import com.fongmi.android.tv.utils.Util; +import com.google.gson.annotations.SerializedName; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +@Entity(indices = @Index(value = {"uuid", "name"}, unique = true)) +public class Device { + + @PrimaryKey(autoGenerate = true) + @SerializedName("id") + private Integer id; + @SerializedName("uuid") + private String uuid; + @SerializedName("name") + private String name; + @SerializedName("ip") + private String ip; + @SerializedName("type") + private int type; + + @Ignore + @SerializedName("serial") + private String serial; + @Ignore + @SerializedName("eth") + private String eth; + @Ignore + @SerializedName("wlan") + private String wlan; + @Ignore + @SerializedName("time") + private long time; + + public static Device get() { + Device device = new Device(); + device.setTime(App.time()); + device.setSerial(Util.getSerial()); + device.setEth(Util.getMac("eth0")); + device.setWlan(Util.getMac("wlan0")); + device.setUuid(Util.getAndroidId()); + device.setName(Util.getDeviceName()); + device.setIp(Server.get().getAddress()); + device.setType(DeviceUtils.getDeviceType()); + return device; + } + + public static Device get(org.fourthline.cling.model.meta.Device item) { + Device device = new Device(); + device.setUuid(item.getIdentity().getUdn().getIdentifierString()); + device.setName(item.getDetails().getFriendlyName()); + device.setType(2); + return device; + } + + public static Device objectFrom(String str) { + return App.gson().fromJson(str, Device.class); + } + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getUuid() { + return TextUtils.isEmpty(uuid) ? "" : uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIp() { + return TextUtils.isEmpty(ip) ? "" : ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public void setSerial(String serial) { + this.serial = serial; + } + + public void setEth(String eth) { + this.eth = eth; + } + + public void setWlan(String wlan) { + this.wlan = wlan; + } + + public void setTime(long time) { + this.time = time; + } + + public boolean isLeanback() { + return getType() == 0; + } + + public boolean isMobile() { + return getType() == 1; + } + + public boolean isDLNA() { + return getType() == 2; + } + + public boolean isApp() { + return isLeanback() || isMobile(); + } + + public String getHost() { + return isDLNA() ? getUuid() : UrlUtil.host(getIp()); + } + + public Device save() { + AppDatabase.get().getDeviceDao().insertOrUpdate(this); + return this; + } + + public static List getAll() { + return AppDatabase.get().getDeviceDao().findAll(); + } + + public static void delete() { + AppDatabase.get().getDeviceDao().delete(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Device)) return false; + Device it = (Device) obj; + return getUuid().equals(it.getUuid()) && getName().equals(it.getName()); + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } + + public static class Sorter implements Comparator { + + public static void sort(List items) { + if (items.size() > 1) Collections.sort(items, new Sorter()); + } + + @Override + public int compare(Device o1, Device o2) { + int comp = Integer.compare(o1.getType(), o2.getType()); + return comp != 0 ? comp : o1.getName().compareTo(o2.getName()); + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Drm.java b/app/src/main/java/com/fongmi/android/tv/bean/Drm.java new file mode 100644 index 00000000..d4351c6c --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Drm.java @@ -0,0 +1,65 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.media3.common.C; +import androidx.media3.common.MediaItem; + +import com.github.catvod.utils.Json; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.util.UUID; + +public class Drm { + + @SerializedName("key") + private String key; + @SerializedName("type") + private String type; + @SerializedName("forceKey") + private boolean forceKey; + @SerializedName("header") + private JsonElement header; + + public static Drm create(String key, String type) { + return new Drm(key, type); + } + + private Drm(String key, String type) { + this.key = key; + this.type = type; + } + + private String getKey() { + return TextUtils.isEmpty(key) ? "" : key; + } + + private String getType() { + return TextUtils.isEmpty(type) ? "" : type; + } + + public boolean isForceKey() { + return forceKey; + } + + private JsonElement getHeader() { + return header; + } + + public UUID getUUID() { + if (getType().contains("playready")) return C.PLAYREADY_UUID; + if (getType().contains("widevine")) return C.WIDEVINE_UUID; + if (getType().contains("clearkey")) return C.CLEARKEY_UUID; + return C.UUID_NIL; + } + + public MediaItem.DrmConfiguration get() { + MediaItem.DrmConfiguration.Builder builder = new MediaItem.DrmConfiguration.Builder(getUUID()); + builder.setMultiSession(!C.CLEARKEY_UUID.equals(getUUID())); + builder.setLicenseRequestHeaders(Json.toMap(getHeader())); + builder.setForceDefaultLicenseUri(isForceKey()); + builder.setLicenseUri(getKey()); + return builder.build(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Epg.java b/app/src/main/java/com/fongmi/android/tv/bean/Epg.java new file mode 100644 index 00000000..89d74811 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Epg.java @@ -0,0 +1,110 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.api.EpgParser; +import com.fongmi.android.tv.utils.Util; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Trans; +import com.google.gson.annotations.SerializedName; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; + +public class Epg { + + @SerializedName("key") + private String key; + @SerializedName("date") + private String date; + @SerializedName("epg_data") + private List list; + + private int width; + + public static Epg objectFrom(String str, String key, List formats) throws Exception { + if (!Json.isObj(str)) return EpgParser.getEpg(str, key); + Epg item = App.gson().fromJson(str, Epg.class); + item.setTime(formats); + item.setKey(key); + return item; + } + + public static Epg create(String key, String date) { + Epg item = new Epg(); + item.setKey(key); + item.setDate(date); + item.setList(new ArrayList<>()); + return item; + } + + public String getKey() { + return TextUtils.isEmpty(key) ? "" : key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getDate() { + return TextUtils.isEmpty(date) ? "" : date; + } + + public void setDate(String date) { + this.date = date; + } + + public List getList() { + return list == null ? Collections.emptyList() : list; + } + + public void setList(List list) { + this.list = list; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public boolean equal(String date) { + return getDate().equals(date); + } + + private void setTime(List formats) { + setList(new ArrayList<>(new LinkedHashSet<>(getList()))); + for (EpgData item : getList()) { + item.setStartTime(Util.format(getDate().concat(item.getStart()), formats)); + item.setEndTime(Util.format(getDate().concat(item.getEnd()), formats)); + if (item.getEndTime() < item.getStartTime()) item.checkDay(); + item.setTitle(Trans.s2t(item.getTitle())); + } + } + + public EpgData getEpgData() { + for (EpgData item : getList()) if (item.isSelected()) return item; + return new EpgData(); + } + + public Epg selected() { + for (EpgData item : getList()) item.setSelected(item.isInRange()); + return this; + } + + public int getSelected() { + for (int i = 0; i < getList().size(); i++) if (getList().get(i).isSelected()) return i; + return -1; + } + + public int getInRange() { + for (int i = 0; i < getList().size(); i++) if (getList().get(i).isInRange()) return i; + return -1; + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java b/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java new file mode 100644 index 00000000..090129d7 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java @@ -0,0 +1,119 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.gson.annotations.SerializedName; + +import java.util.Calendar; + +public class EpgData { + + @SerializedName("title") + private String title; + @SerializedName("start") + private String start; + @SerializedName("end") + private String end; + + private boolean selected; + private long startTime; + private long endTime; + + public String getTitle() { + return TextUtils.isEmpty(title) ? "" : title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getStart() { + return TextUtils.isEmpty(start) ? "" : start; + } + + public void setStart(String start) { + this.start = start; + } + + public String getEnd() { + return TextUtils.isEmpty(end) ? "" : end; + } + + public void setEnd(String end) { + this.end = end; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public void setSelected(EpgData item) { + this.selected = item.equals(this); + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getEndTime() { + return endTime; + } + + public void setEndTime(long endTime) { + this.endTime = endTime; + } + + public boolean isInRange() { + return getStartTime() <= System.currentTimeMillis() && System.currentTimeMillis() <= getEndTime(); + } + + public boolean isFuture() { + return getStartTime() > System.currentTimeMillis(); + } + + public String format() { + if (getTitle().isEmpty()) return ""; + if (getStart().isEmpty() && getEnd().isEmpty()) return ResUtil.getString(R.string.play_now, getTitle()); + return getStart() + " ~ " + getEnd() + " " + getTitle(); + } + + public String getTime() { + if (getStart().isEmpty() && getEnd().isEmpty()) return ""; + return getStart() + " ~ " + getEnd(); + } + + public void checkDay() { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(getEndTime()); + cal.add(Calendar.DAY_OF_MONTH, 1); + setEndTime(cal.getTimeInMillis()); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof EpgData)) return false; + EpgData it = (EpgData) obj; + return getTitle().equals(it.getTitle()) && getEnd().equals(it.getEnd()) && getStart().equals(it.getStart()); + } + + @Override + public int hashCode() { + int result = getTitle().hashCode(); + result = 31 * result + getEnd().hashCode(); + result = 31 * result + getStart().hashCode(); + return result; + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Episode.java b/app/src/main/java/com/fongmi/android/tv/bean/Episode.java new file mode 100644 index 00000000..0691eb1c --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Episode.java @@ -0,0 +1,159 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.Util; +import com.github.catvod.utils.Trans; +import com.google.gson.annotations.SerializedName; + +public class Episode implements Parcelable { + + @SerializedName("name") + private String name; + @SerializedName("desc") + private String desc; + @SerializedName("url") + private String url; + + private int index; + private int number; + private boolean activated; + private boolean selected; + + public static Episode create(String name, String url) { + return new Episode(name, "", url); + } + + public static Episode create(String name, String desc, String url) { + return new Episode(name, desc, url); + } + + public static Episode objectFrom(String str) { + return App.gson().fromJson(str, Episode.class); + } + + public Episode(String name, String desc, String url) { + this.number = Util.getDigit(name); + this.name = Trans.s2t(name); + this.desc = Trans.s2t(desc); + this.url = url; + } + + public Episode() { + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDesc() { + return desc; + } + + public String getUrl() { + return url; + } + + public int getIndex() { + return index; + } + + public void setIndex(int index) { + this.index = index; + } + + public int getNumber() { + return number; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + this.selected = activated; + } + + public void deactivated() { + setActivated(false); + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public boolean rule1(String name) { + return getName().equalsIgnoreCase(name); + } + + public boolean rule2(int number) { + return getNumber() == number && number != -1; + } + + public boolean rule3(String name) { + return getName().toLowerCase().contains(name.toLowerCase()); + } + + public boolean rule4(String name) { + return name.toLowerCase().contains(getName().toLowerCase()); + } + + public boolean equals(Episode episode) { + return rule1(episode.getName()); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Episode)) return false; + Episode it = (Episode) obj; + return getUrl().equals(it.getUrl()) && getName().equals(it.getName()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.name); + dest.writeString(this.desc); + dest.writeString(this.url); + dest.writeInt(this.number); + dest.writeByte(this.activated ? (byte) 1 : (byte) 0); + dest.writeByte(this.selected ? (byte) 1 : (byte) 0); + } + + protected Episode(Parcel in) { + this.name = in.readString(); + this.desc = in.readString(); + this.url = in.readString(); + this.number = in.readInt(); + this.activated = in.readByte() != 0; + this.selected = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Episode createFromParcel(Parcel source) { + return new Episode(source); + } + + @Override + public Episode[] newArray(int size) { + return new Episode[size]; + } + }; +} \ No newline at end of file diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Filter.java b/app/src/main/java/com/fongmi/android/tv/bean/Filter.java new file mode 100644 index 00000000..ac92c484 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Filter.java @@ -0,0 +1,109 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import com.fongmi.android.tv.App; +import com.github.catvod.utils.Trans; +import com.google.common.base.Predicates; +import com.google.common.collect.Iterables; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Filter implements Parcelable { + + @SerializedName("key") + private String key; + @SerializedName("name") + private String name; + @SerializedName("init") + private String init; + @SerializedName("value") + private List value; + + public static Filter objectFrom(JsonElement element) { + return App.gson().fromJson(element, Filter.class); + } + + public static List arrayFrom(String result) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(result, listType); + return items == null ? Collections.emptyList() : items; + } + + public Filter() { + } + + public String getKey() { + return key; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public String getInit() { + return init; + } + + public List getValue() { + return value == null ? Collections.emptyList() : value; + } + + public String setActivated(String v) { + int index = getValue().indexOf(new Value(v)); + if (index != -1) getValue().get(index).setActivated(true); + return v; + } + + public Filter check() { + Iterables.removeIf(getValue(), Predicates.isNull()); + return this; + } + + public Filter trans() { + if (Trans.pass()) return this; + for (Value value : getValue()) value.trans(); + return this; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.key); + dest.writeString(this.name); + dest.writeString(this.init); + dest.writeList(this.value); + } + + protected Filter(Parcel in) { + this.key = in.readString(); + this.name = in.readString(); + this.init = in.readString(); + this.value = new ArrayList<>(); + in.readList(this.value, Value.class.getClassLoader()); + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Filter createFromParcel(Parcel source) { + return new Filter(source); + } + + @Override + public Filter[] newArray(int size) { + return new Filter[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Flag.java b/app/src/main/java/com/fongmi/android/tv/bean/Flag.java new file mode 100644 index 00000000..ae80e49e --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Flag.java @@ -0,0 +1,178 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.Util; +import com.github.catvod.utils.Trans; +import com.google.gson.annotations.SerializedName; + +import org.simpleframework.xml.Attribute; +import org.simpleframework.xml.Text; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +public class Flag implements Parcelable { + + @Attribute(name = "flag", required = false) + @SerializedName("flag") + private String flag; + private String show; + + @Text + private String urls; + + @SerializedName("episodes") + private List episodes; + + private boolean activated; + private int position; + + public static Flag create(String flag) { + return new Flag(flag); + } + + public Flag() { + this.episodes = new ArrayList<>(); + this.position = -1; + } + + public Flag(String flag) { + this.episodes = new ArrayList<>(); + this.show = Trans.s2t(flag); + this.flag = flag; + this.position = -1; + } + + public String getShow() { + return TextUtils.isEmpty(show) ? getFlag() : show; + } + + public String getFlag() { + return TextUtils.isEmpty(flag) ? "" : flag; + } + + public void setFlag(String flag) { + this.flag = flag; + } + + public String getUrls() { + return urls; + } + + public List getEpisodes() { + return episodes; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(Flag item) { + this.activated = item.equals(this); + if (activated) item.episodes = episodes; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public void createEpisode(String data) { + String[] urls = data.contains("#") ? data.split("#") : new String[]{data}; + for (int i = 0; i < urls.length; i++) { + String[] split = urls[i].split("\\$", 2); + String number = String.format(Locale.getDefault(), "%02d", i + 1); + Episode episode = split.length > 1 ? Episode.create(split[0].isEmpty() ? number : split[0].trim(), split[1]) : Episode.create(number, urls[i]); + if (!getEpisodes().contains(episode)) getEpisodes().add(episode); + } + } + + public void toggle(boolean activated, Episode episode) { + if (activated) setActivated(episode); + else for (Episode item : getEpisodes()) item.deactivated(); + } + + private void setActivated(Episode episode) { + setPosition(getEpisodes().indexOf(episode)); + for (int i = 0; i < getEpisodes().size(); i++) getEpisodes().get(i).setActivated(i == getPosition()); + } + + public Episode find(String remarks, boolean strict) { + int number = Util.getDigit(remarks); + if (getEpisodes().size() == 0) return null; + if (getEpisodes().size() == 1) return getEpisodes().get(0); + for (Episode item : getEpisodes()) if (item.rule1(remarks)) return item; + for (Episode item : getEpisodes()) if (item.rule2(number)) return item; + if (number == -1) for (Episode item : getEpisodes()) if (item.rule3(remarks)) return item; + if (number == -1) for (Episode item : getEpisodes()) if (item.rule4(remarks)) return item; + if (getPosition() != -1) return getEpisodes().get(getPosition()); + return strict ? null : getEpisodes().get(0); + } + + public static List create(String flag, String url) { + Flag item = Flag.create(flag); + item.getEpisodes().add(Episode.create("01", url)); + return Arrays.asList(item); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Flag)) return false; + Flag it = (Flag) obj; + return getFlag().equals(it.getFlag()); + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.flag); + dest.writeString(this.show); + dest.writeString(this.urls); + dest.writeTypedList(this.episodes); + dest.writeByte(this.activated ? (byte) 1 : (byte) 0); + dest.writeInt(this.position); + } + + protected Flag(Parcel in) { + this.flag = in.readString(); + this.show = in.readString(); + this.urls = in.readString(); + this.episodes = in.createTypedArrayList(Episode.CREATOR); + this.activated = in.readByte() != 0; + this.position = in.readInt(); + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Flag createFromParcel(Parcel source) { + return new Flag(source); + } + + @Override + public Flag[] newArray(int size) { + return new Flag[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Group.java b/app/src/main/java/com/fongmi/android/tv/bean/Group.java new file mode 100644 index 00000000..163ea232 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Group.java @@ -0,0 +1,164 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.StringRes; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Group { + + @SerializedName("channel") + private List channel; + @SerializedName("name") + private String name; + @SerializedName("pass") + private String pass; + + private boolean selected; + private int position; + private int width; + + public static List arrayFrom(String str) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(str, listType); + return items == null ? Collections.emptyList() : items; + } + + public static Group create() { + return create(R.string.setting_live); + } + + public static Group create(@StringRes int resId) { + return new Group(ResUtil.getString(resId)); + } + + public static Group create(String name, boolean pass) { + return new Group(name, pass); + } + + public Group(String name) { + this(name, false); + } + + public Group(String name, boolean pass) { + this.name = name; + this.position = -1; + if (name.contains("_")) parse(pass); + if (name.isEmpty()) setName(ResUtil.getString(R.string.setting_live)); + } + + private void parse(boolean pass) { + String[] splits = name.split("_", 2); + setName(splits[0]); + if (pass || splits.length == 1) return; + setPass(splits[1]); + } + + public List getChannel() { + return channel = channel == null ? new ArrayList<>() : channel; + } + + public void setChannel(List channel) { + this.channel = channel; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPass() { + return TextUtils.isEmpty(pass) ? "" : pass; + } + + public void setPass(String pass) { + this.pass = pass; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public boolean isHidden() { + return !TextUtils.isEmpty(getPass()); + } + + public boolean isKeep() { + return getName().equals(ResUtil.getString(R.string.keep)); + } + + public boolean isEmpty() { + return getChannel().isEmpty(); + } + + public boolean skip() { + return isKeep(); + } + + public int find(int number) { + return getChannel().lastIndexOf(Channel.create(number)); + } + + public int find(String name) { + return getChannel().lastIndexOf(Channel.create(name)); + } + + public void add(Channel channel) { + int index = getChannel().indexOf(channel); + if (index == -1) getChannel().add(Channel.create(channel)); + else getChannel().get(index).getUrls().addAll(channel.getUrls()); + } + + public Channel find(Channel channel) { + int index = getChannel().indexOf(channel); + if (index != -1) return getChannel().get(index); + getChannel().add(channel); + return channel; + } + + public Channel current() { + return getChannel().get(getPosition()).group(this); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (this == obj) return true; + if (!(obj instanceof Group)) return false; + Group it = (Group) obj; + return getName().equals(it.getName()) && getChannel().size() == it.getChannel().size(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/History.java b/app/src/main/java/com/fongmi/android/tv/bean/History.java new file mode 100644 index 00000000..0ac2f21b --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/History.java @@ -0,0 +1,353 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.media3.common.C; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.RefreshEvent; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +@Entity +public class History { + + @NonNull + @PrimaryKey + @SerializedName("key") + private String key; + @SerializedName("vodPic") + private String vodPic; + @SerializedName("vodName") + private String vodName; + @SerializedName("vodFlag") + private String vodFlag; + @SerializedName("vodRemarks") + private String vodRemarks; + @SerializedName("episodeUrl") + private String episodeUrl; + @SerializedName("revSort") + private boolean revSort; + @SerializedName("revPlay") + private boolean revPlay; + @SerializedName("createTime") + private long createTime; + @SerializedName("opening") + private long opening; + @SerializedName("ending") + private long ending; + @SerializedName("position") + private long position; + @SerializedName("duration") + private long duration; + @SerializedName("speed") + private float speed; + @SerializedName("scale") + private int scale; + @SerializedName("cid") + private int cid; + + public static History objectFrom(String str) { + return App.gson().fromJson(str, History.class); + } + + public static List arrayFrom(String str) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(str, listType); + return items == null ? Collections.emptyList() : items; + } + + public History() { + this.speed = 1; + this.scale = -1; + this.ending = C.TIME_UNSET; + this.opening = C.TIME_UNSET; + this.position = C.TIME_UNSET; + this.duration = C.TIME_UNSET; + } + + @NonNull + public String getKey() { + return key; + } + + public void setKey(@NonNull String key) { + this.key = key; + } + + public String getVodPic() { + return vodPic; + } + + public void setVodPic(String vodPic) { + this.vodPic = vodPic; + } + + public String getVodName() { + return vodName; + } + + public void setVodName(String vodName) { + this.vodName = vodName; + } + + public String getVodFlag() { + return vodFlag; + } + + public void setVodFlag(String vodFlag) { + this.vodFlag = vodFlag; + } + + public String getVodRemarks() { + return vodRemarks == null ? "" : vodRemarks; + } + + public void setVodRemarks(String vodRemarks) { + this.vodRemarks = vodRemarks; + } + + public String getEpisodeUrl() { + return episodeUrl == null ? "" : episodeUrl; + } + + public void setEpisodeUrl(String episodeUrl) { + this.episodeUrl = episodeUrl; + } + + public boolean isRevSort() { + return revSort; + } + + public void setRevSort(boolean revSort) { + this.revSort = revSort; + } + + public boolean isRevPlay() { + return revPlay; + } + + public void setRevPlay(boolean revPlay) { + this.revPlay = revPlay; + } + + public long getCreateTime() { + return createTime; + } + + public void setCreateTime(long createTime) { + this.createTime = createTime; + } + + public long getOpening() { + return opening; + } + + public void setOpening(long opening) { + this.opening = opening; + } + + public long getEnding() { + return ending; + } + + public void setEnding(long ending) { + this.ending = ending; + } + + public long getPosition() { + return position; + } + + public void setPosition(long position) { + this.position = position; + } + + public long getDuration() { + return duration; + } + + public void setDuration(long duration) { + this.duration = duration; + } + + public float getSpeed() { + return speed; + } + + public void setSpeed(float speed) { + this.speed = speed; + } + + public int getScale() { + return scale; + } + + public void setScale(int scale) { + this.scale = scale; + } + + public int getCid() { + return cid; + } + + public void setCid(int cid) { + this.cid = cid; + } + + public String getSiteName() { + return VodConfig.get().getSite(getSiteKey()).getName(); + } + + public String getSiteKey() { + return getKey().split(AppDatabase.SYMBOL)[0]; + } + + public String getVodId() { + return getKey().split(AppDatabase.SYMBOL)[1]; + } + + public Flag getFlag() { + return Flag.create(getVodFlag()); + } + + public Episode getEpisode() { + return Episode.create(getVodRemarks(), getEpisodeUrl()); + } + + public int getSiteVisible() { + return TextUtils.isEmpty(getSiteName()) ? View.GONE : View.VISIBLE; + } + + public int getRevPlayText() { + return isRevPlay() ? R.string.play_backward : R.string.play_forward; + } + + public int getRevPlayHint() { + return isRevPlay() ? R.string.play_backward_hint : R.string.play_forward_hint; + } + + public static List get() { + return get(VodConfig.getCid()); + } + + public static List get(int cid) { + return AppDatabase.get().getHistoryDao().find(cid, System.currentTimeMillis() - Constant.HISTORY_TIME); + } + + public static History find(String key) { + return AppDatabase.get().getHistoryDao().find(VodConfig.getCid(), key); + } + + public static void delete(int cid) { + AppDatabase.get().getHistoryDao().delete(cid); + } + + private void checkParam(History item) { + if (getOpening() <= 0) setOpening(item.getOpening()); + if (getEnding() <= 0) setEnding(item.getEnding()); + if (getSpeed() == 1) setSpeed(item.getSpeed()); + } + + private void merge(List items, boolean force) { + for (History item : items) { + if (getDuration() > 0 && item.getDuration() > 0 && Math.abs(getDuration() - item.getDuration()) > TimeUnit.MINUTES.toMillis(10)) continue; + if (!force && getKey().equals(item.getKey())) continue; + checkParam(item); + item.delete(); + } + } + + public void update() { + merge(find(), false); + save(); + } + + public History update(int cid) { + return update(cid, find()); + } + + public History update(int cid, List items) { + setCid(cid); + merge(items, true); + return save(); + } + + public History save() { + AppDatabase.get().getHistoryDao().insertOrUpdate(this); + return this; + } + + public History delete() { + AppDatabase.get().getHistoryDao().delete(VodConfig.getCid(), getKey()); + AppDatabase.get().getTrackDao().delete(getKey()); + return this; + } + + public List find() { + return AppDatabase.get().getHistoryDao().findByName(VodConfig.getCid(), getVodName()); + } + + public void findEpisode(List flags) { + if (!flags.isEmpty()) { + setVodFlag(flags.get(0).getFlag()); + if (!flags.get(0).getEpisodes().isEmpty()) { + setVodRemarks(flags.get(0).getEpisodes().get(0).getName()); + } + } + for (History item : find()) { + if (getPosition() > 0) break; + for (Flag flag : flags) { + Episode episode = flag.find(item.getVodRemarks(), true); + if (episode == null) continue; + setVodFlag(flag.getFlag()); + setPosition(item.getPosition()); + setVodRemarks(episode.getName()); + checkParam(item); + break; + } + } + } + + private static void startSync(List targets) { + for (History target : targets) { + List items = target.find(); + if (items.isEmpty()) { + target.update(VodConfig.getCid(), items); + continue; + } + for (History item : items) { + if (target.getCreateTime() > item.getCreateTime()) { + target.update(VodConfig.getCid(), items); + break; + } + } + } + } + + public static void sync(List targets) { + App.execute(() -> { + startSync(targets); + RefreshEvent.history(); + }); + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Hot.java b/app/src/main/java/com/fongmi/android/tv/bean/Hot.java new file mode 100644 index 00000000..01b2cac6 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Hot.java @@ -0,0 +1,44 @@ +package com.fongmi.android.tv.bean; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Setting; +import com.github.catvod.utils.Trans; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.List; + +public class Hot { + + @SerializedName("data") + private List data; + + private static Hot objectFrom(String str) { + return App.gson().fromJson(str, Hot.class); + } + + public static List get(String str) { + try { + List items = new ArrayList<>(); + for (Data item : objectFrom(str).getData()) items.add(item.getTitle()); + if (!items.isEmpty()) Setting.putHot(str); + return items; + } catch (Exception e) { + return new ArrayList<>(); + } + } + + public List getData() { + return data; + } + + public static class Data { + + @SerializedName("title") + private String title; + + public String getTitle() { + return Trans.s2t(title); + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Keep.java b/app/src/main/java/com/fongmi/android/tv/bean/Keep.java new file mode 100644 index 00000000..de3a7a36 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Keep.java @@ -0,0 +1,171 @@ +package com.fongmi.android.tv.bean; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.PrimaryKey; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.RefreshEvent; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +@Entity +public class Keep { + + @NonNull + @PrimaryKey + @SerializedName("key") + private String key; + @SerializedName("siteName") + private String siteName; + @SerializedName("vodName") + private String vodName; + @SerializedName("vodPic") + private String vodPic; + @SerializedName("createTime") + private long createTime; + @SerializedName("type") + private int type; + @SerializedName("cid") + private int cid; + + public static List arrayFrom(String str) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(str, listType); + return items == null ? Collections.emptyList() : items; + } + + @NonNull + public String getKey() { + return key; + } + + public void setKey(@NonNull String key) { + this.key = key; + } + + public String getSiteName() { + return siteName; + } + + public void setSiteName(String siteName) { + this.siteName = siteName; + } + + public String getVodName() { + return vodName; + } + + public void setVodName(String vodName) { + this.vodName = vodName; + } + + public String getVodPic() { + return vodPic; + } + + public void setVodPic(String vodPic) { + this.vodPic = vodPic; + } + + public long getCreateTime() { + return createTime; + } + + public void setCreateTime(long createTime) { + this.createTime = createTime; + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public int getCid() { + return cid; + } + + public void setCid(int cid) { + this.cid = cid; + } + + public String getSiteKey() { + return getKey().split(AppDatabase.SYMBOL)[0]; + } + + public String getVodId() { + return getKey().split(AppDatabase.SYMBOL)[1]; + } + + public static Keep find(String key) { + return find(VodConfig.getCid(), key); + } + + public static Keep find(int cid, String key) { + return AppDatabase.get().getKeepDao().find(cid, key); + } + + public static boolean exist(String key) { + return AppDatabase.get().getKeepDao().find(key) != null; + } + + public static void deleteAll() { + AppDatabase.get().getKeepDao().delete(); + } + + public static void delete(int cid) { + AppDatabase.get().getKeepDao().delete(cid); + } + + public static void delete(String key) { + AppDatabase.get().getKeepDao().delete(key); + } + + public static List getVod() { + return AppDatabase.get().getKeepDao().getVod(); + } + + public static List getLive() { + return AppDatabase.get().getKeepDao().getLive(); + } + + public void save(int cid) { + setCid(cid); + AppDatabase.get().getKeepDao().insertOrUpdate(this); + } + + public void save() { + AppDatabase.get().getKeepDao().insertOrUpdate(this); + } + + public Keep delete() { + AppDatabase.get().getKeepDao().delete(getCid(), getKey()); + return this; + } + + private static void startSync(List configs, List targets) { + for (Keep target : targets) { + for (Config config : configs) { + if (target.getCid() == config.getId()) { + target.save(Config.find(config).getId()); + } + } + } + } + + public static void sync(List configs, List targets) { + App.execute(() -> { + startSync(configs, targets); + RefreshEvent.keep(); + }); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Live.java b/app/src/main/java/com/fongmi/android/tv/bean/Live.java new file mode 100644 index 00000000..671a5fdb --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Live.java @@ -0,0 +1,365 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.PrimaryKey; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.loader.BaseLoader; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.gson.ExtAdapter; +import com.github.catvod.crawler.Spider; +import com.github.catvod.utils.Json; +import com.google.common.net.HttpHeaders; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +@Entity +public class Live { + + @NonNull + @PrimaryKey + @SerializedName("name") + private String name; + + @Ignore + @SerializedName("url") + private String url; + + @Ignore + @SerializedName("api") + private String api; + + @Ignore + @JsonAdapter(ExtAdapter.class) + @SerializedName("ext") + private String ext; + + @Ignore + @SerializedName("jar") + private String jar; + + @Ignore + @SerializedName("click") + private String click; + + @Ignore + @SerializedName("logo") + private String logo; + + @Ignore + @SerializedName("epg") + private String epg; + + @Ignore + @SerializedName("ua") + private String ua; + + @Ignore + @SerializedName("origin") + private String origin; + + @Ignore + @SerializedName("referer") + private String referer; + + @Ignore + @SerializedName("timeZone") + private String timeZone; + + @SerializedName("keep") + private String keep; + + @Ignore + @SerializedName("timeout") + private Integer timeout; + + @Ignore + @SerializedName("header") + private JsonElement header; + + @Ignore + @SerializedName("catchup") + private Catchup catchup; + + @Ignore + @SerializedName("core") + private Core core; + + @Ignore + @SerializedName("groups") + private List groups; + + @SerializedName("boot") + private boolean boot; + + @SerializedName("pass") + private boolean pass; + + @Ignore + private boolean activated; + + @Ignore + private int width; + + public static Live objectFrom(JsonElement element) { + return App.gson().fromJson(element, Live.class); + } + + public static List arrayFrom(String str) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(str, listType); + return items == null ? Collections.emptyList() : items; + } + + public static Live get(String name) { + Live live = new Live(); + live.setName(name); + return live; + } + + public Live() { + } + + public Live(@NonNull String name, String url) { + this.name = name; + this.url = url; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public void setName(@NonNull String name) { + this.name = name; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getApi() { + return TextUtils.isEmpty(api) ? "" : api; + } + + public void setApi(String api) { + this.api = api; + } + + public String getExt() { + return TextUtils.isEmpty(ext) ? "" : ext; + } + + public void setExt(String ext) { + this.ext = ext.trim(); + } + + public String getJar() { + return TextUtils.isEmpty(jar) ? "" : jar; + } + + public void setJar(String jar) { + this.jar = jar; + } + + public String getClick() { + return TextUtils.isEmpty(click) ? "" : click; + } + + public String getLogo() { + return TextUtils.isEmpty(logo) ? "" : logo; + } + + public String getEpg() { + return TextUtils.isEmpty(epg) ? "" : epg; + } + + public void setEpg(String epg) { + this.epg = epg; + } + + public String getUa() { + return TextUtils.isEmpty(ua) ? "" : ua; + } + + public String getOrigin() { + return TextUtils.isEmpty(origin) ? "" : origin; + } + + public String getReferer() { + return TextUtils.isEmpty(referer) ? "" : referer; + } + + public String getTimeZone() { + return TextUtils.isEmpty(timeZone) ? "" : timeZone; + } + + public String getKeep() { + return TextUtils.isEmpty(keep) ? "" : keep; + } + + public void setKeep(String keep) { + this.keep = keep; + } + + public long getTimeout() { + return timeout == null ? Constant.TIMEOUT_PLAY : TimeUnit.SECONDS.toMillis(Math.max(timeout, 1)); + } + + public JsonElement getHeader() { + return header; + } + + public Catchup getCatchup() { + return catchup == null ? new Catchup() : catchup; + } + + public Core getCore() { + return core == null ? new Core() : core; + } + + public List getGroups() { + return groups = groups == null ? new ArrayList<>() : groups; + } + + public boolean isBoot() { + return boot; + } + + public void setBoot(boolean boot) { + this.boot = boot; + } + + public boolean isPass() { + return pass; + } + + public void setPass(boolean pass) { + this.pass = pass; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public void setActivated(Live item) { + this.activated = item.equals(this); + } + + public int getWidth() { + return width; + } + + public void setWidth(int width) { + this.width = width; + } + + public String getEpgApi() { + for (String url : getEpg().split(",")) if (url.contains("{")) return url; + return getEpg(); + } + + public List getEpgXml() { + List items = new ArrayList<>(); + for (String epg : getEpg().split(",")) if (!epg.contains("{") && (epg.contains("xml") || epg.contains("gz"))) items.add(epg); + return items; + } + + public boolean isEmpty() { + return getName().isEmpty(); + } + + public Group find(Group item) { + for (Group group : getGroups()) if (group.getName().equals(item.getName())) return group; + getGroups().add(item); + return item; + } + + public int getBootIcon() { + return isBoot() ? R.drawable.ic_live_boot : R.drawable.ic_live_block; + } + + public int getPassIcon() { + return isPass() ? R.drawable.ic_live_block : R.drawable.ic_live_pass; + } + + public Live boot(boolean boot) { + setBoot(boot); + return this; + } + + public Live pass(boolean pass) { + getGroups().clear(); + setPass(pass); + return this; + } + + public Live keep(Channel channel) { + setKeep(channel.getGroup().getName() + AppDatabase.SYMBOL + channel.getName() + AppDatabase.SYMBOL + channel.getCurrent()); + return this; + } + + public Live sync() { + Live item = find(getName()); + if (item == null) return this; + setBoot(item.isBoot()); + setPass(item.isPass()); + setKeep(item.getKeep()); + return this; + } + + public Live recent() { + BaseLoader.get().setRecent(getName(), getApi(), getJar()); + return this; + } + + public Spider spider() { + return BaseLoader.get().getSpider(getName(), getApi(), getExt(), getJar()); + } + + public Map getHeaders() { + Map headers = Json.toMap(getHeader()); + if (!getUa().isEmpty()) headers.put(HttpHeaders.USER_AGENT, getUa()); + if (!getOrigin().isEmpty()) headers.put(HttpHeaders.ORIGIN, getOrigin()); + if (!getReferer().isEmpty()) headers.put(HttpHeaders.REFERER, getReferer()); + return headers; + } + + public static Live find(String name) { + return AppDatabase.get().getLiveDao().find(name); + } + + public void save() { + AppDatabase.get().getLiveDao().insertOrUpdate(this); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Live)) return false; + Live it = (Live) obj; + return getName().equals(it.getName()); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Page.java b/app/src/main/java/com/fongmi/android/tv/bean/Page.java new file mode 100644 index 00000000..37becd7e --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Page.java @@ -0,0 +1,40 @@ +package com.fongmi.android.tv.bean; + +import androidx.annotation.Nullable; + +public class Page { + + private final String vodId; + private final Style style; + private final int position; + + public static Page get(Vod vod, int position) { + return new Page(vod, position); + } + + private Page(Vod vod, int position) { + this.vodId = vod.getVodId(); + this.style = vod.getCate() != null ? vod.getCate().getStyle() : null; + this.position = position; + } + + public String getVodId() { + return vodId; + } + + public Style getStyle() { + return style; + } + + public int getPosition() { + return position; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof Page)) return false; + Page it = (Page) obj; + return getVodId().equals(it.getVodId()) && getPosition() == it.getPosition(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Parse.java b/app/src/main/java/com/fongmi/android/tv/bean/Parse.java new file mode 100644 index 00000000..10fa7757 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Parse.java @@ -0,0 +1,174 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Util; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Parse { + + @SerializedName("name") + private String name; + @SerializedName("type") + private Integer type; + @SerializedName("url") + private String url; + @SerializedName("ext") + private Ext ext; + + private boolean activated; + private String click; + + public static Parse objectFrom(JsonElement element) { + return App.gson().fromJson(element, Parse.class); + } + + public static Parse get(String name) { + Parse parse = new Parse(); + parse.setName(name); + return parse; + } + + public static Parse get(Integer type, String url) { + Parse parse = new Parse(); + parse.setType(type); + parse.setUrl(url); + return parse; + } + + public static Parse god() { + Parse parse = new Parse(); + parse.setName(ResUtil.getString(R.string.parse_god)); + parse.setType(4); + return parse; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public void setName(String name) { + this.name = name; + } + + public Integer getType() { + return type == null ? 0 : type; + } + + public void setType(Integer type) { + this.type = type; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : UrlUtil.convert(url); + } + + public void setUrl(String url) { + this.url = url; + } + + public Ext getExt() { + return ext = ext == null ? new Ext() : ext; + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public void setActivated(Parse item) { + this.activated = item.equals(this); + } + + public String getClick() { + return TextUtils.isEmpty(click) ? "" : click; + } + + public void setClick(String click) { + this.click = click; + } + + public Map getHeaders() { + return Json.toMap(getExt().getHeader()); + } + + public void setHeader(JsonElement header) { + if (getExt().getHeader() == null) getExt().setHeader(header); + } + + public boolean isEmpty() { + return getType() == 0 && getUrl().isEmpty(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Parse)) return false; + Parse it = (Parse) obj; + return getName().equals(it.getName()); + } + + public String extUrl() { + int index = getUrl().indexOf("?"); + if (getExt().isEmpty() || index == -1) return getUrl(); + return getUrl().substring(0, index + 1) + "cat_ext=" + Util.base64(getExt().toString(), Util.URL_SAFE) + "&" + getUrl().substring(index + 1); + } + + public HashMap mixMap() { + HashMap map = new HashMap<>(); + map.put("type", getType().toString()); + map.put("ext", getExt().toString()); + map.put("url", getUrl()); + return map; + } + + public static class Ext { + + @SerializedName("flag") + private List flag; + @SerializedName("header") + private JsonElement header; + + public void setFlag(List flag) { + this.flag = flag; + } + + public List getFlag() { + return flag == null ? Collections.emptyList() : flag; + } + + public JsonElement getHeader() { + return header; + } + + public void setHeader(JsonElement header) { + this.header = header; + } + + public boolean isEmpty() { + return flag == null && header == null; + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Part.java b/app/src/main/java/com/fongmi/android/tv/bean/Part.java new file mode 100644 index 00000000..0ebfb258 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Part.java @@ -0,0 +1,23 @@ +package com.fongmi.android.tv.bean; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Part { + + public static List get(String source) { + List items = new ArrayList<>(); + items.add(source.trim()); + if (source.contains(":")) { + for (String split : source.split(":")) items.add(split.trim().contains(" ") ? split.split(" ")[0].trim() : split.trim()); + } else if (source.contains("第") && source.contains("季")) { + for (String split : source.split("第")) if (!split.contains("季")) items.add(split.trim().contains(" ") ? split.split(" ")[0].trim() : split.trim()); + } else if (source.contains("(")) { + items.add(source.split("\\(")[0].trim()); + } else if (source.contains(" ")) { + items.addAll(Arrays.asList(source.split(" "))); + } + return items; + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Result.java b/app/src/main/java/com/fongmi/android/tv/bean/Result.java new file mode 100644 index 00000000..91c8ba48 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Result.java @@ -0,0 +1,348 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.gson.DanmakuAdapter; +import com.fongmi.android.tv.gson.FilterAdapter; +import com.fongmi.android.tv.gson.MsgAdapter; +import com.fongmi.android.tv.gson.UrlAdapter; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Trans; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; + +import org.json.JSONObject; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Path; +import org.simpleframework.xml.Root; +import org.simpleframework.xml.core.Persister; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +@Root(name = "rss", strict = false) +public class Result implements Parcelable { + + @Path("class") + @ElementList(entry = "ty", required = false, inline = true) + @SerializedName("class") + private List types; + + @Path("list") + @ElementList(entry = "video", required = false, inline = true) + @SerializedName("list") + private List list; + + @SerializedName("filters") + @JsonAdapter(FilterAdapter.class) + private LinkedHashMap> filters; + + @SerializedName("url") + @JsonAdapter(UrlAdapter.class) + private Url url; + + @SerializedName("msg") + @JsonAdapter(MsgAdapter.class) + private String msg; + + @SerializedName("danmaku") + @JsonAdapter(DanmakuAdapter.class) + private List danmaku; + + @SerializedName("subs") + private List subs; + @SerializedName("header") + private JsonElement header; + @SerializedName("playUrl") + private String playUrl; + @SerializedName("jxFrom") + private String jxFrom; + @SerializedName("flag") + private String flag; + @SerializedName("desc") + private String desc; + @SerializedName("format") + private String format; + @SerializedName("click") + private String click; + @SerializedName("key") + private String key; + @SerializedName("pagecount") + private Integer pagecount; + @SerializedName("parse") + private Integer parse; + @SerializedName("code") + private Integer code; + @SerializedName("jx") + private Integer jx; + @SerializedName("drm") + private Drm drm; + + public static Result objectFrom(String str) { + try { + return App.gson().fromJson(str, Result.class); + } catch (Exception e) { + return empty(); + } + } + + public static Result fromJson(String str) { + Result result = objectFrom(str); + return result == null ? empty() : result.trans(); + } + + public static Result fromXml(String str) { + try { + return new Persister().read(Result.class, str, false).trans(); + } catch (Exception e) { + return empty(); + } + } + + public static Result fromType(int type, String str) { + return type == 0 ? fromXml(str) : fromJson(str); + } + + public static Result fromObject(JSONObject object) { + return object == null ? empty() : objectFrom(object.toString()); + } + + public static Result empty() { + return new Result(); + } + + public static Result error(String msg) { + Result result = new Result(); + result.setParse(0); + result.setMsg(msg); + return result; + } + + public static Result folder(Vod item) { + Result result = new Result(); + Class type = new Class(); + type.setTypeFlag("1"); + type.setTypeId(item.getVodId()); + type.setTypeName(item.getVodName()); + result.setTypes(Arrays.asList(type)); + return result; + } + + public static Result type(String json) { + Result result = new Result(); + result.setTypes(Arrays.asList(Class.objectFrom(json))); + return result.trans(); + } + + public static Result list(List items) { + Result result = new Result(); + result.setList(items); + return result; + } + + public static Result vod(Vod item) { + return list(Arrays.asList(item)); + } + + public Result() { + } + + public List getTypes() { + return types == null ? Collections.emptyList() : types; + } + + public void setTypes(List types) { + if (!types.isEmpty()) this.types = types; + } + + public List getList() { + return list == null ? Collections.emptyList() : list; + } + + public void setList(List list) { + this.list = list; + } + + public LinkedHashMap> getFilters() { + return filters == null ? new LinkedHashMap<>() : filters; + } + + public Url getUrl() { + return url == null ? Url.create() : url; + } + + public void setUrl(Url url) { + this.url = url; + } + + public void setUrl(String url) { + this.url = getUrl().replace(url); + } + + public String getMsg() { + return TextUtils.isEmpty(msg) || getCode() != 0 ? "" : msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public List getSubs() { + return subs == null ? new ArrayList<>() : new ArrayList<>(subs); + } + + public JsonElement getHeader() { + return header; + } + + public void setHeader(JsonElement header) { + if (getHeader() == null) this.header = header; + } + + public String getPlayUrl() { + return TextUtils.isEmpty(playUrl) ? "" : playUrl; + } + + public void setPlayUrl(String playUrl) { + this.playUrl = playUrl; + } + + public String getJxFrom() { + return TextUtils.isEmpty(jxFrom) ? "" : jxFrom; + } + + public String getFlag() { + return TextUtils.isEmpty(flag) ? "" : flag; + } + + public void setFlag(String flag) { + this.flag = flag; + } + + public String getDesc() { + return TextUtils.isEmpty(desc) ? "" : desc; + } + + public List getDanmaku() { + return !Setting.isDanmakuLoad() || danmaku == null ? new ArrayList<>() : new ArrayList<>(danmaku); + } + + public String getFormat() { + return format; + } + + public String getClick() { + return TextUtils.isEmpty(click) ? "" : click; + } + + public void setClick(String click) { + this.click = click; + } + + public String getKey() { + return TextUtils.isEmpty(key) ? "" : key; + } + + public void setKey(String key) { + this.key = key; + } + + public Integer getPageCount() { + return pagecount == null ? 0 : pagecount; + } + + public Integer getParse() { + return parse == null ? 0 : parse; + } + + public void setParse(Integer parse) { + this.parse = parse; + } + + public Integer getCode() { + return code == null ? 0 : code; + } + + public Integer getJx() { + return jx == null ? 0 : jx; + } + + public Drm getDrm() { + return drm; + } + + public boolean hasMsg() { + return !getMsg().isEmpty(); + } + + public String getRealUrl() { + return getPlayUrl() + getUrl().v(); + } + + public Map getHeaders() { + return Json.toMap(getHeader()); + } + + public Style getStyle(Style style) { + return getList().isEmpty() ? Style.rect() : getList().get(0).getStyle(style); + } + + public Result clear() { + getList().clear(); + return this; + } + + public Result trans() { + if (Trans.pass()) return this; + for (Class type : getTypes()) type.trans(); + for (Vod vod : getList()) vod.trans(); + for (Sub sub : getSubs()) sub.trans(); + return this; + } + + @NonNull + @Override + public String toString() { + return App.gson().toJson(this); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeList(this.types); + dest.writeTypedList(this.list); + } + + protected Result(Parcel in) { + this.types = new ArrayList<>(); + in.readList(this.types, Class.class.getClassLoader()); + this.list = in.createTypedArrayList(Vod.CREATOR); + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Result createFromParcel(Parcel source) { + return new Result(source); + } + + @Override + public Result[] newArray(int size) { + return new Result[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Rule.java b/app/src/main/java/com/fongmi/android/tv/bean/Rule.java new file mode 100644 index 00000000..d05ea835 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Rule.java @@ -0,0 +1,74 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import com.fongmi.android.tv.App; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.List; + +public class Rule { + + @SerializedName("name") + private String name; + @SerializedName("hosts") + private List hosts; + @SerializedName("regex") + private List regex; + @SerializedName("script") + private List script; + @SerializedName("exclude") + private List exclude; + + public static Rule create(String name) { + return new Rule(name); + } + + public static Rule empty() { + return new Rule(""); + } + + public Rule(String name) { + this.name = name; + } + + public static List arrayFrom(JsonElement element) { + Type listType = new TypeToken>() {}.getType(); + List items = App.gson().fromJson(element, listType); + return items == null ? Collections.emptyList() : items; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public List getHosts() { + return hosts == null ? Collections.emptyList() : hosts; + } + + public List getRegex() { + return regex == null ? Collections.emptyList() : regex; + } + + public List getScript() { + return script == null ? Collections.emptyList() : script; + } + + public List getExclude() { + return exclude == null ? Collections.emptyList() : exclude; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof Rule)) return false; + Rule it = (Rule) obj; + return getName().equals(it.getName()); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Site.java b/app/src/main/java/com/fongmi/android/tv/bean/Site.java new file mode 100644 index 00000000..737a8079 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Site.java @@ -0,0 +1,385 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.room.Entity; +import androidx.room.Ignore; +import androidx.room.PrimaryKey; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.api.loader.BaseLoader; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.gson.ExtAdapter; +import com.github.catvod.crawler.Spider; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Trans; +import com.google.gson.JsonElement; +import com.google.gson.annotations.JsonAdapter; +import com.google.gson.annotations.SerializedName; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import okhttp3.Headers; + +@Entity +public class Site implements Parcelable { + + @NonNull + @PrimaryKey + @SerializedName("key") + private String key; + + @Ignore + @SerializedName("name") + private String name; + + @Ignore + @SerializedName("api") + private String api; + + @Ignore + @JsonAdapter(ExtAdapter.class) + @SerializedName("ext") + private String ext; + + @Ignore + @SerializedName("jar") + private String jar; + + @Ignore + @SerializedName("click") + private String click; + + @Ignore + @SerializedName("playUrl") + private String playUrl; + + @Ignore + @SerializedName("type") + private Integer type; + + @Ignore + @SerializedName("hide") + private Integer hide; + + @Ignore + @SerializedName("indexs") + private Integer indexs; + + @Ignore + @SerializedName("timeout") + private Integer timeout; + + @SerializedName("searchable") + private Integer searchable; + + @SerializedName("changeable") + private Integer changeable; + + @Ignore + @SerializedName("quickSearch") + private Integer quickSearch; + + @Ignore + @SerializedName("categories") + private List categories; + + @Ignore + @SerializedName("header") + private JsonElement header; + + @Ignore + @SerializedName("style") + private Style style; + + @Ignore + private boolean activated; + + public static Site objectFrom(JsonElement element) { + try { + return App.gson().fromJson(element, Site.class); + } catch (Exception e) { + return new Site(); + } + } + + public static Site get(String key) { + Site site = new Site(); + site.setKey(key); + return site; + } + + public static Site get(String key, String name) { + Site site = new Site(); + site.setKey(key); + site.setName(name); + return site; + } + + public Site() { + } + + public String getKey() { + return TextUtils.isEmpty(key) ? "" : key; + } + + public void setKey(@NonNull String key) { + this.key = key; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public void setName(String name) { + this.name = name; + } + + public String getApi() { + return TextUtils.isEmpty(api) ? "" : api; + } + + public void setApi(String api) { + this.api = api; + } + + public String getExt() { + return TextUtils.isEmpty(ext) ? "" : ext; + } + + public void setExt(String ext) { + this.ext = ext.trim(); + } + + public String getJar() { + return TextUtils.isEmpty(jar) ? "" : jar; + } + + public void setJar(String jar) { + this.jar = jar; + } + + public String getClick() { + return TextUtils.isEmpty(click) ? "" : click; + } + + public String getPlayUrl() { + return TextUtils.isEmpty(playUrl) ? "" : playUrl; + } + + public Integer getType() { + return type == null ? 0 : type; + } + + public Integer getHide() { + return hide == null ? 0 : hide; + } + + public Integer getIndexs() { + return indexs == null ? 0 : indexs; + } + + public long getTimeout() { + return timeout == null ? Constant.TIMEOUT_PLAY : TimeUnit.SECONDS.toMillis(Math.max(timeout, 1)); + } + + public Integer getSearchable() { + return searchable == null ? 1 : searchable; + } + + public void setSearchable(Integer searchable) { + this.searchable = searchable; + } + + public Integer getChangeable() { + return changeable == null ? 1 : changeable; + } + + public void setChangeable(Integer changeable) { + this.changeable = changeable; + } + + public Integer getQuickSearch() { + return quickSearch == null ? 1 : quickSearch; + } + + public List getCategories() { + return categories == null ? Collections.emptyList() : categories; + } + + public void setCategories(List categories) { + this.categories = categories; + } + + public JsonElement getHeader() { + return header; + } + + public Style getStyle() { + return style; + } + + public Style getStyle(Style style) { + return getStyle() != null ? getStyle() : style != null ? style : Style.rect(); + } + + public boolean isActivated() { + return activated; + } + + public void setActivated(boolean activated) { + this.activated = activated; + } + + public void setActivated(Site item) { + this.activated = item.equals(this); + } + + public boolean isHide() { + return getHide() == 1; + } + + public boolean isIndex() { + return getIndexs() == 1; + } + + public boolean isSearchable() { + return getSearchable() == 1; + } + + public Site setSearchable(boolean searchable) { + if (getSearchable() != 0) setSearchable(searchable ? 1 : 2); + return this; + } + + public boolean isChangeable() { + return getChangeable() == 1; + } + + public Site setChangeable(boolean changeable) { + if (getChangeable() != 0) setChangeable(changeable ? 1 : 2); + return this; + } + + public boolean isQuickSearch() { + return getQuickSearch() == 1; + } + + public boolean isEmpty() { + return getKey().isEmpty() && getName().isEmpty(); + } + + public Headers getHeaders() { + return Headers.of(Json.toMap(getHeader())); + } + + public Site fetchExt() { + if (!getExt().startsWith("http")) return this; + String extend = OkHttp.string(getExt()); + if (!extend.isEmpty()) setExt(extend); + return this; + } + + public Site trans() { + if (Trans.pass()) return this; + List categories = new ArrayList<>(); + for (String cate : getCategories()) categories.add(Trans.s2t(cate)); + setCategories(categories); + return this; + } + + public Site sync() { + Site item = find(getKey()); + if (item == null) return this; + if (getChangeable() != 0) setChangeable(Math.max(1, item.getChangeable())); + if (getSearchable() != 0) setSearchable(Math.max(1, item.getSearchable())); + return this; + } + + public Site recent() { + BaseLoader.get().setRecent(getKey(), getApi(), getJar()); + return this; + } + + public Spider spider() { + return BaseLoader.get().getSpider(getKey(), getApi(), getExt(), getJar()); + } + + public static Site find(String key) { + return AppDatabase.get().getSiteDao().find(key); + } + + public void save() { + AppDatabase.get().getSiteDao().insertOrUpdate(this); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Site)) return false; + Site it = (Site) obj; + return getKey().equals(it.getKey()); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.key); + dest.writeString(this.name); + dest.writeString(this.api); + dest.writeString(this.ext); + dest.writeString(this.jar); + dest.writeString(this.click); + dest.writeString(this.playUrl); + dest.writeValue(this.type); + dest.writeValue(this.indexs); + dest.writeValue(this.timeout); + dest.writeValue(this.searchable); + dest.writeValue(this.changeable); + dest.writeStringList(this.categories); + dest.writeParcelable(this.style, flags); + dest.writeByte(this.activated ? (byte) 1 : (byte) 0); + } + + protected Site(Parcel in) { + this.key = in.readString(); + this.name = in.readString(); + this.api = in.readString(); + this.ext = in.readString(); + this.jar = in.readString(); + this.click = in.readString(); + this.playUrl = in.readString(); + this.type = (Integer) in.readValue(Integer.class.getClassLoader()); + this.indexs = (Integer) in.readValue(Integer.class.getClassLoader()); + this.timeout = (Integer) in.readValue(Integer.class.getClassLoader()); + this.searchable = (Integer) in.readValue(Integer.class.getClassLoader()); + this.changeable = (Integer) in.readValue(Integer.class.getClassLoader()); + this.categories = in.createStringArrayList(); + this.style = in.readParcelable(Style.class.getClassLoader()); + this.activated = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator<>() { + @Override + public Site createFromParcel(Parcel source) { + return new Site(source); + } + + @Override + public Site[] newArray(int size) { + return new Site[size]; + } + }; +} diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Style.java b/app/src/main/java/com/fongmi/android/tv/bean/Style.java new file mode 100644 index 00000000..3e1161d4 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Style.java @@ -0,0 +1,115 @@ +package com.fongmi.android.tv.bean; + +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import com.fongmi.android.tv.ui.base.ViewType; +import com.google.gson.annotations.SerializedName; + +public class Style implements Parcelable { + + @SerializedName("type") + private String type; + @SerializedName("ratio") + private float ratio; + + public static Style rect() { + return new Style("rect", 0.75f); + } + + public static Style list() { + return new Style("list"); + } + + public static Style get(int land, int circle, float ratio) { + if (land == 1) return new Style("rect", ratio == 0 ? 1.33f : ratio); + if (circle == 1) return new Style("oval", ratio == 0 ? 1.0f : ratio); + return null; + } + + public Style() { + } + + public Style(String type) { + this.type = type; + } + + public Style(String type, float ratio) { + this.type = type; + this.ratio = ratio; + } + + public String getType() { + return TextUtils.isEmpty(type) ? "rect" : type; + } + + public float getRatio() { + return ratio <= 0 ? (isOval() ? 1.0f : 0.75f) : Math.min(4, ratio); + } + + public boolean isRect() { + return "rect".equals(getType()); + } + + public boolean isOval() { + return "oval".equals(getType()); + } + + public boolean isList() { + return "list".equals(getType()); + } + + public boolean isLand() { + return isRect() && getRatio() > 1.0f; + } + + public int getViewType() { + switch (getType()) { + case "oval": + return ViewType.OVAL; + case "list": + return ViewType.LIST; + default: + return ViewType.RECT; + } + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) return true; + if (!(obj instanceof Style)) return false; + Style it = (Style) obj; + return getType().equals(it.getType()) && getRatio() == it.getRatio(); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.type); + dest.writeFloat(this.ratio); + } + + protected Style(Parcel in) { + this.type = in.readString(); + this.ratio = in.readFloat(); + } + + public static final Creator + + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 00000000..19e4ad71 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/AndroidManifest.xml b/app/src/mobile/AndroidManifest.xml new file mode 100644 index 00000000..ad60c4e4 --- /dev/null +++ b/app/src/mobile/AndroidManifest.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/Product.java b/app/src/mobile/java/com/fongmi/android/tv/Product.java new file mode 100644 index 00000000..227fce5f --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/Product.java @@ -0,0 +1,49 @@ +package com.fongmi.android.tv; + +import android.content.Context; + +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.utils.ResUtil; + +public class Product { + + public static int getDeviceType() { + return 1; + } + + public static int getColumn(Context context) { + int count = ResUtil.isLand(context) ? 7 : 5; + count = count + (ResUtil.isPad() ? 1 : 0); + return Math.abs(Setting.getSize() - count); + } + + public static int getColumn(Context context, Style style) { + return style.isLand() ? getColumn(context) - 1 : getColumn(context); + } + + public static int[] getSpec(Context context) { + return getSpec(context, Style.rect()); + } + + public static int[] getSpec(Context context, Style style) { + int column = getColumn(context, style); + int space = ResUtil.dp2px(32) + ResUtil.dp2px(16 * (column - 1)); + if (style.isOval()) space += ResUtil.dp2px(column * 16); + return getSpec(context, space, column, style); + } + + public static int[] getSpec(Context context, int space, int column) { + return getSpec(context, space, column, Style.rect()); + } + + private static int[] getSpec(Context context, int space, int column, Style style) { + int base = ResUtil.getScreenWidth(context) - space; + int width = base / column; + int height = (int) (width / style.getRatio()); + return new int[]{width, height}; + } + + public static int getEms() { + return Math.min(ResUtil.getScreenWidth() / ResUtil.sp2px(20), 25); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/Updater.java b/app/src/mobile/java/com/fongmi/android/tv/Updater.java new file mode 100644 index 00000000..4f80248d --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/Updater.java @@ -0,0 +1,139 @@ +package com.fongmi.android.tv; + +import android.app.Activity; +import android.content.DialogInterface; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.databinding.DialogUpdateBinding; +import com.fongmi.android.tv.utils.Download; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Github; +import com.github.catvod.utils.Path; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.json.JSONObject; + +import java.io.File; +import java.util.Locale; + +public class Updater implements Download.Callback { + + private DialogUpdateBinding binding; + private final Download download; + private AlertDialog dialog; + private boolean dev; + + private File getFile() { + return Path.cache("update.apk"); + } + + private String getJson() { + return Github.getJson(dev, BuildConfig.FLAVOR_mode); + } + + private String getApk() { + return Github.getApk(dev, BuildConfig.FLAVOR_mode + "-" + BuildConfig.FLAVOR_abi); + } + + public static Updater create() { + return new Updater(); + } + + public Updater() { + this.download = Download.create(getApk(), getFile(), this); + } + + public Updater force() { + Notify.show(R.string.update_check); + Setting.putUpdate(true); + return this; + } + + public Updater release() { + this.dev = false; + return this; + } + + public Updater dev() { + this.dev = true; + return this; + } + + private Updater check() { + dismiss(); + return this; + } + + public void start(Activity activity) { + App.execute(() -> doInBackground(activity)); + } + + private boolean need(int code, String name) { + return Setting.getUpdate() && (dev ? !name.equals(BuildConfig.VERSION_NAME) && code >= BuildConfig.VERSION_CODE : code > BuildConfig.VERSION_CODE); + } + + private void doInBackground(Activity activity) { + try { + JSONObject object = new JSONObject(OkHttp.string(getJson())); + String name = object.optString("name"); + String desc = object.optString("desc"); + int code = object.optInt("code"); + if (need(code, name)) App.post(() -> show(activity, name, desc)); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void show(Activity activity, String version, String desc) { + binding = DialogUpdateBinding.inflate(LayoutInflater.from(activity)); + check().create(activity, ResUtil.getString(R.string.update_version, version)).show(); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(this::confirm); + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(this::cancel); + binding.desc.setText(desc); + } + + private AlertDialog create(Activity activity, String title) { + return dialog = new MaterialAlertDialogBuilder(activity).setTitle(title).setView(binding.getRoot()).setPositiveButton(R.string.update_confirm, null).setNegativeButton(R.string.dialog_negative, null).setCancelable(false).create(); + } + + private void cancel(View view) { + Setting.putUpdate(false); + download.cancel(); + dialog.dismiss(); + } + + private void confirm(View view) { + view.setEnabled(false); + download.start(); + } + + private void dismiss() { + try { + if (dialog != null) dialog.dismiss(); + } catch (Exception ignored) { + } + } + + @Override + public void progress(int progress) { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(String.format(Locale.getDefault(), "%1$d%%", progress)); + } + + @Override + public void error(String msg) { + Notify.show(msg); + dismiss(); + } + + @Override + public void success(File file) { + FileUtil.openFile(file); + dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/bean/CastVideo.java b/app/src/mobile/java/com/fongmi/android/tv/bean/CastVideo.java new file mode 100644 index 00000000..fec34c55 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/bean/CastVideo.java @@ -0,0 +1,42 @@ +package com.fongmi.android.tv.bean; + +import androidx.media3.common.C; + +import com.fongmi.android.tv.server.Server; +import com.github.catvod.utils.Path; +import com.github.catvod.utils.Util; + +public class CastVideo { + + private final long position; + private final String name; + private final String url; + + public static CastVideo get(String name, String url) { + return new CastVideo(name, url, C.TIME_UNSET); + } + + public static CastVideo get(String name, String url, long position) { + return new CastVideo(name, url, position); + } + + private CastVideo(String name, String url, long position) { + if (url.startsWith("file")) url = Server.get().getAddress() + "/" + url.replace(Path.rootPath(), "").replace("://", ""); + if (url.contains("127.0.0.1")) url = url.replace("127.0.0.1", Util.getIp()); + this.position = position; + this.name = name; + this.url = url; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public long getPosition() { + return position; + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/receiver/ShortcutReceiver.java b/app/src/mobile/java/com/fongmi/android/tv/receiver/ShortcutReceiver.java new file mode 100644 index 00000000..50c0d237 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/receiver/ShortcutReceiver.java @@ -0,0 +1,18 @@ +package com.fongmi.android.tv.receiver; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.Notify; + +public class ShortcutReceiver extends BroadcastReceiver { + + public static final String ACTION = ShortcutReceiver.class.getSimpleName(); + + @Override + public void onReceive(Context context, Intent intent) { + Notify.show(R.string.shortcut); + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/CollectActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/CollectActivity.java new file mode 100644 index 00000000..c87bd158 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/CollectActivity.java @@ -0,0 +1,312 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextUtils; +import android.view.View; +import android.view.inputmethod.EditorInfo; + +import androidx.annotation.NonNull; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Collect; +import com.fongmi.android.tv.bean.Hot; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Suggest; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.ActivityCollectBinding; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.adapter.CollectAdapter; +import com.fongmi.android.tv.ui.adapter.RecordAdapter; +import com.fongmi.android.tv.ui.adapter.SearchAdapter; +import com.fongmi.android.tv.ui.adapter.VodAdapter; +import com.fongmi.android.tv.ui.adapter.WordAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.custom.CustomScroller; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.utils.PauseExecutor; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Util; +import com.github.catvod.net.OkHttp; +import com.google.android.flexbox.FlexDirection; +import com.google.android.flexbox.FlexboxLayoutManager; + +import java.io.IOException; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import okhttp3.Call; +import okhttp3.Response; + +public class CollectActivity extends BaseActivity implements CustomScroller.Callback, SiteCallback, WordAdapter.OnClickListener, RecordAdapter.OnClickListener, CollectAdapter.OnClickListener, VodAdapter.OnClickListener { + + private ActivityCollectBinding mBinding; + private CollectAdapter mCollectAdapter; + private SearchAdapter mSearchAdapter; + private RecordAdapter mRecordAdapter; + private WordAdapter mWordAdapter; + private CustomScroller mScroller; + private SiteViewModel mViewModel; + private PauseExecutor mExecutor; + private List mSites; + + public static void start(Activity activity) { + start(activity, ""); + } + + public static void start(Activity activity, String keyword) { + Intent intent = new Intent(activity, CollectActivity.class); + intent.putExtra("keyword", keyword); + activity.startActivity(intent); + } + + private String getKeyword() { + return getIntent().getStringExtra("keyword"); + } + + private boolean empty() { + return mBinding.keyword.getText().toString().trim().isEmpty(); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityCollectBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView(Bundle savedInstanceState) { + mScroller = new CustomScroller(this); + mSites = new ArrayList<>(); + setRecyclerView(); + setViewModel(); + checkKeyword(); + setViewType(); + setSite(); + getHot(); + search(); + } + + @Override + protected void initEvent() { + mBinding.site.setOnClickListener(this::onSite); + mBinding.view.setOnClickListener(this::toggleView); + mBinding.keyword.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) search(); + return true; + }); + mBinding.keyword.addTextChangedListener(new CustomTextListener() { + @Override + public void afterTextChanged(Editable s) { + if (s.toString().isEmpty()) getHot(); + else getSuggest(s.toString()); + } + }); + } + + private void setRecyclerView() { + mBinding.collect.setHasFixedSize(true); + mBinding.collect.setItemAnimator(null); + mBinding.collect.setAdapter(mCollectAdapter = new CollectAdapter(this)); + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.addOnScrollListener(mScroller); + mBinding.recycler.setAdapter(mSearchAdapter = new SearchAdapter(this)); + mBinding.wordRecycler.setHasFixedSize(false); + mBinding.wordRecycler.setAdapter(mWordAdapter = new WordAdapter(this)); + mBinding.wordRecycler.setLayoutManager(new FlexboxLayoutManager(this, FlexDirection.ROW)); + mBinding.recordRecycler.setHasFixedSize(false); + mBinding.recordRecycler.setAdapter(mRecordAdapter = new RecordAdapter(this)); + mBinding.recordRecycler.setLayoutManager(new FlexboxLayoutManager(this, FlexDirection.ROW)); + } + + private void setViewType() { + setViewType(Setting.getViewType(ViewType.GRID)); + } + + private void setViewType(int viewType) { + int count = Product.getColumn(this) - 1; + mSearchAdapter.setViewType(viewType, count); + mSearchAdapter.setSize(Product.getSpec(this, ResUtil.dp2px(128 + (count) * 16), count)); + ((GridLayoutManager) mBinding.recycler.getLayoutManager()).setSpanCount(mSearchAdapter.isGrid() ? count : 1); + mBinding.view.setImageResource(mSearchAdapter.isGrid() ? R.drawable.ic_action_list : R.drawable.ic_action_grid); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.search.observe(this, result -> { + if (mCollectAdapter.getPosition() == 0) mSearchAdapter.addAll(result.getList()); + mCollectAdapter.add(Collect.create(result.getList())); + mCollectAdapter.add(result.getList()); + }); + mViewModel.result.observe(this, result -> { + boolean same = !result.getList().isEmpty() && mCollectAdapter.getActivated().getSite().equals(result.getList().get(0).getSite()); + if (same) mCollectAdapter.getActivated().getList().addAll(result.getList()); + if (same) mSearchAdapter.addAll(result.getList()); + mScroller.endLoading(result); + }); + } + + private void checkKeyword() { + if (TextUtils.isEmpty(getKeyword())) Util.showKeyboard(mBinding.keyword); + else setKeyword(getKeyword()); + } + + private void setKeyword(String text) { + mBinding.keyword.setText(text); + mBinding.keyword.setSelection(text.length()); + } + + private void setSite() { + for (Site site : VodConfig.get().getSites()) if (site.isSearchable()) mSites.add(site); + Site home = VodConfig.get().getHome(); + if (!mSites.contains(home)) return; + mSites.remove(home); + mSites.add(0, home); + } + + private void search() { + if (empty()) return; + mSearchAdapter.clear(); + mCollectAdapter.clear(); + Util.hideKeyboard(mBinding.keyword); + mBinding.site.setVisibility(View.GONE); + mBinding.agent.setVisibility(View.GONE); + mBinding.view.setVisibility(View.VISIBLE); + mBinding.result.setVisibility(View.VISIBLE); + if (mExecutor != null) mExecutor.shutdownNow(); + mExecutor = new PauseExecutor(20); + String keyword = mBinding.keyword.getText().toString().trim(); + for (Site site : mSites) mExecutor.execute(() -> search(site, keyword)); + App.post(() -> mRecordAdapter.add(keyword), 250); + } + + private void search(Site site, String keyword) { + try { + mViewModel.searchContent(site, keyword, false); + } catch (Throwable ignored) { + } + } + + private void getHot() { + mBinding.word.setText(R.string.search_hot); + mWordAdapter.addAll(Hot.get(Setting.getHot())); + } + + private void getSuggest(String text) { + mBinding.word.setText(R.string.search_suggest); + OkHttp.newCall("https://suggest.video.iqiyi.com/?if=mobile&key=" + URLEncoder.encode(text)).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (mBinding.keyword.getText().toString().trim().isEmpty()) return; + List items = Suggest.get(response.body().string()); + App.post(() -> mWordAdapter.addAll(items)); + } + }); + } + + private void onSite(View view) { + Util.hideKeyboard(mBinding.keyword); + App.post(() -> SiteDialog.create(this).search().show(), 50); + } + + private void toggleView(View view) { + setViewType(mSearchAdapter.isGrid() ? ViewType.LIST : ViewType.GRID); + } + + private void showAgent() { + mScroller.reset(); + mSearchAdapter.clear(); + mCollectAdapter.clear(); + mBinding.view.setVisibility(View.GONE); + mBinding.result.setVisibility(View.GONE); + mBinding.site.setVisibility(View.VISIBLE); + mBinding.agent.setVisibility(View.VISIBLE); + if (mExecutor != null) mExecutor.shutdownNow(); + } + + @Override + public void setSite(Site item) { + } + + @Override + public void onChanged() { + mSites.clear(); + setSite(); + } + + @Override + public void onItemClick(String text) { + setKeyword(text); + search(); + } + + @Override + public void onDataChanged(int size) { + mBinding.record.setVisibility(size == 0 ? View.GONE : View.VISIBLE); + mBinding.recordRecycler.setVisibility(size == 0 ? View.GONE : View.VISIBLE); + App.post(() -> mBinding.recordRecycler.requestLayout(), 250); + } + + @Override + public void onItemClick(int position, Collect item) { + mBinding.recycler.scrollToPosition(0); + mCollectAdapter.setActivated(position); + mSearchAdapter.setAll(item.getList()); + mScroller.setPage(item.getPage()); + } + + @Override + public void onItemClick(Vod item) { + if (item.isFolder()) FolderActivity.start(this, item.getSiteKey(), Result.folder(item)); + else VideoActivity.collect(this, item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public boolean onLongClick(Vod item) { + return false; + } + + @Override + public void onLoadMore(String page) { + Collect activated = mCollectAdapter.getActivated(); + if ("all".equals(activated.getSite().getKey())) return; + mViewModel.searchContent(activated.getSite(), mBinding.keyword.getText().toString(), page); + activated.setPage(Integer.parseInt(page)); + mScroller.setLoading(true); + } + + @Override + protected void onResume() { + super.onResume(); + if (mExecutor != null) mExecutor.resume(); + } + + @Override + protected void onPause() { + super.onPause(); + if (mExecutor != null) mExecutor.pause(); + } + + @Override + public void onBackPressed() { + if (isVisible(mBinding.result)) { + showAgent(); + } else { + super.onBackPressed(); + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/FileActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/FileActivity.java new file mode 100644 index 00000000..48103fb0 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/FileActivity.java @@ -0,0 +1,75 @@ +package com.fongmi.android.tv.ui.activity; + +import android.Manifest; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; + +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.ActivityFileBinding; +import com.fongmi.android.tv.ui.adapter.FileAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.github.catvod.utils.Path; +import com.permissionx.guolindev.PermissionX; + +import java.io.File; + +public class FileActivity extends BaseActivity implements FileAdapter.OnClickListener { + + private ActivityFileBinding mBinding; + private FileAdapter mAdapter; + private File dir; + + private boolean isRoot() { + return Path.root().equals(dir); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityFileBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView(Bundle savedInstanceState) { + setRecyclerView(); + checkPermission(); + } + + private void checkPermission() { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> { + if (allGranted) update(Path.root()); + else finish(); + }); + } + + private void setRecyclerView() { + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.setAdapter(mAdapter = new FileAdapter(this)); + } + + private void update(File dir) { + mBinding.recycler.scrollToPosition(0); + mAdapter.addAll(Path.list(this.dir = dir)); + mBinding.progressLayout.showContent(true, mAdapter.getItemCount()); + } + + @Override + public void onItemClick(File file) { + if (file.isDirectory()) { + update(file); + } else { + setResult(RESULT_OK, new Intent().setData(Uri.fromFile(file))); + finish(); + } + } + + @Override + public void onBackPressed() { + if (isRoot()) { + super.onBackPressed(); + } else { + update(dir.getParentFile()); + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/FolderActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/FolderActivity.java new file mode 100644 index 00000000..93586481 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/FolderActivity.java @@ -0,0 +1,59 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Class; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.databinding.ActivityFolderBinding; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.fragment.TypeFragment; + +import java.util.HashMap; + +public class FolderActivity extends BaseActivity { + + private ActivityFolderBinding mBinding; + + public static void start(Activity activity, String key, Result result) { + if (result == null || result.getTypes().isEmpty()) return; + Intent intent = new Intent(activity, FolderActivity.class); + intent.putExtra("key", key); + intent.putExtra("result", result); + activity.startActivity(intent); + } + + private String getKey() { + return getIntent().getStringExtra("key"); + } + + private Result getResult() { + return getIntent().getParcelableExtra("result"); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityFolderBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView(Bundle savedInstanceState) { + Result result = getResult(); + Class type = result.getTypes().get(0); + mBinding.text.setText(type.getTypeName()); + getSupportFragmentManager().beginTransaction().replace(R.id.container, TypeFragment.newInstance(getKey(), type.getTypeId(), type.getStyle(), new HashMap<>(), "1".equals(type.getTypeFlag())), "0").commitAllowingStateLoss(); + } + + private TypeFragment getFragment() { + return (TypeFragment) getSupportFragmentManager().findFragmentByTag("0"); + } + + @Override + public void onBackPressed() { + if (getFragment().canBack()) super.onBackPressed(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java new file mode 100644 index 00000000..bbe7a87f --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HistoryActivity.java @@ -0,0 +1,107 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.databinding.ActivityHistoryBinding; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.ui.adapter.HistoryAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.dialog.SyncDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class HistoryActivity extends BaseActivity implements HistoryAdapter.OnClickListener { + + private ActivityHistoryBinding mBinding; + private HistoryAdapter mAdapter; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, HistoryActivity.class)); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityHistoryBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView(Bundle savedInstanceState) { + setRecyclerView(); + getHistory(); + } + + @Override + protected void initEvent() { + mBinding.back.setOnClickListener(v -> finish()); + mBinding.sync.setOnClickListener(this::onSync); + mBinding.delete.setOnClickListener(this::onDelete); + } + + private void setRecyclerView() { + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.getItemAnimator().setChangeDuration(0); + mBinding.recycler.setLayoutManager(new GridLayoutManager(this, Product.getColumn(this))); + mBinding.recycler.setAdapter(mAdapter = new HistoryAdapter(this)); + mAdapter.setSize(Product.getSpec(getActivity())); + } + + private void getHistory() { + mAdapter.addAll(History.get()); + mBinding.delete.setVisibility(mAdapter.getItemCount() > 0 ? View.VISIBLE : View.GONE); + } + + private void onSync(View view) { + SyncDialog.create().history().show(this); + } + + private void onDelete(View view) { + if (mAdapter.isDelete()) { + new MaterialAlertDialogBuilder(this).setTitle(R.string.dialog_delete_record).setMessage(R.string.dialog_delete_history).setNegativeButton(R.string.dialog_negative, null).setPositiveButton(R.string.dialog_positive, (dialog, which) -> mAdapter.clear()).show(); + } else if (mAdapter.getItemCount() > 0) { + mAdapter.setDelete(true); + } else { + mBinding.delete.setVisibility(View.GONE); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType().equals(RefreshEvent.Type.HISTORY)) getHistory(); + } + + @Override + public void onItemClick(History item) { + VideoActivity.start(this, item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public void onItemDelete(History item) { + mAdapter.remove(item.delete()); + if (mAdapter.getItemCount() > 0) return; + mBinding.delete.setVisibility(View.GONE); + mAdapter.setDelete(false); + } + + @Override + public boolean onLongClick() { + mAdapter.setDelete(!mAdapter.isDelete()); + return true; + } + + @Override + public void onBackPressed() { + if (mAdapter.isDelete()) mAdapter.setDelete(false); + else super.onBackPressed(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java new file mode 100644 index 00000000..117cbaca --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java @@ -0,0 +1,227 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.PendingIntent; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.core.content.pm.ShortcutInfoCompat; +import androidx.core.content.pm.ShortcutManagerCompat; +import androidx.core.graphics.drawable.IconCompat; +import androidx.fragment.app.Fragment; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Updater; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.ActivityHomeBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.event.ServerEvent; +import com.fongmi.android.tv.event.StateEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.receiver.ShortcutReceiver; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.FragmentStateManager; +import com.fongmi.android.tv.ui.fragment.SettingFragment; +import com.fongmi.android.tv.ui.fragment.SettingPlayerFragment; +import com.fongmi.android.tv.ui.fragment.VodFragment; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.google.android.material.navigation.NavigationBarView; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class HomeActivity extends BaseActivity implements NavigationBarView.OnItemSelectedListener { + + private FragmentStateManager mManager; + private ActivityHomeBinding mBinding; + private int orientation; + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityHomeBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + checkAction(intent); + } + + @Override + protected void initView(Bundle savedInstanceState) { + orientation = getResources().getConfiguration().orientation; + Updater.create().release().start(this); + initFragment(savedInstanceState); + Server.get().start(); + initConfig(); + setNavigation(); + } + + @Override + protected void initEvent() { + mBinding.navigation.setOnItemSelectedListener(this); + mBinding.navigation.findViewById(R.id.live).setOnLongClickListener(this::addShortcut); + } + + private void checkAction(Intent intent) { + if (Intent.ACTION_SEND.equals(intent.getAction())) { + VideoActivity.push(this, intent.getStringExtra(Intent.EXTRA_TEXT)); + } else if (Intent.ACTION_VIEW.equals(intent.getAction()) && intent.getData() != null) { + if ("text/plain".equals(intent.getType()) || UrlUtil.path(intent.getData()).endsWith(".m3u")) { + loadLive("file:/" + FileChooser.getPathFromUri(this, intent.getData())); + } else { + VideoActivity.push(this, intent.getData().toString()); + } + } + } + + private void initFragment(Bundle savedInstanceState) { + mManager = new FragmentStateManager(mBinding.container, getSupportFragmentManager()) { + @Override + public Fragment getItem(int position) { + if (position == 0) return VodFragment.newInstance(); + if (position == 1) return SettingFragment.newInstance(); + if (position == 2) return SettingPlayerFragment.newInstance(); + return null; + } + }; + if (savedInstanceState == null) mManager.change(0); + } + + private void initConfig() { + WallConfig.get().init(); + LiveConfig.get().init().load(); + VodConfig.get().init().load(getCallback()); + } + + private Callback getCallback() { + return new Callback() { + @Override + public void success(String result) { + Notify.show(result); + } + + @Override + public void success() { + checkAction(getIntent()); + RefreshEvent.config(); + RefreshEvent.video(); + } + + @Override + public void error(String msg) { + RefreshEvent.config(); + StateEvent.empty(); + Notify.show(msg); + } + }; + } + + private void loadLive(String url) { + LiveConfig.load(Config.find(url, 1), new Callback() { + @Override + public void success() { + openLive(); + } + }); + } + + private void setNavigation() { + mBinding.navigation.getMenu().findItem(R.id.vod).setVisible(true); + mBinding.navigation.getMenu().findItem(R.id.setting).setVisible(true); + mBinding.navigation.getMenu().findItem(R.id.live).setVisible(LiveConfig.hasUrl()); + } + + private boolean openLive() { + LiveActivity.start(this); + return false; + } + + private boolean addShortcut(View view) { + ShortcutInfoCompat info = new ShortcutInfoCompat.Builder(this, getString(R.string.nav_live)).setIcon(IconCompat.createWithResource(this, R.mipmap.ic_launcher)).setIntent(new Intent(Intent.ACTION_VIEW, null, this, LiveActivity.class)).setShortLabel(getString(R.string.nav_live)).build(); + PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(this, ShortcutReceiver.class).setAction(ShortcutReceiver.ACTION), PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + ShortcutManagerCompat.requestPinShortcut(this, info, pendingIntent.getIntentSender()); + return true; + } + + public void change(int position) { + mManager.change(position); + } + + @Override + public void onRefreshEvent(RefreshEvent event) { + super.onRefreshEvent(event); + if (event.getType().equals(RefreshEvent.Type.CONFIG)) setNavigation(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onServerEvent(ServerEvent event) { + if (event.getType() != ServerEvent.Type.PUSH) return; + VideoActivity.push(this, event.getText()); + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + if (mBinding.navigation.getSelectedItemId() == item.getItemId()) return false; + if (item.getItemId() == R.id.setting) return mManager.change(1); + if (item.getItemId() == R.id.vod) return mManager.change(0); + if (item.getItemId() == R.id.live) return openLive(); + return false; + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + App.post(() -> checkOrientation(newConfig), 100); + } + + private void checkOrientation(Configuration newConfig) { + if (orientation != newConfig.orientation) { + orientation = newConfig.orientation; + RefreshEvent.video(); + } + } + + protected boolean handleBack() { + return true; + } + + @Override + protected void onBackPress() { + if (!mBinding.navigation.getMenu().findItem(R.id.vod).isVisible()) { + setNavigation(); + } else if (mManager.isVisible(2)) { + change(1); + } else if (mManager.isVisible(1)) { + mBinding.navigation.setSelectedItemId(R.id.vod); + } else if (mManager.canBack(0)) { + finish(); + } + } + + @Override + protected void onDestroy() { + WallConfig.get().clear(); + LiveConfig.get().clear(); + VodConfig.get().clear(); + OkHttp.get().clear(); + AppDatabase.backup(); + Source.get().exit(); + Server.get().stop(); + super.onDestroy(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/KeepActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/KeepActivity.java new file mode 100644 index 00000000..72819b40 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/KeepActivity.java @@ -0,0 +1,130 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.databinding.ActivityKeepBinding; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.adapter.KeepAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.dialog.SyncDialog; +import com.fongmi.android.tv.utils.Notify; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +public class KeepActivity extends BaseActivity implements KeepAdapter.OnClickListener { + + private ActivityKeepBinding mBinding; + private KeepAdapter mAdapter; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, KeepActivity.class)); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityKeepBinding.inflate(getLayoutInflater()); + } + + @Override + protected void initView(Bundle savedInstanceState) { + setRecyclerView(); + getKeep(); + } + + @Override + protected void initEvent() { + mBinding.back.setOnClickListener(v -> finish()); + mBinding.sync.setOnClickListener(this::onSync); + mBinding.delete.setOnClickListener(this::onDelete); + } + + private void setRecyclerView() { + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.getItemAnimator().setChangeDuration(0); + mBinding.recycler.setLayoutManager(new GridLayoutManager(this, Product.getColumn(this))); + mBinding.recycler.setAdapter(mAdapter = new KeepAdapter(this)); + mAdapter.setSize(Product.getSpec(getActivity())); + } + + private void getKeep() { + mAdapter.addAll(Keep.getVod()); + mBinding.delete.setVisibility(mAdapter.getItemCount() > 0 ? View.VISIBLE : View.GONE); + } + + private void onSync(View view) { + SyncDialog.create().keep().show(this); + } + + private void onDelete(View view) { + if (mAdapter.isDelete()) { + new MaterialAlertDialogBuilder(this).setTitle(R.string.dialog_delete_record).setMessage(R.string.dialog_delete_keep).setNegativeButton(R.string.dialog_negative, null).setPositiveButton(R.string.dialog_positive, (dialog, which) -> mAdapter.clear()).show(); + } else if (mAdapter.getItemCount() > 0) { + mAdapter.setDelete(true); + } else { + mBinding.delete.setVisibility(View.GONE); + } + } + + private void loadConfig(Config config, Keep item) { + VodConfig.load(config, new Callback() { + @Override + public void success() { + VideoActivity.start(getActivity(), item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + RefreshEvent.config(); + RefreshEvent.video(); + } + + @Override + public void error(String msg) { + Notify.show(msg); + } + }); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType().equals(RefreshEvent.Type.KEEP)) getKeep(); + } + + @Override + public void onItemClick(Keep item) { + Config config = Config.find(item.getCid()); + if (config == null) CollectActivity.start(this, item.getVodName()); + else if (item.getCid() != VodConfig.getCid()) loadConfig(config, item); + else VideoActivity.start(this, item.getSiteKey(), item.getVodId(), item.getVodName(), item.getVodPic()); + } + + @Override + public void onItemDelete(Keep item) { + mAdapter.remove(item.delete()); + if (mAdapter.getItemCount() > 0) return; + mBinding.delete.setVisibility(View.GONE); + mAdapter.setDelete(false); + } + + @Override + public boolean onLongClick() { + mAdapter.setDelete(!mAdapter.isDelete()); + return true; + } + + @Override + public void onBackPressed() { + if (mAdapter.isDelete()) mAdapter.setDelete(false); + else super.onBackPressed(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java new file mode 100644 index 00000000..cf20c3b3 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java @@ -0,0 +1,1118 @@ +package com.fongmi.android.tv.ui.activity; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.C; +import androidx.media3.common.Player; +import androidx.viewbinding.ViewBinding; + +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.bean.CastVideo; +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Epg; +import com.fongmi.android.tv.bean.EpgData; +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Track; +import com.fongmi.android.tv.databinding.ActivityLiveBinding; +import com.fongmi.android.tv.event.ActionEvent; +import com.fongmi.android.tv.event.ErrorEvent; +import com.fongmi.android.tv.event.PlayerEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.LiveCallback; +import com.fongmi.android.tv.impl.PassCallback; +import com.fongmi.android.tv.model.LiveViewModel; +import com.fongmi.android.tv.player.Players; +import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.player.exo.ExoUtil; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.service.PlaybackService; +import com.fongmi.android.tv.ui.adapter.ChannelAdapter; +import com.fongmi.android.tv.ui.adapter.EpgDataAdapter; +import com.fongmi.android.tv.ui.adapter.GroupAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.custom.CustomKeyDownLive; +import com.fongmi.android.tv.ui.dialog.CastDialog; +import com.fongmi.android.tv.ui.dialog.InfoDialog; +import com.fongmi.android.tv.ui.dialog.LiveDialog; +import com.fongmi.android.tv.ui.dialog.PassDialog; +import com.fongmi.android.tv.ui.dialog.SubtitleDialog; +import com.fongmi.android.tv.ui.dialog.TrackDialog; +import com.fongmi.android.tv.utils.Biometric; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.PiP; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Traffic; +import com.fongmi.android.tv.utils.Util; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +public class LiveActivity extends BaseActivity implements CustomKeyDownLive.Listener, TrackDialog.Listener, Biometric.Callback, PassCallback, LiveCallback, GroupAdapter.OnClickListener, ChannelAdapter.OnClickListener, EpgDataAdapter.OnClickListener, CastDialog.Listener, InfoDialog.Listener { + + private ActivityLiveBinding mBinding; + private ChannelAdapter mChannelAdapter; + private EpgDataAdapter mEpgDataAdapter; + private Observer mObserveUrl; + private CustomKeyDownLive mKeyDown; + private GroupAdapter mGroupAdapter; + private Observer mObserveEpg; + private LiveViewModel mViewModel; + private List mHides; + private Players mPlayers; + private Channel mChannel; + private Group mGroup; + private Runnable mR1; + private Runnable mR2; + private Runnable mR3; + private boolean redirect; + private boolean rotate; + private boolean stop; + private boolean lock; + private String tag; + private int count; + private PiP mPiP; + + public static void start(Context context) { + if (!LiveConfig.isEmpty()) context.startActivity(new Intent(context, LiveActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("empty", false)); + } + + private boolean isEmpty() { + return getIntent().getBooleanExtra("empty", true); + } + + private Group getKeep() { + return mGroupAdapter.get(0); + } + + private Live getHome() { + return LiveConfig.get().getHome(); + } + + private long getTimeout() { + return getHome().isEmpty() ? Constant.TIMEOUT_PLAY : getHome().getTimeout(); + } + + @Override + protected boolean customWall() { + return false; + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityLiveBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Util.hideSystemUI(this); + } + + @Override + protected void initView(Bundle savedInstanceState) { + mKeyDown = CustomKeyDownLive.create(this, mBinding.exo); + setPadding(mBinding.control.getRoot()); + setPadding(mBinding.recycler, true); + mPlayers = Players.create(this); + mObserveEpg = this::setEpg; + mObserveUrl = this::start; + mHides = new ArrayList<>(); + mR1 = this::hideControl; + mR2 = this::setTraffic; + mR3 = this::hideInfo; + mPiP = new PiP(); + Server.get().start(); + setRecyclerView(); + setVideoView(); + setViewModel(); + checkLive(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + protected void initEvent() { + mBinding.control.seek.setListener(mPlayers); + mBinding.control.cast.setOnClickListener(view -> onCast()); + mBinding.control.info.setOnClickListener(view -> onInfo()); + mBinding.control.play.setOnClickListener(view -> checkPlay()); + mBinding.control.next.setOnClickListener(view -> nextChannel()); + mBinding.control.prev.setOnClickListener(view -> prevChannel()); + mBinding.control.right.back.setOnClickListener(view -> onBack()); + mBinding.control.right.lock.setOnClickListener(view -> onLock()); + mBinding.control.right.rotate.setOnClickListener(view -> onRotate()); + mBinding.control.action.text.setOnClickListener(this::onTrack); + mBinding.control.action.audio.setOnClickListener(this::onTrack); + mBinding.control.action.video.setOnClickListener(this::onTrack); + mBinding.control.action.home.setOnClickListener(view -> onHome()); + mBinding.control.action.line.setOnClickListener(view -> onLine()); + mBinding.control.action.scale.setOnClickListener(view -> onScale()); + mBinding.control.action.speed.setOnClickListener(view -> onSpeed()); + mBinding.control.action.invert.setOnClickListener(view -> onInvert()); + mBinding.control.action.across.setOnClickListener(view -> onAcross()); + mBinding.control.action.change.setOnClickListener(view -> onChange()); + mBinding.control.action.player.setOnClickListener(view -> onChoose()); + mBinding.control.action.decode.setOnClickListener(view -> onDecode()); + mBinding.control.action.text.setOnLongClickListener(view -> onTextLong()); + mBinding.control.action.speed.setOnLongClickListener(view -> onSpeedLong()); + mBinding.control.action.getRoot().setOnTouchListener(this::onActionTouch); + mBinding.video.setOnTouchListener((view, event) -> mKeyDown.onTouchEvent(event)); + } + + private void setRecyclerView() { + mBinding.group.setItemAnimator(null); + mBinding.channel.setItemAnimator(null); + mBinding.epgData.setItemAnimator(null); + mBinding.group.setAdapter(mGroupAdapter = new GroupAdapter(this)); + mBinding.channel.setAdapter(mChannelAdapter = new ChannelAdapter(this)); + mBinding.epgData.setAdapter(mEpgDataAdapter = new EpgDataAdapter(this)); + } + + private void setVideoView() { + mPlayers.init(mBinding.exo); + PlaybackService.start(mPlayers); + setScale(Setting.getLiveScale()); + ExoUtil.setSubtitleView(mBinding.exo); + mPlayers.setTag(tag = UUID.randomUUID().toString()); + mBinding.control.action.invert.setActivated(Setting.isInvert()); + mBinding.control.action.across.setActivated(Setting.isAcross()); + mBinding.control.action.change.setActivated(Setting.isChange()); + mBinding.control.action.speed.setText(mPlayers.getSpeedText()); + mBinding.control.action.decode.setText(mPlayers.getDecodeText()); + mBinding.video.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> mPiP.update(getActivity(), view)); + } + + private void setDecode() { + mBinding.control.action.decode.setText(mPlayers.getDecodeText()); + } + + private void setScale(int scale) { + Setting.putLiveScale(scale); + mBinding.exo.setResizeMode(scale); + mBinding.control.action.scale.setText(ResUtil.getStringArray(R.array.select_scale)[scale]); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(LiveViewModel.class); + mViewModel.url.observeForever(mObserveUrl); + mViewModel.xml.observe(this, this::setEpg); + mViewModel.epg.observeForever(mObserveEpg); + mViewModel.live.observe(this, live -> { + mViewModel.getXml(live); + hideProgress(); + setGroup(live); + setWidth(live); + }); + } + + private void checkLive() { + if (isEmpty()) { + LiveConfig.get().init().load(getCallback()); + } else { + getLive(); + } + } + + private Callback getCallback() { + return new Callback() { + @Override + public void success() { + getLive(); + } + + @Override + public void error(String msg) { + Notify.show(msg); + } + }; + } + + private void getLive() { + mBinding.control.action.home.setText(LiveConfig.isOnly() ? getString(R.string.live_refresh) : getHome().getName()); + mViewModel.getLive(getHome()); + showProgress(); + } + + private void setGroup(Live live) { + List items = new ArrayList<>(); + for (Group group : live.getGroups()) (group.isHidden() ? mHides : items).add(group); + mGroupAdapter.addAll(items); + setPosition(LiveConfig.get().find(items)); + } + + private void setWidth(Live live) { + int padding = ResUtil.dp2px(48); + if (live.getWidth() == 0) for (Group item : live.getGroups()) live.setWidth(Math.max(live.getWidth(), ResUtil.getTextWidth(item.getName(), 14))); + mBinding.group.getLayoutParams().width = live.getWidth() == 0 ? 0 : Math.min(live.getWidth() + padding, ResUtil.getScreenWidth() / 4); + } + + @Override + public void setWidth(Group group) { + int logo = ResUtil.dp2px(56); + int padding = ResUtil.dp2px(60); + if (group.isKeep()) group.setWidth(0); + if (group.getWidth() == 0) for (Channel item : group.getChannel()) group.setWidth(Math.max(group.getWidth(), (item.getLogo().isEmpty() ? 0 : logo) + ResUtil.getTextWidth(item.getNumber() + item.getName(), 14))); + mBinding.channel.getLayoutParams().width = group.getWidth() == 0 ? 0 : Math.min(group.getWidth() + padding, ResUtil.getScreenWidth() / 2); + } + + private void setWidth(Epg epg) { + int padding = ResUtil.dp2px(40); + if (epg.getList().isEmpty()) return; + int minWidth = ResUtil.getTextWidth(epg.getList().get(0).getTime(), 14); + if (epg.getWidth() == 0) for (EpgData item : epg.getList()) epg.setWidth(Math.max(epg.getWidth(), ResUtil.getTextWidth(item.getTitle(), 14))); + mBinding.epgData.getLayoutParams().width = epg.getWidth() == 0 ? 0 : Math.min(Math.max(epg.getWidth(), minWidth) + padding, ResUtil.getScreenWidth() / 2); + } + + private void setPosition(int[] position) { + if (position[0] == -1) return; + int size = mGroupAdapter.getItemCount(); + if (size == 1 || position[0] >= size) return; + mGroup = mGroupAdapter.get(position[0]); + mGroup.setPosition(position[1]); + onItemClick(mGroup); + onItemClick(mGroup.current()); + } + + private void setPosition() { + if (mChannel == null) return; + mGroup = mChannel.getGroup(); + int position = mGroupAdapter.indexOf(mGroup); + boolean change = mGroupAdapter.getPosition() != position; + if (change) mGroupAdapter.setSelected(position); + if (change) mChannelAdapter.addAll(mGroup.getChannel()); + if (change) mChannelAdapter.setSelected(mGroup.getPosition()); + mBinding.channel.scrollToPosition(mGroup.getPosition()); + mBinding.group.scrollToPosition(position); + } + + private void onCast() { + CastDialog.create().video(CastVideo.get(mBinding.control.title.getText().toString(), mPlayers.getUrl())).fm(false).show(this); + } + + private void onInfo() { + InfoDialog.create(this).title(mBinding.control.title.getText()).headers(mPlayers.getHeaders()).url(mPlayers.getUrl()).show(); + } + + private void onBack() { + finish(); + } + + private void onLock() { + setLock(!isLock()); + mKeyDown.setLock(isLock()); + checkLockImg(); + showControl(); + } + + private void onRotate() { + setR1Callback(); + setRotate(!isRotate()); + setRequestedOrientation(ResUtil.isLand(this) ? ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } + + private void checkPlay() { + if (mPlayers.isPlaying()) onPaused(); + else onPlay(); + } + + private void onTrack(View view) { + TrackDialog.create().player(mPlayers).type(Integer.parseInt(view.getTag().toString())).show(this); + hideControl(); + } + + private void onHome() { + if (LiveConfig.isOnly()) setLive(getHome()); + else LiveDialog.create(this).show(); + hideControl(); + } + + private void onLine() { + nextLine(false); + } + + private void onScale() { + int index = Setting.getLiveScale(); + String[] array = ResUtil.getStringArray(R.array.select_scale); + if (mKeyDown.getScale() != 1.0f) mKeyDown.resetScale(); + else setScale(index == array.length - 1 ? 0 : ++index); + setR1Callback(); + } + + private void onSpeed() { + mBinding.control.action.speed.setText(mPlayers.addSpeed()); + setR1Callback(); + } + + private boolean onSpeedLong() { + mBinding.control.action.speed.setText(mPlayers.toggleSpeed()); + setR1Callback(); + return true; + } + + private void onInvert() { + setR1Callback(); + Setting.putInvert(!Setting.isInvert()); + mBinding.control.action.invert.setActivated(Setting.isInvert()); + } + + private void onAcross() { + setR1Callback(); + Setting.putAcross(!Setting.isAcross()); + mBinding.control.action.across.setActivated(Setting.isAcross()); + } + + private void onChange() { + setR1Callback(); + Setting.putChange(!Setting.isChange()); + mBinding.control.action.change.setActivated(Setting.isChange()); + } + + private void onDecode() { + mPlayers.toggleDecode(); + setR1Callback(); + setDecode(); + } + + private void onChoose() { + mPlayers.choose(this, mBinding.control.title.getText()); + setRedirect(true); + } + + private boolean onTextLong() { + onSubtitleClick(); + return true; + } + + private boolean onActionTouch(View v, MotionEvent e) { + if (e.getAction() == MotionEvent.ACTION_UP) setR1Callback(); + return false; + } + + private void hideUI() { + if (isGone(mBinding.recycler)) return; + mBinding.recycler.setVisibility(View.GONE); + setPosition(); + } + + private void showUI() { + if (isVisible(mBinding.recycler)) return; + mBinding.recycler.setVisibility(View.VISIBLE); + mBinding.channel.requestFocus(); + setPosition(); + hideEpg(); + } + + private void showEpg(Channel item) { + if (mChannel == null || mChannel.getData().getList().isEmpty() || mEpgDataAdapter.getItemCount() == 0 || !mChannel.equals(item) || !mChannel.getGroup().equals(mGroup)) return; + mBinding.epgData.scrollToPosition(item.getData().getSelected()); + mBinding.epgData.setVisibility(View.VISIBLE); + mBinding.channel.setVisibility(View.GONE); + mBinding.group.setVisibility(View.GONE); + } + + private void hideEpg() { + mBinding.channel.setVisibility(View.VISIBLE); + mBinding.group.setVisibility(View.VISIBLE); + mBinding.epgData.setVisibility(View.GONE); + } + + private void showProgress() { + mBinding.widget.progress.setVisibility(View.VISIBLE); + App.post(mR2, 0); + hideError(); + } + + private void hideProgress() { + mBinding.widget.progress.setVisibility(View.GONE); + App.removeCallbacks(mR2); + Traffic.reset(); + } + + private void showError(String text) { + mBinding.widget.error.setVisibility(View.VISIBLE); + mBinding.widget.text.setText(text); + hideProgress(); + } + + private void hideError() { + mBinding.widget.error.setVisibility(View.GONE); + mBinding.widget.text.setText(""); + } + + private void showControl() { + if (mPiP.isInMode(this)) return; + mBinding.control.info.setVisibility(mPlayers.isEmpty() ? View.GONE : View.VISIBLE); + mBinding.control.cast.setVisibility(mPlayers.isEmpty() ? View.GONE : View.VISIBLE); + mBinding.control.right.rotate.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.right.back.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.center.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.bottom.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.top.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.getRoot().setVisibility(View.VISIBLE); + setR1Callback(); + checkPlayImg(); + hideInfo(); + } + + private void hideControl() { + mBinding.control.getRoot().setVisibility(View.GONE); + App.removeCallbacks(mR1); + } + + private void showInfo() { + mBinding.widget.infoPip.setVisibility(mPiP.isInMode(this) ? View.VISIBLE : View.GONE); + mBinding.widget.info.setVisibility(mPiP.isInMode(this) ? View.GONE : View.VISIBLE); + setR3Callback(); + hideControl(); + setInfo(); + } + + private void hideInfo() { + mBinding.widget.infoPip.setVisibility(View.GONE); + mBinding.widget.info.setVisibility(View.GONE); + App.removeCallbacks(mR3); + } + + private void setTraffic() { + Traffic.setSpeed(mBinding.widget.traffic); + App.post(mR2, Constant.INTERVAL_TRAFFIC); + } + + private void setR1Callback() { + App.post(mR1, Constant.INTERVAL_HIDE); + } + + private void setR3Callback() { + App.post(mR3, Constant.INTERVAL_HIDE); + } + + private void onToggle() { + if (isVisible(mBinding.control.getRoot())) hideControl(); + else if (isVisible(mBinding.recycler)) hideUI(); + else showUI(); + hideInfo(); + } + + private void resetPass() { + this.count = 0; + } + + private void setArtwork(String url) { + ImgUtil.load(url, R.drawable.radio, new CustomTarget<>(ResUtil.getScreenWidth(), ResUtil.getScreenHeight()) { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + mBinding.exo.setDefaultArtwork(resource); + } + + @Override + public void onLoadFailed(@Nullable Drawable error) { + mBinding.exo.setDefaultArtwork(error); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + } + }); + } + + @Override + public void onItemClick(Group item) { + mGroupAdapter.setSelected(mGroup = item); + mChannelAdapter.addAll(item.getChannel()); + mChannelAdapter.setSelected(item.getPosition()); + mBinding.channel.scrollToPosition(Math.max(item.getPosition(), 0)); + if (!item.isKeep() || ++count < 5 || mHides.isEmpty()) return; + if (Biometric.enable()) Biometric.show(this); + else PassDialog.create().show(this); + resetPass(); + } + + @Override + public void onItemClick(Channel item) { + if (!item.getData().getList().isEmpty() && item.isSelected() && mChannel != null && mChannel.equals(item) && mChannel.getGroup().equals(mGroup)) { + showEpg(item); + } else { + mGroup.setPosition(mChannelAdapter.setSelected(item.group(mGroup))); + setArtwork(item.getLogo()); + mChannel = item; + showInfo(); + hideUI(); + fetch(); + } + } + + @Override + public boolean onLongClick(Channel item) { + if (mGroup.isHidden()) return false; + boolean exist = Keep.exist(item.getName()); + Notify.show(exist ? R.string.keep_del : R.string.keep_add); + if (exist) delKeep(item); + else addKeep(item); + return true; + } + + @Override + public void onItemClick(EpgData item) { + if (item.isSelected()) { + fetch(item); + } else if (mChannel.hasCatchup()) { + mBinding.control.title.setText(getString(R.string.detail_title, mChannel.getName(), item.getTitle())); + Notify.show(getString(R.string.play_ready, item.getTitle())); + mEpgDataAdapter.setSelected(item); + fetch(item); + } + } + + private void addKeep(Channel item) { + getKeep().add(item); + Keep keep = new Keep(); + keep.setKey(item.getName()); + keep.setType(1); + keep.save(); + } + + private void delKeep(Channel item) { + if (mGroup.isKeep()) mChannelAdapter.remove(item); + getKeep().getChannel().remove(item); + Keep.delete(item.getName()); + } + + private void setInfo() { + mViewModel.getEpg(mChannel); + mBinding.widget.play.setText(""); + mChannel.loadLogo(mBinding.widget.logo); + mBinding.control.title.setSelected(true); + mBinding.widget.name.setText(mChannel.getName()); + mBinding.control.title.setText(mChannel.getName()); + mBinding.widget.namePip.setText(mChannel.getName()); + mBinding.widget.line.setText(mChannel.getLineText()); + mBinding.widget.number.setText(mChannel.getNumber()); + mBinding.widget.numberPip.setText(mChannel.getNumber()); + mBinding.widget.line.setVisibility(mChannel.getLineVisible()); + mBinding.control.action.line.setText(mBinding.widget.line.getText()); + mBinding.control.action.line.setVisibility(mBinding.widget.line.getVisibility()); + } + + private void setEpg() { + EpgData data = mChannel.getData().getEpgData(); + boolean hasTitle = !data.getTitle().isEmpty(); + mEpgDataAdapter.addAll(mChannel.getData().getList()); + if (hasTitle) mBinding.control.title.setText(getString(R.string.detail_title, mChannel.getName(), data.getTitle())); + mBinding.widget.name.setMaxEms(hasTitle ? 12 : 48); + mBinding.widget.play.setText(data.format()); + setWidth(mChannel.getData()); + setMetadata(); + } + + private void setEpg(boolean success) { + if (mChannel != null && success) mViewModel.getEpg(mChannel); + } + + private void setEpg(Epg epg) { + if (mChannel != null && mChannel.getTvgId().equals(epg.getKey())) setEpg(); + } + + private void fetch(EpgData item) { + if (mChannel == null) return; + mViewModel.getUrl(mChannel, item); + mPlayers.clear(); + mPlayers.stop(); + hideUI(); + } + + private void fetch() { + if (mChannel == null) return; + LiveConfig.get().setKeep(mChannel); + mViewModel.getUrl(mChannel); + mPlayers.clear(); + mPlayers.stop(); + showProgress(); + } + + private void start(Channel result) { + mPlayers.start(result, getTimeout()); + } + + private void checkControl() { + if (isVisible(mBinding.control.getRoot())) showControl(); + } + + private void checkPlayImg() { + mBinding.control.play.setImageResource(mPlayers.isPlaying() ? androidx.media3.ui.R.drawable.exo_icon_pause : androidx.media3.ui.R.drawable.exo_icon_play); + mPiP.update(this, mPlayers.isPlaying()); + ActionEvent.update(); + } + + private void checkLockImg() { + mBinding.control.right.lock.setImageResource(isLock() ? R.drawable.ic_control_lock_on : R.drawable.ic_control_lock_off); + } + + private void resetAdapter() { + mBinding.channel.getLayoutParams().width = 0; + mBinding.epgData.getLayoutParams().width = 0; + mBinding.group.getLayoutParams().width = 0; + mEpgDataAdapter.clear(); + mChannelAdapter.clear(); + mGroupAdapter.clear(); + mHides.clear(); + mChannel = null; + mGroup = null; + } + + @Override + public void onSubtitleClick() { + App.post(this::hideControl, 200); + App.post(() -> SubtitleDialog.create().view(mBinding.exo.getSubtitleView()).full(true).show(this), 200); + } + + @Override + public void setLive(Live item) { + if (item.isActivated()) item.getGroups().clear(); + LiveConfig.get().setHome(item); + mPlayers.reset(); + mPlayers.stop(); + resetAdapter(); + hideControl(); + getLive(); + } + + @Override + public void setPass(String pass) { + unlock(pass); + } + + @Override + public void onBiometricSuccess() { + unlock(null); + } + + private void unlock(String pass) { + boolean first = true; + Iterator iterator = mHides.iterator(); + while (iterator.hasNext()) { + Group item = iterator.next(); + if (pass != null && !pass.equals(item.getPass())) continue; + mGroupAdapter.add(item); + if (first) onItemClick(item); + iterator.remove(); + first = false; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onActionEvent(ActionEvent event) { + if (ActionEvent.PLAY.equals(event.getAction()) || ActionEvent.PAUSE.equals(event.getAction())) { + checkPlay(); + } else if (ActionEvent.NEXT.equals(event.getAction())) { + nextChannel(); + } else if (ActionEvent.PREV.equals(event.getAction())) { + prevChannel(); + } else if (ActionEvent.STOP.equals(event.getAction())) { + finish(); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + switch (event.getType()) { + case LIVE: + setLive(getHome()); + break; + case PLAYER: + fetch(); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerEvent(PlayerEvent event) { + if (!event.getTag().equals(tag)) return; + switch (event.getState()) { + case PlayerEvent.PREPARE: + setDecode(); + break; + case Player.STATE_BUFFERING: + showProgress(); + break; + case Player.STATE_READY: + hideProgress(); + checkControl(); + checkPlayImg(); + mPlayers.reset(); + break; + case Player.STATE_ENDED: + checkNext(); + break; + case PlayerEvent.TRACK: + setMetadata(); + setTrackVisible(); + break; + case PlayerEvent.SIZE: + mBinding.control.size.setText(mPlayers.getSizeText()); + break; + } + } + + private void setTrackVisible() { + mBinding.control.action.text.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_TEXT) || mPlayers.isVod() ? View.VISIBLE : View.GONE); + mBinding.control.action.audio.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_AUDIO) ? View.VISIBLE : View.GONE); + mBinding.control.action.video.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_VIDEO) ? View.VISIBLE : View.GONE); + mBinding.control.action.speed.setVisibility(mPlayers.isVod() ? View.VISIBLE : View.GONE); + } + + private void setMetadata() { + String title = mBinding.widget.name.getText().toString(); + String artist = mBinding.widget.play.getText().toString(); + mPlayers.setMetadata(title, artist, mChannel.getLogo(), mBinding.exo.getDefaultArtwork()); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onErrorEvent(ErrorEvent event) { + if (!event.getTag().equals(tag)) return; + if (mPlayers.retried()) onError(event); + else fetch(); + } + + private void onError(ErrorEvent event) { + Track.delete(mPlayers.getUrl()); + showError(event.getMsg()); + mPlayers.resetTrack(); + mPlayers.reset(); + mPlayers.stop(); + startFlow(); + } + + private void startFlow() { + if (!Setting.isChange()) return; + if (!mChannel.isLast()) nextLine(true); + } + + private boolean prevGroup() { + int position = mGroupAdapter.getPosition() - 1; + if (position < 0) position = mGroupAdapter.getItemCount() - 1; + if (mGroup.equals(mGroupAdapter.get(position))) return false; + mGroup = mGroupAdapter.get(position); + mGroupAdapter.setSelected(position); + if (mGroup.skip()) return prevGroup(); + mChannelAdapter.addAll(mGroup.getChannel()); + mGroup.setPosition(mGroup.getChannel().size() - 1); + return true; + } + + private boolean nextGroup() { + int position = mGroupAdapter.getPosition() + 1; + if (position > mGroupAdapter.getItemCount() - 1) position = 0; + if (mGroup.equals(mGroupAdapter.get(position))) return false; + mGroup = mGroupAdapter.get(position); + mGroupAdapter.setSelected(position); + if (mGroup.skip()) return nextGroup(); + mChannelAdapter.addAll(mGroup.getChannel()); + mGroup.setPosition(0); + return true; + } + + private void prevChannel() { + if (mGroup == null) return; + int position = mGroup.getPosition() - 1; + boolean limit = position < 0; + if (Setting.isAcross() & limit) prevGroup(); + else mGroup.setPosition(limit ? mChannelAdapter.getItemCount() - 1 : position); + if (!mGroup.isEmpty()) onItemClick(mGroup.current()); + } + + private void nextChannel() { + if (mGroup == null) return; + int position = mGroup.getPosition() + 1; + boolean limit = position > mChannelAdapter.getItemCount() - 1; + if (Setting.isAcross() && limit) nextGroup(); + else mGroup.setPosition(limit ? 0 : position); + if (!mGroup.isEmpty()) onItemClick(mGroup.current()); + } + + private void checkNext() { + int current = mChannel.getData().getInRange(); + int position = mChannel.getData().getSelected() + 1; + boolean hasNext = position <= current && position > 0; + if (hasNext) onItemClick(mChannel.getData().getList().get(position)); + else fetch(); + } + + private void prevLine() { + if (mChannel == null || mChannel.isOnly()) return; + mChannel.prevLine(); + showInfo(); + fetch(); + } + + private void nextLine(boolean show) { + if (mChannel == null || mChannel.isOnly()) return; + mChannel.nextLine(); + if (show) showInfo(); + else setInfo(); + fetch(); + } + + private void onPaused() { + mPlayers.pause(); + checkPlayImg(); + } + + private void onPlay() { + mPlayers.play(); + checkPlayImg(); + } + + public boolean isRedirect() { + return redirect; + } + + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + + public boolean isRotate() { + return rotate; + } + + public void setRotate(boolean rotate) { + this.rotate = rotate; + if (rotate) { + noPadding(mBinding.recycler); + noPadding(mBinding.control.getRoot()); + } else { + setPadding(mBinding.recycler, true); + setPadding(mBinding.control.getRoot()); + } + } + + public boolean isStop() { + return stop; + } + + public void setStop(boolean stop) { + this.stop = stop; + } + + public boolean isLock() { + return lock; + } + + public void setLock(boolean lock) { + this.lock = lock; + } + + @Override + public void onCasted() { + } + + @Override + public void onSpeedUp() { + if (mPlayers.isLive() || !mPlayers.isPlaying()) return; + mBinding.control.action.speed.setText(mPlayers.setSpeed(Setting.getSpeed())); + mBinding.widget.speed.startAnimation(ResUtil.getAnim(R.anim.forward)); + mBinding.widget.speed.setVisibility(View.VISIBLE); + } + + @Override + public void onSpeedEnd() { + mBinding.control.action.speed.setText(mPlayers.setSpeed(1.0f)); + mBinding.widget.speed.setVisibility(View.GONE); + mBinding.widget.speed.clearAnimation(); + } + + @Override + public void onBright(int progress) { + mBinding.widget.bright.setVisibility(View.VISIBLE); + mBinding.widget.brightProgress.setProgress(progress); + if (progress < 35) mBinding.widget.brightIcon.setImageResource(R.drawable.ic_widget_bright_low); + else if (progress < 70) mBinding.widget.brightIcon.setImageResource(R.drawable.ic_widget_bright_medium); + else mBinding.widget.brightIcon.setImageResource(R.drawable.ic_widget_bright_high); + } + + @Override + public void onBrightEnd() { + mBinding.widget.bright.setVisibility(View.GONE); + } + + @Override + public void onVolume(int progress) { + mBinding.widget.volume.setVisibility(View.VISIBLE); + mBinding.widget.volumeProgress.setProgress(progress); + if (progress < 35) mBinding.widget.volumeIcon.setImageResource(R.drawable.ic_widget_volume_low); + else if (progress < 70) mBinding.widget.volumeIcon.setImageResource(R.drawable.ic_widget_volume_medium); + else mBinding.widget.volumeIcon.setImageResource(R.drawable.ic_widget_volume_high); + } + + @Override + public void onVolumeEnd() { + mBinding.widget.volume.setVisibility(View.GONE); + } + + @Override + public void onFlingUp() { + if (Setting.isInvert()) nextChannel(); + else prevChannel(); + } + + @Override + public void onFlingDown() { + if (Setting.isInvert()) prevChannel(); + else nextChannel(); + } + + @Override + public void onFlingLeft() { + if (mPlayers.isLive()) prevLine(); + } + + @Override + public void onFlingRight() { + if (mPlayers.isLive()) nextLine(true); + } + + @Override + public void onSeek(long time) { + if (mPlayers.isLive()) return; + mBinding.widget.action.setImageResource(time > 0 ? R.drawable.ic_widget_forward : R.drawable.ic_widget_rewind); + mBinding.widget.time.setText(mPlayers.getPositionTime(time)); + mBinding.widget.seek.setVisibility(View.VISIBLE); + hideProgress(); + } + + @Override + public void onSeekEnd(long time) { + if (mPlayers.isLive()) return; + mBinding.widget.seek.setVisibility(View.GONE); + mPlayers.seek(time); + showProgress(); + onPlay(); + } + + @Override + public void onSingleTap() { + onToggle(); + } + + @Override + public void onDoubleTap() { + if (isVisible(mBinding.recycler)) hideUI(); + else if (isVisible(mBinding.control.getRoot())) hideControl(); + else showControl(); + } + + @Override + public void onShare(CharSequence title) { + mPlayers.share(this, title); + setRedirect(true); + } + + @Override + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + if (isRedirect()) return; + if (isLock()) App.post(this::onLock, 500); + if (mPlayers.haveTrack(C.TRACK_TYPE_VIDEO)) mPiP.enter(this, mPlayers.getVideoWidth(), mPlayers.getVideoHeight(), Setting.getLiveScale()); + } + + @Override + public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, @NonNull Configuration newConfig) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); + if (isInPictureInPictureMode) { + hideControl(); + hideInfo(); + hideUI(); + } else { + hideInfo(); + if (isStop()) finish(); + } + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Util.hideSystemUI(this); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) Util.hideSystemUI(this); + } + + @Override + protected void onStart() { + super.onStart(); + setStop(false); + onPlay(); + } + + @Override + protected void onResume() { + super.onResume(); + if (isRedirect()) onPlay(); + setRedirect(false); + } + + @Override + protected void onPause() { + super.onPause(); + if (isRedirect()) onPaused(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Setting.isBackgroundOff()) onPaused(); + setStop(true); + } + + @Override + public void onBackPressed() { + if (isVisible(mBinding.control.getRoot())) { + hideControl(); + } else if (isVisible(mBinding.widget.info)) { + hideInfo(); + } else if (isVisible(mBinding.recycler)) { + hideUI(); + } else if (!isLock()) { + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mPlayers.release(); + Source.get().exit(); + PlaybackService.stop(); + App.removeCallbacks(mR1, mR2, mR3); + mViewModel.url.removeObserver(mObserveUrl); + mViewModel.epg.removeObserver(mObserveEpg); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/ScanActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/ScanActivity.java new file mode 100644 index 00000000..9cb995f1 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/ScanActivity.java @@ -0,0 +1,92 @@ +package com.fongmi.android.tv.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.ActivityScanBinding; +import com.fongmi.android.tv.event.ScanEvent; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.utils.Util; +import com.google.zxing.BarcodeFormat; +import com.journeyapps.barcodescanner.BarcodeCallback; +import com.journeyapps.barcodescanner.BarcodeResult; +import com.journeyapps.barcodescanner.CaptureManager; +import com.journeyapps.barcodescanner.DefaultDecoderFactory; + +import java.util.List; + +public class ScanActivity extends BaseActivity implements BarcodeCallback { + + private ActivityScanBinding mBinding; + private CaptureManager mCapture; + + public static void start(Activity activity) { + activity.startActivity(new Intent(activity, ScanActivity.class)); + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityScanBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Util.hideSystemUI(this); + } + + @Override + protected void initView(Bundle savedInstanceState) { + mCapture = new CaptureManager(this, mBinding.scanner); + mBinding.scanner.getBarcodeView().setDecoderFactory(new DefaultDecoderFactory(List.of(BarcodeFormat.QR_CODE))); + } + + @Override + public void barcodeResult(BarcodeResult result) { + if (!result.getText().startsWith("http")) return; + ScanEvent.post(result.getText()); + finish(); + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + mCapture.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Util.hideSystemUI(this); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) Util.hideSystemUI(this); + } + + @Override + protected void onResume() { + super.onResume(); + mCapture.onResume(); + mBinding.scanner.decodeSingle(this); + } + + @Override + protected void onPause() { + super.onPause(); + mCapture.onPause(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mCapture.onDestroy(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java new file mode 100644 index 00000000..2e2765ec --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/VideoActivity.java @@ -0,0 +1,1621 @@ +package com.fongmi.android.tv.ui.activity; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.text.Html; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.TextUtils; +import android.text.style.ClickableSpan; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModelProvider; +import androidx.media3.common.C; +import androidx.media3.common.Player; +import androidx.recyclerview.widget.RecyclerView; +import androidx.viewbinding.ViewBinding; + +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.CastVideo; +import com.fongmi.android.tv.bean.Danmaku; +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.bean.Flag; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.bean.Parse; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Sub; +import com.fongmi.android.tv.bean.Track; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.ActivityVideoBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.ActionEvent; +import com.fongmi.android.tv.event.CastEvent; +import com.fongmi.android.tv.event.ErrorEvent; +import com.fongmi.android.tv.event.PlayerEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.player.Players; +import com.fongmi.android.tv.player.exo.ExoUtil; +import com.fongmi.android.tv.service.PlaybackService; +import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; +import com.fongmi.android.tv.ui.adapter.FlagAdapter; +import com.fongmi.android.tv.ui.adapter.ParseAdapter; +import com.fongmi.android.tv.ui.adapter.QualityAdapter; +import com.fongmi.android.tv.ui.adapter.QuickAdapter; +import com.fongmi.android.tv.ui.base.BaseActivity; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.custom.CustomKeyDownVod; +import com.fongmi.android.tv.ui.custom.CustomMovement; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.ui.dialog.CastDialog; +import com.fongmi.android.tv.ui.dialog.ControlDialog; +import com.fongmi.android.tv.ui.dialog.DanmakuDialog; +import com.fongmi.android.tv.ui.dialog.EpisodeGridDialog; +import com.fongmi.android.tv.ui.dialog.EpisodeListDialog; +import com.fongmi.android.tv.ui.dialog.InfoDialog; +import com.fongmi.android.tv.ui.dialog.ReceiveDialog; +import com.fongmi.android.tv.ui.dialog.SubtitleDialog; +import com.fongmi.android.tv.ui.dialog.TrackDialog; +import com.fongmi.android.tv.utils.Clock; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.PiP; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Sniffer; +import com.fongmi.android.tv.utils.Timer; +import com.fongmi.android.tv.utils.Traffic; +import com.fongmi.android.tv.utils.Util; +import com.github.bassaer.library.MDColor; +import com.github.catvod.utils.Trans; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.permissionx.guolindev.PermissionX; + +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.regex.Matcher; + +public class VideoActivity extends BaseActivity implements Clock.Callback, CustomKeyDownVod.Listener, TrackDialog.Listener, ControlDialog.Listener, FlagAdapter.OnClickListener, EpisodeAdapter.OnClickListener, QualityAdapter.OnClickListener, QuickAdapter.OnClickListener, ParseAdapter.OnClickListener, CastDialog.Listener, InfoDialog.Listener { + + private ActivityVideoBinding mBinding; + private ViewGroup.LayoutParams mFrameParams; + private Observer mObserveDetail; + private Observer mObservePlayer; + private Observer mObserveSearch; + private EpisodeAdapter mEpisodeAdapter; + private QualityAdapter mQualityAdapter; + private ControlDialog mControlDialog; + private QuickAdapter mQuickAdapter; + private ParseAdapter mParseAdapter; + private CustomKeyDownVod mKeyDown; + private ExecutorService mExecutor; + private SiteViewModel mViewModel; + private FlagAdapter mFlagAdapter; + private List mDialogs; + private List mBroken; + private History mHistory; + private Players mPlayers; + private boolean fullscreen; + private boolean initAuto; + private boolean autoMode; + private boolean useParse; + private boolean redirect; + private boolean rotate; + private boolean stop; + private boolean lock; + private Runnable mR1; + private Runnable mR2; + private Runnable mR3; + private Runnable mR4; + private Clock mClock; + private String tag; + private PiP mPiP; + + public static void push(FragmentActivity activity, String text) { + if (FileChooser.isValid(activity, Uri.parse(text))) file(activity, FileChooser.getPathFromUri(activity, Uri.parse(text))); + else start(activity, Sniffer.getUrl(text)); + } + + public static void file(FragmentActivity activity, String path) { + if (TextUtils.isEmpty(path)) return; + String name = new File(path).getName(); + PermissionX.init(activity).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> start(activity, "push_agent", "file://" + path, name)); + } + + public static void cast(Activity activity, History history) { + start(activity, history.getSiteKey(), history.getVodId(), history.getVodName(), history.getVodPic()); + } + + public static void collect(Activity activity, String key, String id, String name, String pic) { + start(activity, key, id, name, pic, null, true); + } + + public static void start(Activity activity, String url) { + start(activity, "push_agent", url, url); + } + + public static void start(Activity activity, String key, String id, String name) { + start(activity, key, id, name, null); + } + + public static void start(Activity activity, String key, String id, String name, String pic) { + start(activity, key, id, name, pic, null); + } + + public static void start(Activity activity, String key, String id, String name, String pic, String mark) { + start(activity, key, id, name, pic, mark, false); + } + + public static void start(Activity activity, String key, String id, String name, String pic, String mark, boolean collect) { + Intent intent = new Intent(activity, VideoActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("collect", collect); + intent.putExtra("mark", mark); + intent.putExtra("name", name); + intent.putExtra("pic", pic); + intent.putExtra("key", key); + intent.putExtra("id", id); + activity.startActivity(intent); + } + + private String getName() { + return Objects.toString(getIntent().getStringExtra("name"), ""); + } + + private String getPic() { + return Objects.toString(getIntent().getStringExtra("pic"), ""); + } + + private String getMark() { + return Objects.toString(getIntent().getStringExtra("mark"), ""); + } + + private String getKey() { + return Objects.toString(getIntent().getStringExtra("key"), ""); + } + + private String getId() { + return Objects.toString(getIntent().getStringExtra("id"), ""); + } + + private String getHistoryKey() { + return getKey().concat(AppDatabase.SYMBOL).concat(getId()).concat(AppDatabase.SYMBOL) + VodConfig.getCid(); + } + + private Site getSite() { + return VodConfig.get().getSite(getKey()); + } + + private Flag getFlag() { + return mFlagAdapter.getActivated(); + } + + private Episode getEpisode() { + return mEpisodeAdapter.getActivated(); + } + + private int getScale() { + return mHistory != null && mHistory.getScale() != -1 ? mHistory.getScale() : Setting.getScale(); + } + + private boolean isReplay() { + return Setting.getReset() == 1; + } + + private boolean isFromCollect() { + return getIntent().getBooleanExtra("collect", false); + } + + private boolean isAutoRotate() { + return Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 1; + } + + private boolean isLand() { + return mBinding.getRoot().getTag().equals("land"); + } + + private boolean isPort() { + return mBinding.getRoot().getTag().equals("port"); + } + + @Override + protected boolean transparent() { + return false; + } + + @Override + protected ViewBinding getBinding() { + return mBinding = ActivityVideoBinding.inflate(getLayoutInflater()); + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + String id = Objects.toString(intent.getStringExtra("id"), ""); + if (TextUtils.isEmpty(id) || id.equals(getId())) return; + mBinding.swipeLayout.setRefreshing(true); + getIntent().putExtras(intent); + stopSearch(); + setOrient(); + checkId(); + } + + @Override + protected void initView(Bundle savedInstanceState) { + mKeyDown = CustomKeyDownVod.create(this, mBinding.exo); + mFrameParams = mBinding.video.getLayoutParams(); + mBinding.progressLayout.showProgress(); + mBinding.swipeLayout.setEnabled(false); + mObserveDetail = this::setDetail; + mObservePlayer = this::setPlayer; + mObserveSearch = this::setSearch; + mPlayers = Players.create(this); + mDialogs = new ArrayList<>(); + mBroken = new ArrayList<>(); + mClock = Clock.create(); + mR1 = this::hideControl; + mR2 = this::setTraffic; + mR3 = this::setOrient; + mR4 = this::showEmpty; + mPiP = new PiP(); + checkDanmakuImg(); + setRecyclerView(); + setVideoView(); + setViewModel(); + showProgress(); + showDanmaku(); + checkId(); + } + + @Override + @SuppressLint("ClickableViewAccessibility") + protected void initEvent() { + mBinding.name.setOnClickListener(view -> onName()); + mBinding.more.setOnClickListener(view -> onMore()); + mBinding.actor.setOnClickListener(view -> onActor()); + mBinding.content.setOnClickListener(view -> onContent()); + mBinding.reverse.setOnClickListener(view -> onReverse()); + mBinding.director.setOnClickListener(view -> onDirector()); + mBinding.name.setOnLongClickListener(view -> onChange()); + mBinding.content.setOnLongClickListener(view -> onCopy()); + mBinding.control.cast.setOnClickListener(view -> onCast()); + mBinding.control.info.setOnClickListener(view -> onInfo()); + mBinding.control.full.setOnClickListener(view -> onFull()); + mBinding.control.keep.setOnClickListener(view -> onKeep()); + mBinding.control.play.setOnClickListener(view -> checkPlay()); + mBinding.control.next.setOnClickListener(view -> checkNext()); + mBinding.control.prev.setOnClickListener(view -> checkPrev()); + mBinding.control.setting.setOnClickListener(view -> onSetting()); + mBinding.control.title.setOnLongClickListener(view -> onChange()); + mBinding.control.right.back.setOnClickListener(view -> onFull()); + mBinding.control.right.lock.setOnClickListener(view -> onLock()); + mBinding.control.right.rotate.setOnClickListener(view -> onRotate()); + mBinding.control.danmaku.setOnClickListener(view -> onDanmakuShow()); + mBinding.control.action.text.setOnClickListener(this::onTrack); + mBinding.control.action.audio.setOnClickListener(this::onTrack); + mBinding.control.action.video.setOnClickListener(this::onTrack); + mBinding.control.action.loop.setOnClickListener(view -> onLoop()); + mBinding.control.action.scale.setOnClickListener(view -> onScale()); + mBinding.control.action.speed.setOnClickListener(view -> onSpeed()); + mBinding.control.action.reset.setOnClickListener(view -> onReset()); + mBinding.control.action.player.setOnClickListener(view -> onChoose()); + mBinding.control.action.decode.setOnClickListener(view -> onDecode()); + mBinding.control.action.ending.setOnClickListener(view -> onEnding()); + mBinding.control.action.opening.setOnClickListener(view -> onOpening()); + mBinding.control.action.danmaku.setOnClickListener(view -> onDanmaku()); + mBinding.control.action.episodes.setOnClickListener(view -> onEpisodes()); + mBinding.control.action.text.setOnLongClickListener(view -> onTextLong()); + mBinding.control.action.speed.setOnLongClickListener(view -> onSpeedLong()); + mBinding.control.action.reset.setOnLongClickListener(view -> onResetToggle()); + mBinding.control.action.ending.setOnLongClickListener(view -> onEndingReset()); + mBinding.control.action.opening.setOnLongClickListener(view -> onOpeningReset()); + mBinding.video.setOnTouchListener((view, event) -> mKeyDown.onTouchEvent(event)); + mBinding.control.action.getRoot().setOnTouchListener(this::onActionTouch); + mBinding.swipeLayout.setOnRefreshListener(this::onSwipeRefresh); + mBinding.control.seek.setListener(mPlayers); + } + + private void setRecyclerView() { + mBinding.flag.setHasFixedSize(true); + mBinding.flag.setItemAnimator(null); + mBinding.flag.addItemDecoration(new SpaceItemDecoration(8)); + mBinding.flag.setAdapter(mFlagAdapter = new FlagAdapter(this)); + mBinding.quick.setAdapter(mQuickAdapter = new QuickAdapter(this)); + mBinding.episode.setHasFixedSize(true); + mBinding.episode.setItemAnimator(null); + mBinding.episode.addItemDecoration(new SpaceItemDecoration(8)); + mBinding.episode.setAdapter(mEpisodeAdapter = new EpisodeAdapter(this, ViewType.HORI)); + mBinding.quality.setHasFixedSize(true); + mBinding.quality.setItemAnimator(null); + mBinding.quality.addItemDecoration(new SpaceItemDecoration(8)); + mBinding.quality.setAdapter(mQualityAdapter = new QualityAdapter(this)); + mBinding.control.parse.setHasFixedSize(true); + mBinding.control.parse.setItemAnimator(null); + mBinding.control.parse.addItemDecoration(new SpaceItemDecoration(8)); + mBinding.control.parse.setAdapter(mParseAdapter = new ParseAdapter(this, ViewType.DARK)); + } + + private void setVideoView() { + mPlayers.init(mBinding.exo); + PlaybackService.start(mPlayers); + ExoUtil.setSubtitleView(mBinding.exo); + mPlayers.setDanmakuView(mBinding.danmaku); + mPlayers.setTag(tag = UUID.randomUUID().toString()); + if (isPort() && ResUtil.isLand(this)) enterFullscreen(); + mBinding.control.action.decode.setText(mPlayers.getDecodeText()); + mBinding.control.action.danmaku.setVisibility(Setting.isDanmakuLoad() ? View.VISIBLE : View.GONE); + mBinding.control.action.reset.setText(ResUtil.getStringArray(R.array.select_reset)[Setting.getReset()]); + mBinding.video.addOnLayoutChangeListener((view, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> mPiP.update(getActivity(), view)); + } + + private void setVideoView(boolean isInPictureInPictureMode) { + if (isInPictureInPictureMode) { + mBinding.video.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)); + } else { + mBinding.video.setLayoutParams(mFrameParams); + } + } + + private void setDecode() { + mBinding.control.action.decode.setText(mPlayers.getDecodeText()); + } + + private void setScale(int scale) { + mHistory.setScale(scale); + mBinding.exo.setResizeMode(scale); + mBinding.control.action.scale.setText(ResUtil.getStringArray(R.array.select_scale)[scale]); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.result.observeForever(mObserveDetail); + mViewModel.player.observeForever(mObservePlayer); + mViewModel.search.observeForever(mObserveSearch); + mViewModel.episode.observe(this, episode -> { + onItemClick(episode); + hideSheet(); + }); + } + + private void checkId() { + if (getId().startsWith("push://")) getIntent().putExtra("key", "push_agent").putExtra("id", getId().substring(7)); + if (getId().isEmpty() || getId().startsWith("msearch:")) setEmpty(false); + else getDetail(); + } + + private void getDetail() { + mViewModel.detailContent(getKey(), getId()); + } + + private void getDetail(Vod item) { + getIntent().putExtra("key", item.getSiteKey()); + getIntent().putExtra("pic", item.getVodPic()); + getIntent().putExtra("id", item.getVodId()); + mBinding.swipeLayout.setRefreshing(true); + mBinding.swipeLayout.setEnabled(false); + mBinding.scroll.scrollTo(0, 0); + mClock.setCallback(null); + mPlayers.reset(); + mPlayers.stop(); + getDetail(); + } + + private void setDetail(Result result) { + mBinding.swipeLayout.setRefreshing(false); + if (result.getList().isEmpty()) setEmpty(result.hasMsg()); + else setDetail(result.getList().get(0)); + Notify.show(result.getMsg()); + } + + private void setEmpty(boolean finish) { + if (isFromCollect() || finish) { + finish(); + } else if (getName().isEmpty()) { + showEmpty(); + } else { + mBinding.name.setText(getName()); + App.post(mR4, 10000); + checkSearch(false); + } + } + + private void showEmpty() { + showError(getString(R.string.error_detail)); + mBinding.swipeLayout.setEnabled(true); + mBinding.progressLayout.showEmpty(); + stopSearch(); + } + + private void setDetail(Vod item) { + mBinding.progressLayout.showContent(); + mBinding.video.setTag(item.getVodPic(getPic())); + mBinding.name.setText(item.getVodName(getName())); + setText(mBinding.remark, 0, item.getVodRemarks()); + setText(mBinding.site, R.string.detail_site, getSite().getName()); + setText(mBinding.content, 0, Html.fromHtml(item.getVodContent()).toString()); + setText(mBinding.actor, R.string.detail_actor, Html.fromHtml(item.getVodActor()).toString()); + setText(mBinding.director, R.string.detail_director, Html.fromHtml(item.getVodDirector()).toString()); + mBinding.contentLayout.setVisibility(mBinding.content.getVisibility()); + mFlagAdapter.addAll(item.getVodFlags()); + setOther(mBinding.other, item); + setArtwork(item.getVodPic()); + App.removeCallbacks(mR4); + checkHistory(item); + checkFlag(item); + checkKeepImg(); + } + + private void setText(TextView view, int resId, String text) { + view.setText(getSpan(resId, text), TextView.BufferType.SPANNABLE); + view.setVisibility(text.isEmpty() ? View.GONE : View.VISIBLE); + view.setLinkTextColor(MDColor.YELLOW_500); + CustomMovement.bind(view); + view.setTag(text); + } + + private SpannableStringBuilder getSpan(int resId, String text) { + if (resId > 0) text = getString(resId, text); + Map map = new HashMap<>(); + Matcher m = Sniffer.CLICKER.matcher(text); + while (m.find()) { + String key = Trans.s2t(m.group(2)).trim(); + text = text.replace(m.group(), key); + map.put(key, m.group(1)); + } + SpannableStringBuilder span = new SpannableStringBuilder(text); + for (String s : map.keySet()) { + int index = text.indexOf(s); + Result result = Result.type(map.get(s)); + span.setSpan(getClickSpan(result), index, index + s.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + return span; + } + + private ClickableSpan getClickSpan(Result result) { + return new ClickableSpan() { + @Override + public void onClick(@NonNull View view) { + FolderActivity.start(getActivity(), getKey(), result); + ((TextView) view).setMaxLines(Integer.MAX_VALUE); + setRedirect(true); + } + }; + } + + private void setOther(TextView view, Vod item) { + StringBuilder sb = new StringBuilder(); + if (!item.getVodYear().isEmpty()) sb.append(getString(R.string.detail_year, item.getVodYear())).append(" "); + if (!item.getVodArea().isEmpty()) sb.append(getString(R.string.detail_area, item.getVodArea())).append(" "); + if (!item.getTypeName().isEmpty()) sb.append(getString(R.string.detail_type, item.getTypeName())).append(" "); + view.setVisibility(sb.length() == 0 ? View.GONE : View.VISIBLE); + view.setText(Util.substring(sb.toString(), 2)); + } + + private void getPlayer(Flag flag, Episode episode, boolean replay) { + mBinding.control.title.setText(getString(R.string.detail_title, mBinding.name.getText(), episode.getName())); + mViewModel.playerContent(getKey(), flag.getFlag(), episode.getUrl()); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mBinding.control.title.setSelected(true); + updateHistory(episode, replay); + showProgress(); + setMetadata(); + } + + private void setPlayer(Result result) { + result.getUrl().set(mQualityAdapter.getPosition()); + if (!result.getDesc().isEmpty()) setText(mBinding.content, R.string.detail_content, Html.fromHtml(result.getDesc()).toString()); + setUseParse(VodConfig.hasParse() && ((result.getPlayUrl().isEmpty() && VodConfig.get().getFlags().contains(result.getFlag())) || result.getJx() == 1)); + if (mControlDialog != null && mControlDialog.isVisible()) mControlDialog.setParseVisible(isUseParse()); + mBinding.control.parse.setVisibility(isFullscreen() && isUseParse() ? View.VISIBLE : View.GONE); + mPlayers.start(result, isUseParse(), getSite().isChangeable() ? getSite().getTimeout() : -1); + setQualityVisible(result.getUrl().isMulti()); + mBinding.swipeLayout.setRefreshing(false); + mPlayers.setKey(getHistoryKey()); + mQualityAdapter.addAll(result); + } + + @Override + public void onItemClick(Flag item) { + if (item.isActivated()) return; + mFlagAdapter.setActivated(item); + mBinding.flag.scrollToPosition(mFlagAdapter.getPosition()); + setEpisodeAdapter(item.getEpisodes()); + setQualityVisible(false); + seamless(item); + } + + @Override + public void onItemClick(Episode item) { + if (shouldEnterFullscreen(item)) return; + mFlagAdapter.toggle(item); + notifyItemChanged(mEpisodeAdapter); + mBinding.episode.scrollToPosition(mEpisodeAdapter.getPosition()); + if (isFullscreen()) Notify.show(getString(R.string.play_ready, item.getName())); + onRefresh(); + } + + @Override + public void onItemClick(Result result) { + try { + mPlayers.start(result, isUseParse(), getSite().isChangeable() ? getSite().getTimeout() : -1); + } catch (Exception e) { + ErrorEvent.extract(tag, e.getMessage()); + e.printStackTrace(); + } + } + + @Override + public void onItemClick(Vod item) { + setAutoMode(false); + getDetail(item); + } + + @Override + public void onItemClick(Parse item) { + setParse(item); + onRefresh(); + } + + private void setParse(Parse item) { + VodConfig.get().setParse(item); + notifyItemChanged(mParseAdapter); + if (mControlDialog != null && mControlDialog.isVisible()) mControlDialog.updateParse(); + } + + private void setEpisodeAdapter(List items) { + mBinding.control.action.episodes.setVisibility(items.size() < 2 ? View.GONE : View.VISIBLE); + mBinding.control.nextRoot.setVisibility(items.size() < 2 ? View.GONE : View.VISIBLE); + mBinding.control.prevRoot.setVisibility(items.size() < 2 ? View.GONE : View.VISIBLE); + mBinding.episode.setVisibility(items.size() == 0 ? View.GONE : View.VISIBLE); + mBinding.reverse.setVisibility(items.size() < 2 ? View.GONE : View.VISIBLE); + mBinding.more.setVisibility(items.size() < 10 ? View.GONE : View.VISIBLE); + mEpisodeAdapter.addAll(items); + } + + private void seamless(Flag flag) { + Episode episode = flag.find(mHistory.getVodRemarks(), getMark().isEmpty()); + setQualityVisible(episode != null && episode.isActivated() && mQualityAdapter.getItemCount() > 1); + if (episode == null || episode.isActivated()) return; + mHistory.setVodRemarks(episode.getName()); + onItemClick(episode); + } + + private void setQualityVisible(boolean visible) { + mBinding.qualityText.setVisibility(visible ? View.VISIBLE : View.GONE); + mBinding.quality.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + private void reverseEpisode(boolean scroll) { + mFlagAdapter.reverse(); + setEpisodeAdapter(getFlag().getEpisodes()); + if (scroll) mBinding.episode.scrollToPosition(mEpisodeAdapter.getPosition()); + } + + private void onName() { + String name = mBinding.name.getText().toString(); + Notify.show(getString(R.string.detail_search, name)); + initSearch(name, false); + } + + private void onMore() { + EpisodeGridDialog.create().reverse(mHistory.isRevSort()).episodes(mEpisodeAdapter.getItems()).show(this); + } + + private void onActor() { + mBinding.actor.setMaxLines(mBinding.actor.getMaxLines() == 1 ? Integer.MAX_VALUE : 1); + } + + private void onDirector() { + mBinding.director.setMaxLines(mBinding.director.getMaxLines() == 1 ? Integer.MAX_VALUE : 1); + } + + private void onContent() { + mBinding.content.setMaxLines(mBinding.content.getMaxLines() == 2 ? Integer.MAX_VALUE : 2); + } + + private void onReverse() { + mHistory.setRevSort(!mHistory.isRevSort()); + reverseEpisode(false); + } + + private boolean onChange() { + checkSearch(true); + return true; + } + + private boolean onCopy() { + Util.copy(mBinding.content.getText().toString()); + return true; + } + + private void onCast() { + CastDialog.create().history(mHistory).video(CastVideo.get(mBinding.name.getText().toString(), mPlayers.getUrl(), mPlayers.getPosition())).fm(true).show(this); + } + + private void onInfo() { + InfoDialog.create(this).title(mBinding.control.title.getText()).headers(mPlayers.getHeaders()).url(mPlayers.getUrl()).show(); + } + + private void onFull() { + setR1Callback(); + toggleFullscreen(); + } + + private void onKeep() { + Keep keep = Keep.find(getHistoryKey()); + Notify.show(keep != null ? R.string.keep_del : R.string.keep_add); + if (keep != null) keep.delete(); + else createKeep(); + RefreshEvent.keep(); + checkKeepImg(); + } + + private void checkPlay() { + setR1Callback(); + if (mPlayers.isPlaying()) onPaused(); + else if (mPlayers.isEmpty()) onRefresh(); + else onPlay(); + } + + private void checkNext() { + checkNext(true); + } + + private void checkNext(boolean notify) { + setR1Callback(); + Episode item = mEpisodeAdapter.getNext(); + if (!item.isActivated()) onItemClick(item); + else if (notify) Notify.show(R.string.error_play_next); + } + + private void checkPrev() { + setR1Callback(); + Episode item = mEpisodeAdapter.getPrev(); + if (!item.isActivated()) onItemClick(item); + else Notify.show(R.string.error_play_prev); + } + + private void onSetting() { + mControlDialog = ControlDialog.create().parent(mBinding).history(mHistory).player(mPlayers).parse(isUseParse()).show(this); + } + + private void onLock() { + setLock(!isLock()); + setRequestedOrientation(getLockOrient()); + mKeyDown.setLock(isLock()); + checkLockImg(); + showControl(); + } + + private void onRotate() { + setR1Callback(); + setRotate(!isRotate()); + setRequestedOrientation(ResUtil.isLand(this) ? ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + } + + private void onTrack(View view) { + TrackDialog.create().player(mPlayers).type(Integer.parseInt(view.getTag().toString())).show(this); + hideControl(); + } + + private void onDanmaku() { + DanmakuDialog.create().player(mPlayers).show(this); + hideControl(); + } + + private void onDanmakuShow() { + Setting.putDanmakuShow(!Setting.isDanmakuShow()); + checkDanmakuImg(); + showDanmaku(); + } + + private void onLoop() { + mBinding.control.action.loop.setActivated(!mBinding.control.action.loop.isActivated()); + } + + private void onScale() { + int index = getScale(); + String[] array = ResUtil.getStringArray(R.array.select_scale); + if (mKeyDown.getScale() != 1.0f) mKeyDown.resetScale(); + else setScale(index == array.length - 1 ? 0 : ++index); + setR1Callback(); + } + + private void onSpeed() { + mBinding.control.action.speed.setText(mPlayers.addSpeed()); + mHistory.setSpeed(mPlayers.getSpeed()); + setR1Callback(); + } + + private boolean onSpeedLong() { + mBinding.control.action.speed.setText(mPlayers.toggleSpeed()); + mHistory.setSpeed(mPlayers.getSpeed()); + setR1Callback(); + return true; + } + + private void onRefresh() { + onReset(false); + } + + private void onReset() { + onReset(isReplay()); + } + + private void onReset(boolean replay) { + mPlayers.stop(); + mPlayers.clear(); + mClock.setCallback(null); + if (mFlagAdapter.isEmpty()) return; + if (mEpisodeAdapter.isEmpty()) return; + getPlayer(getFlag(), getEpisode(), replay); + } + + private boolean onResetToggle() { + Setting.putReset(Math.abs(Setting.getReset() - 1)); + mBinding.control.action.reset.setText(ResUtil.getStringArray(R.array.select_reset)[Setting.getReset()]); + return true; + } + + private void onDecode() { + mPlayers.toggleDecode(); + setR1Callback(); + setDecode(); + } + + private void onEnding() { + long current = mPlayers.getPosition(); + long duration = mPlayers.getDuration(); + if (current < 0 || duration < 0) return; + if (duration - current > Constant.OPED_LIMIT) return; + setEnding(duration - current); + setR1Callback(); + } + + private boolean onEndingReset() { + setR1Callback(); + setEnding(0); + return true; + } + + private void setEnding(long ending) { + mHistory.setEnding(ending); + mBinding.control.action.ending.setText(ending <= 0 ? getString(R.string.play_ed) : mPlayers.stringToTime(mHistory.getEnding())); + } + + private void onOpening() { + long current = mPlayers.getPosition(); + long duration = mPlayers.getDuration(); + if (current < 0 || duration < 0) return; + if (current > Constant.OPED_LIMIT) return; + setOpening(current); + setR1Callback(); + } + + private boolean onOpeningReset() { + setR1Callback(); + setOpening(0); + return true; + } + + private void setOpening(long opening) { + mHistory.setOpening(opening); + mBinding.control.action.opening.setText(opening <= 0 ? getString(R.string.play_op) : mPlayers.stringToTime(mHistory.getOpening())); + } + + private void onEpisodes() { + mDialogs.add(EpisodeListDialog.create(this).episodes(mEpisodeAdapter.getItems()).show()); + } + + private void onChoose() { + mPlayers.choose(this, mBinding.control.title.getText()); + setRedirect(true); + } + + private boolean onTextLong() { + onSubtitleClick(); + return true; + } + + private boolean onActionTouch(View v, MotionEvent e) { + setR1Callback(); + return false; + } + + private void onSwipeRefresh() { + if (mBinding.progressLayout.isEmpty()) getDetail(); + else onRefresh(); + } + + private void toggleFullscreen() { + if (isFullscreen()) exitFullscreen(); + else enterFullscreen(); + } + + private boolean shouldEnterFullscreen(Episode item) { + boolean enter = !isFullscreen() && item.isActivated(); + if (enter) enterFullscreen(); + return enter; + } + + private void enterFullscreen() { + if (isFullscreen()) return; + App.post(() -> mBinding.video.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT)), 50); + setRequestedOrientation(mPlayers.isPortrait() ? ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); + mBinding.control.full.setVisibility(View.GONE); + setRotate(mPlayers.isPortrait(), true); + mPlayers.setDanmakuSize(1.0f); + Util.hideSystemUI(this); + mKeyDown.resetScale(); + App.post(mR3, 2000); + hideControl(); + } + + private void exitFullscreen() { + if (!isFullscreen()) return; + setRequestedOrientation(isPort() ? ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT : ActivityInfo.SCREEN_ORIENTATION_FULL_USER); + App.post(() -> mBinding.episode.scrollToPosition(mEpisodeAdapter.getPosition()), 50); + mBinding.control.full.setVisibility(View.VISIBLE); + mBinding.video.setLayoutParams(mFrameParams); + mPlayers.setDanmakuSize(0.8f); + setRotate(false, false); + mKeyDown.resetScale(); + App.post(mR3, 2000); + hideControl(); + } + + private int getLockOrient() { + if (isLock()) { + return ResUtil.isLand(this) ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; + } else if (isRotate()) { + return ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; + } else if (isPort() && isAutoRotate()) { + return ActivityInfo.SCREEN_ORIENTATION_FULL_USER; + } else { + return ResUtil.isLand(this) ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT; + } + } + + private void showProgress() { + mBinding.widget.progress.setVisibility(View.VISIBLE); + App.post(mR2, 0); + hideError(); + } + + private void hideProgress() { + mBinding.widget.progress.setVisibility(View.GONE); + App.removeCallbacks(mR2); + Traffic.reset(); + } + + private void showError(String text) { + mBinding.widget.error.setVisibility(View.VISIBLE); + mBinding.widget.text.setText(text); + hideProgress(); + } + + private void hideError() { + mBinding.widget.error.setVisibility(View.GONE); + mBinding.widget.text.setText(""); + } + + private void showDanmaku() { + mBinding.danmaku.setVisibility(Setting.isDanmakuShow() ? View.VISIBLE : View.INVISIBLE); + } + + private void hideDanmaku() { + mBinding.danmaku.setVisibility(View.INVISIBLE); + } + + private void showControl() { + if (mPiP.isInMode(this)) return; + mBinding.control.danmaku.setVisibility(isLock() || !mPlayers.haveDanmaku() ? View.GONE : View.VISIBLE); + mBinding.control.setting.setVisibility(mHistory == null || isFullscreen() ? View.GONE : View.VISIBLE); + mBinding.control.right.rotate.setVisibility(isFullscreen() && !isLock() ? View.VISIBLE : View.GONE); + mBinding.control.keep.setVisibility(mHistory == null || isFullscreen() ? View.GONE : View.VISIBLE); + mBinding.control.right.back.setVisibility(isFullscreen() && !isLock() ? View.VISIBLE : View.GONE); + mBinding.control.parse.setVisibility(isFullscreen() && isUseParse() ? View.VISIBLE : View.GONE); + mBinding.control.action.getRoot().setVisibility(isFullscreen() ? View.VISIBLE : View.GONE); + mBinding.control.right.lock.setVisibility(isFullscreen() ? View.VISIBLE : View.GONE); + mBinding.control.info.setVisibility(mPlayers.isEmpty() ? View.GONE : View.VISIBLE); + mBinding.control.cast.setVisibility(mPlayers.isEmpty() ? View.GONE : View.VISIBLE); + mBinding.control.center.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.bottom.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.top.setVisibility(isLock() ? View.GONE : View.VISIBLE); + mBinding.control.getRoot().setVisibility(View.VISIBLE); + setR1Callback(); + checkPlayImg(); + } + + private void hideControl() { + mBinding.control.getRoot().setVisibility(View.GONE); + App.removeCallbacks(mR1); + } + + private void hideSheet() { + for (Dialog dialog : mDialogs) dialog.dismiss(); + for (Fragment fragment : getSupportFragmentManager().getFragments()) if (fragment instanceof BottomSheetDialogFragment) ((BottomSheetDialogFragment) fragment).dismiss(); + mDialogs.clear(); + } + + private void setTraffic() { + Traffic.setSpeed(mBinding.widget.traffic); + App.post(mR2, Constant.INTERVAL_TRAFFIC); + } + + private void setOrient() { + if (isPort() && isAutoRotate()) setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_USER); + if (isLand() && isAutoRotate()) setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE); + } + + private void setR1Callback() { + App.post(mR1, Constant.INTERVAL_HIDE); + } + + private void setArtwork(String url) { + ImgUtil.load(url, R.drawable.radio, new CustomTarget<>(ResUtil.getScreenWidth(), ResUtil.getScreenHeight()) { + @Override + public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { + mBinding.exo.setDefaultArtwork(resource); + } + + @Override + public void onLoadFailed(@Nullable Drawable error) { + mBinding.exo.setDefaultArtwork(error); + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + } + }); + } + + private void checkFlag(Vod item) { + boolean empty = item.getVodFlags().isEmpty(); + mBinding.flag.setVisibility(empty ? View.GONE : View.VISIBLE); + if (empty) { + ErrorEvent.flag(tag); + } else { + onItemClick(mHistory.getFlag()); + if (mHistory.isRevSort()) reverseEpisode(true); + } + } + + private void checkHistory(Vod item) { + mHistory = History.find(getHistoryKey()); + mHistory = mHistory == null ? createHistory(item) : mHistory; + if (!TextUtils.isEmpty(getMark())) mHistory.setVodRemarks(getMark()); + if (Setting.isIncognito() && mHistory.getKey().equals(getHistoryKey())) mHistory.delete(); + mBinding.control.action.opening.setText(mHistory.getOpening() <= 0 ? getString(R.string.play_op) : mPlayers.stringToTime(mHistory.getOpening())); + mBinding.control.action.ending.setText(mHistory.getEnding() <= 0 ? getString(R.string.play_ed) : mPlayers.stringToTime(mHistory.getEnding())); + mBinding.control.action.speed.setText(mPlayers.setSpeed(mHistory.getSpeed())); + mHistory.setVodPic(item.getVodPic()); + setScale(getScale()); + } + + private History createHistory(Vod item) { + History history = new History(); + history.setKey(getHistoryKey()); + history.setCid(VodConfig.getCid()); + history.setVodName(item.getVodName()); + history.findEpisode(item.getVodFlags()); + return history; + } + + private void updateHistory(Episode item, boolean replay) { + replay = replay || !item.equals(mHistory.getEpisode()); + mHistory.setEpisodeUrl(item.getUrl()); + mHistory.setVodRemarks(item.getName()); + mHistory.setVodFlag(getFlag().getFlag()); + mHistory.setCreateTime(System.currentTimeMillis()); + mHistory.setPosition(replay ? C.TIME_UNSET : mHistory.getPosition()); + } + + private void checkControl() { + if (isVisible(mBinding.control.getRoot())) showControl(); + } + + private void checkPlayImg() { + mBinding.control.play.setImageResource(mPlayers.isPlaying() ? androidx.media3.ui.R.drawable.exo_icon_pause : androidx.media3.ui.R.drawable.exo_icon_play); + mPiP.update(this, mPlayers.isPlaying()); + ActionEvent.update(); + } + + private void checkKeepImg() { + mBinding.control.keep.setImageResource(Keep.find(getHistoryKey()) == null ? R.drawable.ic_control_keep_off : R.drawable.ic_control_keep_on); + } + + private void checkLockImg() { + mBinding.control.right.lock.setImageResource(isLock() ? R.drawable.ic_control_lock_on : R.drawable.ic_control_lock_off); + } + + private void checkDanmakuImg() { + mBinding.control.danmaku.setImageResource(Setting.isDanmakuShow() ? R.drawable.ic_control_danmaku_on : R.drawable.ic_control_danmaku_off); + } + + private void createKeep() { + Keep keep = new Keep(); + keep.setKey(getHistoryKey()); + keep.setCid(VodConfig.getCid()); + keep.setSiteName(getSite().getName()); + keep.setVodPic(mBinding.video.getTag().toString()); + keep.setVodName(mBinding.name.getText().toString()); + keep.setCreateTime(System.currentTimeMillis()); + keep.save(); + } + + @Override + public void onSubtitleClick() { + App.post(this::hideControl, 200); + App.post(() -> SubtitleDialog.create().view(mBinding.exo.getSubtitleView()).full(isFullscreen()).show(this), 200); + } + + @Override + public void onTimeChanged() { + long position, duration; + mHistory.setPosition(position = mPlayers.getPosition()); + mHistory.setDuration(duration = mPlayers.getDuration()); + if (position >= 0 && duration > 0 && !Setting.isIncognito()) App.execute(() -> mHistory.update()); + if (mHistory.getEnding() > 0 && duration > 0 && mHistory.getEnding() + position >= duration) { + checkEnded(false); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onCastEvent(CastEvent event) { + if (isRedirect()) return; + ReceiveDialog.create().event(event).show(this); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onActionEvent(ActionEvent event) { + if (isRedirect()) return; + if (ActionEvent.PLAY.equals(event.getAction()) || ActionEvent.PAUSE.equals(event.getAction())) { + mBinding.control.play.performClick(); + } else if (ActionEvent.NEXT.equals(event.getAction())) { + mBinding.control.next.performClick(); + } else if (ActionEvent.PREV.equals(event.getAction())) { + mBinding.control.prev.performClick(); + } else if (ActionEvent.STOP.equals(event.getAction())) { + finish(); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (isRedirect()) return; + if (event.getType() == RefreshEvent.Type.DETAIL) getDetail(); + else if (event.getType() == RefreshEvent.Type.PLAYER) onRefresh(); + else if (event.getType() == RefreshEvent.Type.SUBTITLE) mPlayers.setSub(Sub.from(event.getPath())); + else if (event.getType() == RefreshEvent.Type.DANMAKU) mPlayers.setDanmaku(Danmaku.from(event.getPath())); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onPlayerEvent(PlayerEvent event) { + if (!event.getTag().equals(tag)) return; + switch (event.getState()) { + case PlayerEvent.PREPARE: + setDecode(); + setPosition(); + break; + case Player.STATE_BUFFERING: + showProgress(); + break; + case Player.STATE_READY: + hideProgress(); + checkControl(); + checkPlayImg(); + mPlayers.reset(); + break; + case Player.STATE_ENDED: + checkEnded(true); + break; + case PlayerEvent.TRACK: + setMetadata(); + setTrackVisible(); + mClock.setCallback(this); + break; + case PlayerEvent.SIZE: + checkOrientation(); + mBinding.control.size.setText(mPlayers.getSizeText()); + break; + } + } + + private void setPosition() { + if (mHistory != null) mPlayers.seekTo(Math.max(mHistory.getOpening(), mHistory.getPosition())); + } + + private void checkOrientation() { + if (isFullscreen() && !isRotate() && mPlayers.isPortrait()) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT); + setRotate(true); + } else if (isFullscreen() && isRotate() && mPlayers.isLandscape()) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE); + setRotate(false); + } + } + + private void checkEnded(boolean notify) { + if (mBinding.control.action.loop.isActivated()) { + onReset(true); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + checkNext(notify); + checkPlayImg(); + } + } + + private void setTrackVisible() { + mBinding.control.action.text.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_TEXT) || mPlayers.isVod() ? View.VISIBLE : View.GONE); + mBinding.control.action.audio.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_AUDIO) ? View.VISIBLE : View.GONE); + mBinding.control.action.video.setVisibility(mPlayers.haveTrack(C.TRACK_TYPE_VIDEO) ? View.VISIBLE : View.GONE); + if (mControlDialog != null && mControlDialog.isVisible()) mControlDialog.setTrackVisible(); + } + + private void setMetadata() { + String title = mHistory.getVodName(); + String episode = getEpisode().getName(); + String artist = title.equals(episode) ? "" : getString(R.string.play_now, episode); + mPlayers.setMetadata(title, artist, mHistory.getVodPic(), mBinding.exo.getDefaultArtwork()); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onErrorEvent(ErrorEvent event) { + if (!event.getTag().equals(tag)) return; + if (mPlayers.retried()) onError(event); + else onRefresh(); + } + + private void onError(ErrorEvent event) { + mBinding.swipeLayout.setEnabled(true); + Track.delete(mPlayers.getUrl()); + showError(event.getMsg()); + mClock.setCallback(null); + mPlayers.resetTrack(); + mPlayers.reset(); + mPlayers.stop(); + startFlow(); + } + + private void startFlow() { + if (!getSite().isChangeable()) return; + if (isUseParse()) checkParse(); + else checkFlag(); + } + + private void checkParse() { + int position = mParseAdapter.getPosition(); + boolean last = position == mParseAdapter.getItemCount() - 1; + boolean pass = position == 0 || last; + if (last) initParse(); + if (pass) checkFlag(); + else nextParse(position); + } + + private void initParse() { + if (mParseAdapter.isEmpty()) return; + setParse(mParseAdapter.first()); + } + + private void checkFlag() { + int position = isGone(mBinding.flag) ? -1 : mFlagAdapter.getPosition(); + if (position == mFlagAdapter.getItemCount() - 1) checkSearch(false); + else nextFlag(position); + } + + private void checkSearch(boolean force) { + if (mQuickAdapter.isEmpty()) initSearch(mBinding.name.getText().toString(), true); + else if (isAutoMode() || force) nextSite(); + } + + private void initSearch(String keyword, boolean auto) { + stopSearch(); + setAutoMode(auto); + setInitAuto(auto); + startSearch(keyword); + } + + private boolean isPass(Site item) { + if (isAutoMode() && !item.isChangeable()) return false; + return item.isSearchable(); + } + + private void startSearch(String keyword) { + mQuickAdapter.clear(); + List sites = new ArrayList<>(); + mExecutor = Executors.newFixedThreadPool(20); + for (Site item : VodConfig.get().getSites()) if (isPass(item)) sites.add(item); + for (Site site : sites) mExecutor.execute(() -> search(site, keyword)); + } + + private void stopSearch() { + if (mExecutor == null) return; + mExecutor.shutdownNow(); + mExecutor = null; + } + + private void search(Site site, String keyword) { + try { + mViewModel.searchContent(site, keyword, true); + } catch (Throwable ignored) { + } + } + + private void setSearch(Result result) { + List items = result.getList(); + Iterator iterator = items.iterator(); + while (iterator.hasNext()) if (mismatch(iterator.next())) iterator.remove(); + mBinding.quick.setVisibility(View.VISIBLE); + mQuickAdapter.addAll(items); + if (isInitAuto()) nextSite(); + if (items.isEmpty()) return; + App.removeCallbacks(mR4); + } + + private boolean mismatch(Vod item) { + if (getId().equals(item.getVodId())) return true; + if (mBroken.contains(item.getVodId())) return true; + String keyword = mBinding.name.getText().toString(); + if (isAutoMode()) return !item.getVodName().equals(keyword); + else return !item.getVodName().contains(keyword); + } + + private void nextParse(int position) { + Parse parse = mParseAdapter.get(position + 1); + Notify.show(getString(R.string.play_switch_parse, parse.getName())); + onItemClick(parse); + } + + private void nextFlag(int position) { + Flag flag = mFlagAdapter.get(position + 1); + Notify.show(getString(R.string.play_switch_flag, flag.getFlag())); + onItemClick(flag); + } + + private void nextSite() { + if (mQuickAdapter.isEmpty()) return; + Vod item = mQuickAdapter.get(0); + Notify.show(getString(R.string.play_switch_site, item.getSiteName())); + mQuickAdapter.remove(0); + mBroken.add(getId()); + setInitAuto(false); + getDetail(item); + } + + private void onPaused() { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mPlayers.pause(); + checkPlayImg(); + } + + private void onPlay() { + if (mHistory != null && mPlayers.isEnded()) mPlayers.seekTo(mHistory.getOpening()); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + if (!mPlayers.isEmpty() && mPlayers.isIdle()) mPlayers.prepare(); + mPlayers.play(); + checkPlayImg(); + } + + private boolean isFullscreen() { + return fullscreen; + } + + private void setFullscreen(boolean fullscreen) { + Util.toggleFullscreen(this, this.fullscreen = fullscreen); + } + + private boolean isInitAuto() { + return initAuto; + } + + private void setInitAuto(boolean initAuto) { + this.initAuto = initAuto; + } + + private boolean isAutoMode() { + return autoMode; + } + + private void setAutoMode(boolean autoMode) { + this.autoMode = autoMode; + } + + public boolean isUseParse() { + return useParse; + } + + public void setUseParse(boolean useParse) { + this.useParse = useParse; + } + + public boolean isRedirect() { + return redirect; + } + + public void setRedirect(boolean redirect) { + this.redirect = redirect; + } + + public boolean isRotate() { + return rotate; + } + + public void setRotate(boolean rotate, boolean fullscreen) { + this.rotate = rotate; + setFullscreen(fullscreen); + if (!fullscreen || rotate) noPadding(mBinding.control.getRoot()); + if (fullscreen && !rotate) setPadding(mBinding.control.getRoot()); + } + + public void setRotate(boolean rotate) { + this.rotate = rotate; + if (fullscreen && rotate) noPadding(mBinding.control.getRoot()); + if (fullscreen && !rotate) setPadding(mBinding.control.getRoot()); + } + + public boolean isStop() { + return stop; + } + + public void setStop(boolean stop) { + this.stop = stop; + } + + public boolean isLock() { + return lock; + } + + public void setLock(boolean lock) { + this.lock = lock; + } + + private void notifyItemChanged(RecyclerView.Adapter adapter) { + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + } + + @Override + public void onCasted() { + onPaused(); + } + + @Override + public void onScale(int tag) { + mKeyDown.resetScale(); + setScale(tag); + } + + @Override + public void onParse(Parse item) { + onItemClick(item); + } + + @Override + public void onSpeedUp() { + if (!mPlayers.isPlaying()) return; + mBinding.control.action.speed.setText(mPlayers.setSpeed(Setting.getSpeed())); + mBinding.widget.speed.startAnimation(ResUtil.getAnim(R.anim.forward)); + mBinding.widget.speed.setVisibility(View.VISIBLE); + } + + @Override + public void onSpeedEnd() { + mBinding.control.action.speed.setText(mPlayers.setSpeed(mHistory.getSpeed())); + mBinding.widget.speed.setVisibility(View.GONE); + mBinding.widget.speed.clearAnimation(); + } + + @Override + public void onBright(int progress) { + mBinding.widget.bright.setVisibility(View.VISIBLE); + mBinding.widget.brightProgress.setProgress(progress); + if (progress < 35) mBinding.widget.brightIcon.setImageResource(R.drawable.ic_widget_bright_low); + else if (progress < 70) mBinding.widget.brightIcon.setImageResource(R.drawable.ic_widget_bright_medium); + else mBinding.widget.brightIcon.setImageResource(R.drawable.ic_widget_bright_high); + } + + @Override + public void onBrightEnd() { + mBinding.widget.bright.setVisibility(View.GONE); + } + + @Override + public void onVolume(int progress) { + mBinding.widget.volume.setVisibility(View.VISIBLE); + mBinding.widget.volumeProgress.setProgress(progress); + if (progress < 35) mBinding.widget.volumeIcon.setImageResource(R.drawable.ic_widget_volume_low); + else if (progress < 70) mBinding.widget.volumeIcon.setImageResource(R.drawable.ic_widget_volume_medium); + else mBinding.widget.volumeIcon.setImageResource(R.drawable.ic_widget_volume_high); + } + + @Override + public void onVolumeEnd() { + mBinding.widget.volume.setVisibility(View.GONE); + } + + @Override + public void onFlingUp() { + checkNext(); + } + + @Override + public void onFlingDown() { + checkPrev(); + } + + @Override + public void onSeek(long time) { + mBinding.widget.action.setImageResource(time > 0 ? R.drawable.ic_widget_forward : R.drawable.ic_widget_rewind); + mBinding.widget.time.setText(mPlayers.getPositionTime(time)); + mBinding.widget.seek.setVisibility(View.VISIBLE); + hideProgress(); + } + + @Override + public void onSeekEnd(long time) { + mBinding.widget.seek.setVisibility(View.GONE); + mPlayers.seek(time); + showProgress(); + onPlay(); + } + + @Override + public void onSingleTap() { + if (isVisible(mBinding.control.getRoot())) hideControl(); + else showControl(); + } + + @Override + public void onDoubleTap() { + if (!isFullscreen()) { + App.post(this::enterFullscreen, 250); + } else if (mPlayers.isPlaying()) { + showControl(); + onPaused(); + } else { + hideControl(); + onPlay(); + } + } + + @Override + public void onShare(CharSequence title) { + mPlayers.share(this, title); + setRedirect(true); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode == RESULT_OK) mPlayers.checkData(data); + } + + @Override + protected void onUserLeaveHint() { + super.onUserLeaveHint(); + if (isRedirect()) return; + if (isLock()) App.post(this::onLock, 500); + if (mPlayers.haveTrack(C.TRACK_TYPE_VIDEO)) mPiP.enter(this, mPlayers.getVideoWidth(), mPlayers.getVideoHeight(), getScale()); + } + + @Override + public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode, @NonNull Configuration newConfig) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig); + if (!isFullscreen()) setVideoView(isInPictureInPictureMode); + if (isInPictureInPictureMode) { + hideControl(); + hideDanmaku(); + hideSheet(); + } else { + showDanmaku(); + if (isStop()) finish(); + } + } + + @Override + public void onConfigurationChanged(@NonNull Configuration newConfig) { + super.onConfigurationChanged(newConfig); + if (isAutoRotate() && isPort() && newConfig.orientation == Configuration.ORIENTATION_PORTRAIT && !isRotate()) exitFullscreen(); + if (isAutoRotate() && isPort() && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) enterFullscreen(); + if (isFullscreen()) Util.hideSystemUI(this); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (isFullscreen() && hasFocus) Util.hideSystemUI(this); + } + + @Override + protected void onStart() { + super.onStart(); + mClock.stop().start(); + setStop(false); + onPlay(); + } + + @Override + protected void onResume() { + super.onResume(); + if (isRedirect()) onPlay(); + setRedirect(false); + } + + @Override + protected void onPause() { + super.onPause(); + if (isRedirect()) onPaused(); + } + + @Override + protected void onStop() { + super.onStop(); + if (Setting.isBackgroundOff()) onPaused(); + if (Setting.isBackgroundOff()) mClock.stop(); + setStop(true); + } + + @Override + public void onBackPressed() { + if (isVisible(mBinding.control.getRoot())) { + hideControl(); + } else if (isFullscreen() && !isLock()) { + exitFullscreen(); + } else if (!isLock()) { + stopSearch(); + super.onBackPressed(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + stopSearch(); + mClock.release(); + mPlayers.release(); + Timer.get().reset(); + RefreshEvent.history(); + PlaybackService.stop(); + App.removeCallbacks(mR1, mR2, mR3, mR4); + mViewModel.result.removeObserver(mObserveDetail); + mViewModel.player.removeObserver(mObservePlayer); + mViewModel.search.removeObserver(mObserveSearch); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java new file mode 100644 index 00000000..59ca7777 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java @@ -0,0 +1,93 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.databinding.AdapterChannelBinding; + +import java.util.ArrayList; +import java.util.List; + +public class ChannelAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public ChannelAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(Channel item); + + boolean onLongClick(Channel item); + } + + public void clear() { + mItems.clear(); + notifyDataSetChanged(); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void remove(Channel item) { + int position = mItems.indexOf(item); + if (position == -1) return; + mItems.remove(position); + notifyItemRemoved(position); + } + + public void setSelected(int position) { + if (position == -1) return; + for (int i = 0; i < mItems.size(); i++) mItems.get(i).setSelected(i == position); + notifyItemRangeChanged(0, getItemCount()); + } + + public int setSelected(Channel channel) { + int position = mItems.indexOf(channel); + setSelected(position); + return position; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ChannelAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterChannelBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ChannelAdapter.ViewHolder holder, int position) { + Channel item = mItems.get(position); + item.loadLogo(holder.binding.logo); + holder.binding.name.setText(item.getName()); + holder.binding.number.setText(item.getNumber()); + holder.binding.getRoot().setSelected(item.isSelected()); + holder.binding.getRoot().setOnClickListener(view -> mListener.onItemClick(item)); + holder.binding.getRoot().setOnLongClickListener(view -> mListener.onLongClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterChannelBinding binding; + + ViewHolder(@NonNull AdapterChannelBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/CollectAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/CollectAdapter.java new file mode 100644 index 00000000..257e33c4 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/CollectAdapter.java @@ -0,0 +1,88 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Collect; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterCollectBinding; + +import java.util.ArrayList; +import java.util.List; + +public class CollectAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public CollectAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(int position, Collect item); + } + + public void clear() { + mItems.clear(); + mItems.add(Collect.all()); + notifyDataSetChanged(); + } + + public void add(Collect item) { + mItems.add(item); + notifyItemInserted(mItems.size() - 1); + } + + public void add(List items) { + mItems.get(0).getList().addAll(items); + } + + public int getPosition() { + for (int i = 0; i < mItems.size(); i++) if (mItems.get(i).isActivated()) return i; + return 0; + } + + public Collect getActivated() { + return mItems.get(getPosition()); + } + + public void setActivated(int position) { + for (int i = 0; i < mItems.size(); i++) mItems.get(i).setActivated(i == position); + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterCollectBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Collect item = mItems.get(position); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.text.setText(item.getSite().getName()); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(position, item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterCollectBinding binding; + + ViewHolder(@NonNull AdapterCollectBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java new file mode 100644 index 00000000..63ffad8d --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ConfigAdapter.java @@ -0,0 +1,73 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.AdapterConfigBinding; + +import java.util.List; + +public class ConfigAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private List mItems; + + public ConfigAdapter(OnClickListener listener) { + this.mListener = listener; + } + + public interface OnClickListener { + + void onTextClick(Config item); + + void onDeleteClick(Config item); + } + + public ConfigAdapter addAll(int type) { + mItems = Config.getAll(type); + mItems.remove(type == 0 ? VodConfig.get().getConfig() : LiveConfig.get().getConfig()); + return this; + } + + public int remove(Config item) { + item.delete(); + mItems.remove(item); + notifyDataSetChanged(); + return getItemCount(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterConfigBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Config item = mItems.get(position); + holder.binding.text.setText(item.getDesc()); + holder.binding.text.setOnClickListener(v -> mListener.onTextClick(item)); + holder.binding.delete.setOnClickListener(v -> mListener.onDeleteClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterConfigBinding binding; + + ViewHolder(@NonNull AdapterConfigBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java new file mode 100644 index 00000000..7be64f17 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/DeviceAdapter.java @@ -0,0 +1,93 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.databinding.AdapterDeviceBinding; + +import java.util.ArrayList; +import java.util.List; + +public class DeviceAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public DeviceAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(Device item); + + boolean onLongClick(Device item); + } + + public void addAll(List items) { + if (items == null) return; + mItems.removeAll(items); + mItems.addAll(items); + Device.Sorter.sort(mItems); + notifyDataSetChanged(); + } + + public void remove(Device item) { + if (item == null) return; + mItems.remove(item); + notifyDataSetChanged(); + } + + public void clear() { + mItems.clear(); + Device.delete(); + notifyDataSetChanged(); + } + + public List getIps() { + List ips = new ArrayList<>(); + for (Device item : mItems) if (item.isApp()) ips.add(item.getIp()); + return ips; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterDeviceBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Device item = mItems.get(position); + holder.binding.name.setText(item.getName()); + holder.binding.host.setText(item.getHost()); + holder.binding.type.setImageResource(getIcon(item)); + holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(item)); + holder.binding.getRoot().setOnLongClickListener(v -> mListener.onLongClick(item)); + } + + private int getIcon(Device item) { + return item.isMobile() ? R.drawable.ic_cast_mobile : R.drawable.ic_cast_tv; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterDeviceBinding binding; + + ViewHolder(@NonNull AdapterDeviceBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/EpgDataAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/EpgDataAdapter.java new file mode 100644 index 00000000..2117e008 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/EpgDataAdapter.java @@ -0,0 +1,81 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.EpgData; +import com.fongmi.android.tv.databinding.AdapterEpgDataBinding; + +import java.util.ArrayList; +import java.util.List; + +public class EpgDataAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public EpgDataAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(EpgData item); + } + + public void clear() { + mItems.clear(); + notifyDataSetChanged(); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void setSelected(EpgData item) { + setSelected(mItems.indexOf(item)); + } + + public void setSelected(int position) { + for (int i = 0; i < mItems.size(); i++) mItems.get(i).setSelected(i == position); + notifyItemRangeChanged(0, getItemCount()); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterEpgDataBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + EpgData item = mItems.get(position); + holder.binding.time.setText(item.getTime()); + holder.binding.title.setText(item.getTitle()); + holder.binding.getRoot().setSelected(item.isSelected()); + holder.binding.getRoot().setOnClickListener(view -> { + if (!item.isFuture()) mListener.onItemClick(item); + }); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterEpgDataBinding binding; + + ViewHolder(@NonNull AdapterEpgDataBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/EpisodeAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/EpisodeAdapter.java new file mode 100644 index 00000000..e870f0e0 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/EpisodeAdapter.java @@ -0,0 +1,110 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.AdapterEpisodeGridBinding; +import com.fongmi.android.tv.databinding.AdapterEpisodeHoriBinding; +import com.fongmi.android.tv.databinding.AdapterEpisodeVertBinding; +import com.fongmi.android.tv.ui.base.BaseEpisodeHolder; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.holder.EpisodeGridHolder; +import com.fongmi.android.tv.ui.holder.EpisodeHoriHolder; +import com.fongmi.android.tv.ui.holder.EpisodeVertHolder; + +import java.util.ArrayList; +import java.util.List; + +public class EpisodeAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private final int viewType; + + public EpisodeAdapter(OnClickListener listener, int viewType) { + this(listener, viewType, new ArrayList<>()); + } + + public EpisodeAdapter(OnClickListener listener, int viewType, ArrayList items) { + this.mListener = listener; + this.viewType = viewType; + this.mItems = items; + } + + public interface OnClickListener { + + void onItemClick(Episode item); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public int getPosition() { + for (int i = 0; i < mItems.size(); i++) if (mItems.get(i).isActivated()) return i; + return 0; + } + + public int getPosition(Episode item) { + return mItems.indexOf(item); + } + + public Episode getActivated() { + return mItems.get(getPosition()); + } + + public Episode getNext() { + int current = getPosition(); + int max = getItemCount() - 1; + current = ++current > max ? max : current; + return mItems.get(current); + } + + public Episode getPrev() { + int current = getPosition(); + current = --current < 0 ? 0 : current; + return mItems.get(current); + } + + public List getItems() { + return mItems; + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @Override + public int getItemViewType(int position) { + return viewType; + } + + @Override + public void onBindViewHolder(@NonNull BaseEpisodeHolder holder, int position) { + holder.initView(mItems.get(position)); + } + + @NonNull + @Override + public BaseEpisodeHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType) { + case ViewType.HORI: + return new EpisodeHoriHolder(AdapterEpisodeHoriBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener); + case ViewType.VERT: + return new EpisodeVertHolder(AdapterEpisodeVertBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener); + default: + return new EpisodeGridHolder(AdapterEpisodeGridBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener); + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FileAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FileAdapter.java new file mode 100644 index 00000000..8e060907 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FileAdapter.java @@ -0,0 +1,65 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.AdapterFileBinding; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class FileAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public FileAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(File file); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterFileBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + File file = mItems.get(position); + holder.binding.name.setText(file.getName()); + holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(file)); + holder.binding.image.setImageResource(file.isDirectory() ? R.drawable.ic_folder : R.drawable.ic_file); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterFileBinding binding; + + ViewHolder(@NonNull AdapterFileBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FilterAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FilterAdapter.java new file mode 100644 index 00000000..97c1f95a --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FilterAdapter.java @@ -0,0 +1,53 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Filter; +import com.fongmi.android.tv.databinding.AdapterFilterBinding; +import com.fongmi.android.tv.impl.FilterCallback; + +import java.util.List; + +public class FilterAdapter extends RecyclerView.Adapter { + + private final FilterCallback mListener; + private final List mItems; + + public FilterAdapter(FilterCallback listener, List items) { + this.mListener = listener; + this.mItems = items; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterFilterBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Filter item = mItems.get(position); + holder.binding.recycler.setHasFixedSize(true); + holder.binding.recycler.setItemAnimator(null); + holder.binding.recycler.setAdapter(new ValueAdapter(mListener, item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterFilterBinding binding; + + ViewHolder(@NonNull AdapterFilterBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FlagAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FlagAdapter.java new file mode 100644 index 00000000..71484bd1 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/FlagAdapter.java @@ -0,0 +1,97 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.bean.Flag; +import com.fongmi.android.tv.databinding.AdapterFlagBinding; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class FlagAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public FlagAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(Flag item); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public int getPosition() { + for (int i = 0; i < mItems.size(); i++) if (mItems.get(i).isActivated()) return i; + return 0; + } + + public Flag get(int position) { + return mItems.get(position); + } + + public Flag getActivated() { + return mItems.get(getPosition()); + } + + public void setActivated(Flag flag) { + if (!mItems.contains(flag)) flag.setFlag(mItems.get(0).getFlag()); + for (Flag item : mItems) item.setActivated(flag); + notifyItemRangeChanged(0, getItemCount()); + } + + public void toggle(Episode episode) { + for (Flag item : mItems) item.toggle(item.isActivated(), episode); + } + + public void reverse() { + for (Flag item : mItems) Collections.reverse(item.getEpisodes()); + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterFlagBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Flag item = mItems.get(position); + holder.binding.text.setText(item.getShow()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterFlagBinding binding; + + ViewHolder(@NonNull AdapterFlagBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/GroupAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/GroupAdapter.java new file mode 100644 index 00000000..4555cc6f --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/GroupAdapter.java @@ -0,0 +1,99 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.databinding.AdapterGroupBinding; + +import java.util.ArrayList; +import java.util.List; + +public class GroupAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public GroupAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void setWidth(Group item); + + void onItemClick(Group item); + } + + public void clear() { + mItems.clear(); + notifyDataSetChanged(); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void add(Group item) { + mItems.add(item); + notifyItemInserted(getItemCount() - 1); + } + + public Group get(int position) { + return mItems.get(position); + } + + public int getPosition() { + for (int i = 0; i < mItems.size(); i++) if (mItems.get(i).isSelected()) return i; + return 0; + } + + public int indexOf(Group group) { + return mItems.indexOf(group); + } + + public void setSelected(Group group) { + setSelected(indexOf(group)); + } + + public void setSelected(int position) { + for (int i = 0; i < mItems.size(); i++) mItems.get(i).setSelected(i == position); + notifyItemRangeChanged(0, getItemCount()); + mListener.setWidth(mItems.get(position)); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterGroupBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Group item = mItems.get(position); + holder.binding.name.setText(item.getName()); + holder.binding.getRoot().setSelected(item.isSelected()); + holder.binding.getRoot().setOnClickListener(view -> mListener.onItemClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterGroupBinding binding; + + ViewHolder(@NonNull AdapterGroupBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/HistoryAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/HistoryAdapter.java new file mode 100644 index 00000000..9bf5c4ee --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/HistoryAdapter.java @@ -0,0 +1,119 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.databinding.AdapterVodBinding; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.ResUtil; + +import java.util.ArrayList; +import java.util.List; + +public class HistoryAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private int width, height; + private boolean delete; + + public HistoryAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(History item); + + void onItemDelete(History item); + + boolean onLongClick(); + } + + public void setSize(int[] size) { + this.width = size[0]; + this.height = size[1]; + } + + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + notifyItemRangeChanged(0, mItems.size()); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void clear() { + mItems.clear(); + setDelete(false); + notifyDataSetChanged(); + History.delete(VodConfig.getCid()); + } + + public void remove(History item) { + int index = mItems.indexOf(item); + if (index == -1) return; + mItems.remove(index); + notifyItemRemoved(index); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ViewHolder holder = new ViewHolder(AdapterVodBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + holder.binding.getRoot().getLayoutParams().width = width; + holder.binding.getRoot().getLayoutParams().height = height; + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + History item = mItems.get(position); + holder.binding.name.setText(item.getVodName()); + holder.binding.site.setText(item.getSiteName()); + holder.binding.site.setVisibility(item.getSiteVisible()); + holder.binding.remark.setVisibility(delete ? View.GONE : View.VISIBLE); + holder.binding.delete.setVisibility(!delete ? View.GONE : View.VISIBLE); + holder.binding.remark.setText(ResUtil.getString(R.string.vod_last, item.getVodRemarks())); + ImgUtil.loadVod(item.getVodName(), item.getVodPic(), holder.binding.image); + setClickListener(holder.binding.getRoot(), item); + } + + private void setClickListener(View root, History item) { + root.setOnLongClickListener(view -> mListener.onLongClick()); + root.setOnClickListener(view -> { + if (isDelete()) mListener.onItemDelete(item); + else mListener.onItemClick(item); + }); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterVodBinding binding; + + ViewHolder(@NonNull AdapterVodBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/KeepAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/KeepAdapter.java new file mode 100644 index 00000000..c48ab3b1 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/KeepAdapter.java @@ -0,0 +1,115 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.databinding.AdapterVodBinding; +import com.fongmi.android.tv.utils.ImgUtil; + +import java.util.ArrayList; +import java.util.List; + +public class KeepAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private int width, height; + private boolean delete; + + public KeepAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(Keep item); + + void onItemDelete(Keep item); + + boolean onLongClick(); + } + + public void setSize(int[] size) { + this.width = size[0]; + this.height = size[1]; + } + + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + notifyItemRangeChanged(0, mItems.size()); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items); + notifyDataSetChanged(); + } + + public void clear() { + mItems.clear(); + setDelete(false); + notifyDataSetChanged(); + Keep.deleteAll(); + } + + public void remove(Keep item) { + int index = mItems.indexOf(item); + if (index == -1) return; + mItems.remove(index); + notifyItemRemoved(index); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + ViewHolder holder = new ViewHolder(AdapterVodBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + holder.binding.getRoot().getLayoutParams().width = width; + holder.binding.getRoot().getLayoutParams().height = height; + return holder; + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Keep item = mItems.get(position); + holder.binding.name.setText(item.getVodName()); + holder.binding.remark.setVisibility(View.GONE); + holder.binding.site.setVisibility(View.VISIBLE); + holder.binding.site.setText(item.getSiteName()); + holder.binding.delete.setVisibility(!delete ? View.GONE : View.VISIBLE); + ImgUtil.loadVod(item.getVodName(), item.getVodPic(), holder.binding.image); + setClickListener(holder.binding.getRoot(), item); + } + + private void setClickListener(View root, Keep item) { + root.setOnLongClickListener(view -> mListener.onLongClick()); + root.setOnClickListener(view -> { + if (isDelete()) mListener.onItemDelete(item); + else mListener.onItemClick(item); + }); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterVodBinding binding; + + ViewHolder(@NonNull AdapterVodBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/LiveAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/LiveAdapter.java new file mode 100644 index 00000000..bbaa4e17 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/LiveAdapter.java @@ -0,0 +1,81 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.databinding.AdapterLiveBinding; + +import java.util.List; + +public class LiveAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private boolean action; + + public LiveAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = LiveConfig.get().getLives(); + } + + public void setAction(boolean action) { + this.action = action; + } + + public interface OnClickListener { + + void onItemClick(Live item); + + void onBootClick(int position, Live item); + + void onPassClick(int position, Live item); + + boolean onBootLongClick(Live item); + + boolean onPassLongClick(Live item); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterLiveBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Live item = mItems.get(position); + holder.binding.text.setText(item.getName()); + holder.binding.text.setSelected(item.isActivated()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.boot.setImageResource(item.getBootIcon()); + holder.binding.pass.setImageResource(item.getPassIcon()); + holder.binding.boot.setVisibility(action ? View.VISIBLE : View.GONE); + holder.binding.pass.setVisibility(action ? View.VISIBLE : View.GONE); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(item)); + holder.binding.boot.setOnClickListener(v -> mListener.onBootClick(position, item)); + holder.binding.pass.setOnClickListener(v -> mListener.onPassClick(position, item)); + holder.binding.boot.setOnLongClickListener(v -> mListener.onBootLongClick(item)); + holder.binding.pass.setOnLongClickListener(v -> mListener.onPassLongClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterLiveBinding binding; + + ViewHolder(@NonNull AdapterLiveBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ParseAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ParseAdapter.java new file mode 100644 index 00000000..2742e2c6 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ParseAdapter.java @@ -0,0 +1,97 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Parse; +import com.fongmi.android.tv.databinding.AdapterParseDarkBinding; +import com.fongmi.android.tv.databinding.AdapterParseLightBinding; +import com.fongmi.android.tv.ui.base.ViewType; + +import java.util.List; + +public class ParseAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private final int viewType; + + public ParseAdapter(OnClickListener listener, int viewType) { + this.mItems = VodConfig.get().getParses(); + this.mListener = listener; + this.viewType = viewType; + } + + public interface OnClickListener { + + void onItemClick(Parse item); + } + + public int getPosition() { + for (int i = 0; i < mItems.size(); i++) if (mItems.get(i).isActivated()) return i; + return 0; + } + + public Parse get(int position) { + return mItems.get(position); + } + + public Parse first() { + return mItems.get(0); + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @Override + public int getItemViewType(int position) { + return viewType; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == ViewType.DARK) return new ViewHolder(AdapterParseDarkBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + return new ViewHolder(AdapterParseLightBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Parse item = mItems.get(position); + if (holder.darkBinding != null) holder.initView(holder.darkBinding.text, item); + if (holder.lightBinding != null) holder.initView(holder.lightBinding.text, item); + } + + class ViewHolder extends RecyclerView.ViewHolder { + + private AdapterParseDarkBinding darkBinding; + private AdapterParseLightBinding lightBinding; + + ViewHolder(@NonNull AdapterParseDarkBinding binding) { + super(binding.getRoot()); + this.darkBinding = binding; + } + + ViewHolder(@NonNull AdapterParseLightBinding binding) { + super(binding.getRoot()); + this.lightBinding = binding; + } + + void initView(TextView view, Parse item) { + view.setText(item.getName()); + view.setActivated(item.isActivated()); + view.setOnClickListener(v -> mListener.onItemClick(item)); + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/QualityAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/QualityAdapter.java new file mode 100644 index 00000000..abd009e2 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/QualityAdapter.java @@ -0,0 +1,71 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.databinding.AdapterQualityBinding; + +public class QualityAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private Result mResult; + private int position; + + public QualityAdapter(OnClickListener listener) { + this.mListener = listener; + this.mResult = Result.empty(); + } + + public interface OnClickListener { + + void onItemClick(Result result); + } + + public void addAll(Result result) { + mResult = result; + notifyDataSetChanged(); + } + + public int getPosition() { + return position; + } + + @Override + public int getItemCount() { + return mResult.getUrl().getValues().size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterQualityBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.binding.text.setText(mResult.getUrl().n(position)); + holder.binding.text.setOnClickListener(v -> onItemClick(position)); + holder.binding.text.setActivated(mResult.getUrl().getPosition() == position); + } + + private void onItemClick(int position) { + this.position = position; + mResult.getUrl().set(position); + mListener.onItemClick(mResult); + notifyItemRangeChanged(0, getItemCount()); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterQualityBinding binding; + + ViewHolder(@NonNull AdapterQualityBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/QuickAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/QuickAdapter.java new file mode 100644 index 00000000..a3bf4f50 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/QuickAdapter.java @@ -0,0 +1,83 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterQuickBinding; + +import java.util.ArrayList; +import java.util.List; + +public class QuickAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public QuickAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(Vod item); + } + + public void clear() { + mItems.clear(); + notifyDataSetChanged(); + } + + public void addAll(List items) { + int position = mItems.size() + 1; + mItems.addAll(items); + notifyItemRangeInserted(position, items.size()); + } + + public Vod get(int position) { + return mItems.get(position); + } + + public void remove(int position) { + mItems.remove(position); + notifyItemRemoved(position); + } + + public boolean isEmpty() { + return getItemCount() == 0; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterQuickBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Vod item = mItems.get(position); + holder.binding.name.setText(item.getVodName()); + holder.binding.site.setText(item.getSiteName()); + holder.binding.remark.setText(item.getVodRemarks()); + holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterQuickBinding binding; + + ViewHolder(@NonNull AdapterQuickBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RecordAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RecordAdapter.java new file mode 100644 index 00000000..2594e981 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RecordAdapter.java @@ -0,0 +1,91 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.AdapterCollectRecordBinding; +import com.google.gson.reflect.TypeToken; + +import java.util.ArrayList; +import java.util.List; + +public class RecordAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public RecordAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = getItems(); + this.mListener.onDataChanged(mItems.size()); + } + + public interface OnClickListener { + + void onItemClick(String text); + + void onDataChanged(int size); + } + + private List getItems() { + if (Setting.getKeyword().isEmpty()) return new ArrayList<>(); + return App.gson().fromJson(Setting.getKeyword(), new TypeToken>() {}.getType()); + } + + private void checkToAdd(String item) { + mItems.remove(item); + mItems.add(0, item); + if (mItems.size() > 8) mItems.remove(8); + } + + public void add(String item) { + checkToAdd(item); + notifyDataSetChanged(); + mListener.onDataChanged(getItemCount()); + Setting.putKeyword(App.gson().toJson(mItems)); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterCollectRecordBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + String text = mItems.get(position); + holder.binding.text.setText(text); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(text)); + } + + class ViewHolder extends RecyclerView.ViewHolder implements View.OnLongClickListener { + + private final AdapterCollectRecordBinding binding; + + ViewHolder(@NonNull AdapterCollectRecordBinding binding) { + super(binding.getRoot()); + this.binding = binding; + itemView.setOnLongClickListener(this); + } + + @Override + public boolean onLongClick(View v) { + mItems.remove(getLayoutPosition()); + notifyItemRemoved(getLayoutPosition()); + mListener.onDataChanged(getItemCount()); + Setting.putKeyword(App.gson().toJson(mItems)); + return true; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java new file mode 100644 index 00000000..d072c03c --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java @@ -0,0 +1,85 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.databinding.AdapterRestoreBinding; +import com.github.catvod.utils.Path; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +public class RestoreAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final SimpleDateFormat format; + private final List mItems; + + public RestoreAdapter(OnClickListener listener) { + this.format = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); + this.mItems = new ArrayList<>(); + this.mListener = listener; + this.addAll(); + } + + public interface OnClickListener { + + void onItemClick(File item); + + void onDeleteClick(File item); + } + + private void addAll() { + File[] files = Path.tv().listFiles(); + if (files == null) files = new File[0]; + for (File file : files) if (file.getName().startsWith("tv") && file.getName().endsWith(".bk.gz")) mItems.add(file); + if (!mItems.isEmpty()) Collections.sort(mItems, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified())); + notifyDataSetChanged(); + } + + public int remove(File item) { + int position = mItems.indexOf(item); + if (position == -1) return -1; + Path.clear(item); + mItems.remove(position); + notifyItemRemoved(position); + return getItemCount(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterRestoreBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + File item = mItems.get(position); + holder.binding.name.setText(item.getName()); + holder.binding.time.setText(format.format(item.lastModified())); + holder.binding.delete.setOnClickListener(v -> mListener.onDeleteClick(item)); + holder.binding.getRoot().setOnClickListener(v -> mListener.onItemClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterRestoreBinding binding; + + ViewHolder(@NonNull AdapterRestoreBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/SearchAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/SearchAdapter.java new file mode 100644 index 00000000..6f299ebf --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/SearchAdapter.java @@ -0,0 +1,92 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodOneBinding; +import com.fongmi.android.tv.databinding.AdapterVodRectBinding; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.holder.VodOneHolder; +import com.fongmi.android.tv.ui.holder.VodRectHolder; + +import java.util.ArrayList; +import java.util.List; + +public class SearchAdapter extends RecyclerView.Adapter { + + private final VodAdapter.OnClickListener mListener; + private final List mItems; + private int viewType; + private int[] size; + + public SearchAdapter(VodAdapter.OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public void setViewType(int viewType, int count) { + if (this.viewType > 0 && this.viewType != viewType && count == 1) notifyDataSetChanged(); + Setting.putViewType(viewType); + this.viewType = viewType; + } + + public void setSize(int[] size) { + this.size = size; + } + + public int getWidth() { + return size[0]; + } + + public boolean isList() { + return viewType == ViewType.LIST; + } + + public boolean isGrid() { + return viewType == ViewType.GRID; + } + + public void setAll(List items) { + clear().addAll(items); + } + + public void addAll(List items) { + int position = mItems.size() + 1; + mItems.addAll(items); + notifyItemRangeInserted(position, items.size()); + } + + public SearchAdapter clear() { + mItems.clear(); + notifyDataSetChanged(); + return this; + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @Override + public int getItemViewType(int position) { + return viewType; + } + + @Override + public void onBindViewHolder(@NonNull BaseVodHolder holder, int position) { + holder.initView(mItems.get(position)); + } + + @NonNull + @Override + public BaseVodHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + if (viewType == ViewType.LIST) return new VodOneHolder(AdapterVodOneBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener); + else return new VodRectHolder(AdapterVodRectBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener).size(size); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/SiteAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/SiteAdapter.java new file mode 100644 index 00000000..0c775733 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/SiteAdapter.java @@ -0,0 +1,110 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.AdapterSiteBinding; + +import java.util.ArrayList; +import java.util.List; + +public class SiteAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private boolean search; + private boolean change; + + public SiteAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + this.addAll(); + } + + public SiteAdapter search(boolean search) { + this.search = search; + return this; + } + + public SiteAdapter change(boolean change) { + this.change = change; + return this; + } + + private void addAll() { + for (Site site : VodConfig.get().getSites()) if (!site.isHide()) mItems.add(site); + } + + public List getItems() { + return mItems; + } + + public interface OnClickListener { + + void onTextClick(Site item); + + void onSearchClick(int position, Site item); + + void onChangeClick(int position, Site item); + + boolean onSearchLongClick(Site item); + + boolean onChangeLongClick(Site item); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterSiteBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Site item = mItems.get(position); + boolean on = !search || change; + holder.binding.text.setText(item.getName()); + holder.binding.text.setEnabled(on); + holder.binding.text.setFocusable(on); + holder.binding.text.setSelected(on && item.isActivated()); + holder.binding.text.setActivated(on && item.isActivated()); + holder.binding.search.setImageResource(getSearchIcon(item)); + holder.binding.change.setImageResource(getChangeIcon(item)); + holder.binding.search.setVisibility(search ? View.VISIBLE : View.GONE); + holder.binding.change.setVisibility(change ? View.VISIBLE : View.GONE); + holder.binding.text.setOnClickListener(v -> mListener.onTextClick(item)); + holder.binding.search.setOnClickListener(v -> mListener.onSearchClick(position, item)); + holder.binding.change.setOnClickListener(v -> mListener.onChangeClick(position, item)); + holder.binding.search.setOnLongClickListener(v -> mListener.onSearchLongClick(item)); + holder.binding.change.setOnLongClickListener(v -> mListener.onChangeLongClick(item)); + } + + private int getSearchIcon(Site item) { + return item.isSearchable() ? R.drawable.ic_site_search : R.drawable.ic_site_block; + } + + private int getChangeIcon(Site item) { + return item.isChangeable() ? R.drawable.ic_site_change : R.drawable.ic_site_block; + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterSiteBinding binding; + + ViewHolder(@NonNull AdapterSiteBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java new file mode 100644 index 00000000..fd90a30c --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/TypeAdapter.java @@ -0,0 +1,90 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.Class; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.databinding.AdapterTypeBinding; +import com.fongmi.android.tv.utils.ResUtil; + +import java.util.ArrayList; +import java.util.List; + +public class TypeAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public TypeAdapter(OnClickListener listener) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + } + + public interface OnClickListener { + + void onItemClick(int position, Class item); + } + + private Class home() { + Class type = new Class(); + type.setTypeName(ResUtil.getString(R.string.vod_home)); + type.setTypeId("home"); + return type; + } + + public void clear() { + mItems.clear(); + notifyDataSetChanged(); + } + + public void addAll(Result result) { + mItems.addAll(result.getTypes()); + if (!result.getList().isEmpty()) mItems.add(0, home()); + if (!mItems.isEmpty()) mItems.get(0).setActivated(true); + notifyDataSetChanged(); + } + + public void setActivated(int position) { + for (Class item : mItems) item.setActivated(false); + mItems.get(position).setActivated(true); + notifyItemRangeChanged(0, mItems.size()); + } + + public Class get(int position) { + return mItems.get(position); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterTypeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Class item = mItems.get(position); + holder.binding.text.setText(item.getTypeName()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(position, item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterTypeBinding binding; + + ViewHolder(@NonNull AdapterTypeBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ValueAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ValueAdapter.java new file mode 100644 index 00000000..c33bccb8 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ValueAdapter.java @@ -0,0 +1,62 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Filter; +import com.fongmi.android.tv.bean.Value; +import com.fongmi.android.tv.databinding.AdapterValueBinding; +import com.fongmi.android.tv.impl.FilterCallback; + +import java.util.List; + +public class ValueAdapter extends RecyclerView.Adapter { + + private final FilterCallback mListener; + private final List mItems; + private final String mKey; + + public ValueAdapter(FilterCallback listener, Filter filter) { + this.mListener = listener; + this.mItems = filter.getValue(); + this.mKey = filter.getKey(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterValueBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + Value item = mItems.get(position); + holder.binding.text.setText(item.getN()); + holder.binding.text.setActivated(item.isActivated()); + holder.binding.text.setOnClickListener(v -> onItemClick(item)); + } + + private void onItemClick(Value value) { + for (Value item : mItems) item.setActivated(value); + notifyItemRangeChanged(0, getItemCount()); + mListener.setFilter(mKey, value); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterValueBinding binding; + + ViewHolder(@NonNull AdapterValueBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/VodAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/VodAdapter.java new file mode 100644 index 00000000..5db77ac5 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/VodAdapter.java @@ -0,0 +1,86 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodListBinding; +import com.fongmi.android.tv.databinding.AdapterVodOvalBinding; +import com.fongmi.android.tv.databinding.AdapterVodRectBinding; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.holder.VodListHolder; +import com.fongmi.android.tv.ui.holder.VodOvalHolder; +import com.fongmi.android.tv.ui.holder.VodRectHolder; + +import java.util.ArrayList; +import java.util.List; + +public class VodAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + private final Style style; + private final int[] size; + + public VodAdapter(OnClickListener listener, Style style, int[] size) { + this.mListener = listener; + this.mItems = new ArrayList<>(); + this.style = style; + this.size = size; + } + + public interface OnClickListener { + + void onItemClick(Vod item); + + boolean onLongClick(Vod item); + } + + public Style getStyle() { + return style; + } + + public void addAll(List items) { + int position = mItems.size() + 1; + mItems.addAll(items); + notifyItemRangeInserted(position, items.size()); + } + + public void clear() { + mItems.clear(); + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @Override + public int getItemViewType(int position) { + return style.getViewType(); + } + + @Override + public void onBindViewHolder(@NonNull BaseVodHolder holder, int position) { + holder.initView(mItems.get(position)); + } + + @NonNull + @Override + public BaseVodHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType) { + case ViewType.LIST: + return new VodListHolder(AdapterVodListBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener); + case ViewType.OVAL: + return new VodOvalHolder(AdapterVodOvalBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener).size(size); + default: + return new VodRectHolder(AdapterVodRectBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false), mListener).size(size); + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/WordAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/WordAdapter.java new file mode 100644 index 00000000..08e9e440 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/WordAdapter.java @@ -0,0 +1,62 @@ +package com.fongmi.android.tv.ui.adapter; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.databinding.AdapterCollectWordBinding; + +import java.util.ArrayList; +import java.util.List; + +public class WordAdapter extends RecyclerView.Adapter { + + private final OnClickListener mListener; + private final List mItems; + + public WordAdapter(OnClickListener listener) { + this.mItems = new ArrayList<>(); + this.mListener = listener; + } + + public interface OnClickListener { + + void onItemClick(String text); + } + + public void addAll(List items) { + mItems.clear(); + mItems.addAll(items.subList(0, Math.min(items.size(), 20))); + notifyDataSetChanged(); + } + + @Override + public int getItemCount() { + return mItems.size(); + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new ViewHolder(AdapterCollectWordBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + String item = mItems.get(position); + holder.binding.text.setText(item); + holder.binding.text.setOnClickListener(v -> mListener.onItemClick(item)); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final AdapterCollectWordBinding binding; + + ViewHolder(@NonNull AdapterCollectWordBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java new file mode 100644 index 00000000..c9622e04 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java @@ -0,0 +1,140 @@ +package com.fongmi.android.tv.ui.base; + +import android.app.Activity; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.os.Bundle; +import android.view.DisplayCutout; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +import androidx.activity.OnBackPressedCallback; +import androidx.appcompat.app.AppCompatActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.ResUtil; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; + +public abstract class BaseActivity extends AppCompatActivity { + + protected abstract ViewBinding getBinding(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (transparent()) setTransparent(this); + setContentView(getBinding().getRoot()); + EventBus.getDefault().register(this); + initView(savedInstanceState); + setBackCallback(); + initEvent(); + } + + @Override + public void setContentView(View view) { + super.setContentView(view); + refreshWall(); + } + + protected Activity getActivity() { + return this; + } + + protected boolean transparent() { + return true; + } + + protected boolean customWall() { + return true; + } + + protected boolean handleBack() { + return false; + } + + protected void initView(Bundle savedInstanceState) { + } + + protected void initEvent() { + } + + protected void onBackPress() { + } + + protected boolean isVisible(View view) { + return view.getVisibility() == View.VISIBLE; + } + + protected boolean isGone(View view) { + return view.getVisibility() == View.GONE; + } + + protected void setPadding(ViewGroup layout) { + setPadding(layout, false); + } + + protected void setPadding(ViewGroup layout, boolean leftOnly) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) return; + DisplayCutout cutout = ResUtil.getDisplay(this).getCutout(); + if (cutout == null) return; + int top = cutout.getSafeInsetTop(); + int left = cutout.getSafeInsetLeft(); + int right = cutout.getSafeInsetRight(); + int bottom = cutout.getSafeInsetBottom(); + int padding = left | right | top | bottom; + layout.setPadding(padding, 0, leftOnly ? 0 : padding, 0); + } + + protected void noPadding(ViewGroup layout) { + layout.setPadding(0, 0, 0, 0); + } + + private void setBackCallback() { + getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(handleBack()) { + @Override + public void handleOnBackPressed() { + onBackPress(); + } + }); + } + + private void setTransparent(Activity activity) { + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + activity.getWindow().setStatusBarColor(Color.TRANSPARENT); + } + + private void refreshWall() { + try { + if (!customWall()) return; + File file = FileUtil.getWall(Setting.getWall()); + if (file.exists() && file.length() > 0) getWindow().setBackgroundDrawable(Drawable.createFromPath(file.getAbsolutePath())); + else getWindow().setBackgroundDrawableResource(ResUtil.getDrawable(file.getName())); + } catch (Exception e) { + getWindow().setBackgroundDrawableResource(R.drawable.wallpaper_4); + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType() == RefreshEvent.Type.WALL) refreshWall(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseEpisodeHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseEpisodeHolder.java new file mode 100644 index 00000000..32559409 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseEpisodeHolder.java @@ -0,0 +1,17 @@ +package com.fongmi.android.tv.ui.base; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Episode; + +public abstract class BaseEpisodeHolder extends RecyclerView.ViewHolder { + + public BaseEpisodeHolder(@NonNull View itemView) { + super(itemView); + } + + public abstract void initView(Episode item); +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseFragment.java new file mode 100644 index 00000000..177f9452 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseFragment.java @@ -0,0 +1,61 @@ +package com.fongmi.android.tv.ui.base; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.viewbinding.ViewBinding; + +public abstract class BaseFragment extends Fragment { + + protected abstract ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container); + + private boolean init; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + return getBinding(inflater, container).getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + initView(); + initEvent(); + } + + protected void initView() { + } + + protected void initEvent() { + } + + protected void initData() { + } + + private void onVisible() { + if (init) return; + initData(); + init = true; + } + + public boolean canBack() { + return true; + } + + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + if (isVisibleToUser) if (isResumed()) onVisible(); + } + + @Override + public void onResume() { + super.onResume(); + if (getUserVisibleHint()) onVisible(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseVodHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseVodHolder.java new file mode 100644 index 00000000..29f29c7a --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseVodHolder.java @@ -0,0 +1,17 @@ +package com.fongmi.android.tv.ui.base; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Vod; + +public abstract class BaseVodHolder extends RecyclerView.ViewHolder { + + public BaseVodHolder(@NonNull View itemView) { + super(itemView); + } + + public abstract void initView(Vod item); +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/base/ViewType.java b/app/src/mobile/java/com/fongmi/android/tv/ui/base/ViewType.java new file mode 100644 index 00000000..3472d491 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/base/ViewType.java @@ -0,0 +1,15 @@ +package com.fongmi.android.tv.ui.base; + +public class ViewType { + + public static final int RECT = 0; + public static final int OVAL = 1; + public static final int LIST = 2; + public static final int GRID = 3; + + public static final int HORI = 0; + public static final int VERT = 1; + + public static final int DARK = 0; + public static final int LIGHT = 1; +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomFabBehavior.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomFabBehavior.java new file mode 100644 index 00000000..143c5fa8 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomFabBehavior.java @@ -0,0 +1,54 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.view.ViewCompat; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; + +public class CustomFabBehavior extends FloatingActionButton.Behavior { + + public CustomFabBehavior() { + super(); + } + + public CustomFabBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) { + return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type); + } + + @Override + public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull FloatingActionButton child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, @NonNull int[] consumed) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed); + if ("top".equals(child.getTag())) { + if (dyConsumed > 0 && child.getVisibility() == View.INVISIBLE) { + child.show(); + } else if (dyConsumed < 0 && child.getVisibility() == View.VISIBLE) { + onHide(child); + } + } else { + if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { + onHide(child); + } else if (dyConsumed < 0 && child.getVisibility() == View.INVISIBLE) { + child.show(); + } + } + } + + private void onHide(FloatingActionButton child) { + child.hide(new FloatingActionButton.OnVisibilityChangedListener() { + @Override + public void onHidden(FloatingActionButton fab) { + child.setVisibility(View.INVISIBLE); + } + }); + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomKeyDownLive.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomKeyDownLive.java new file mode 100644 index 00000000..ea9e9606 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomKeyDownLive.java @@ -0,0 +1,249 @@ +package com.fongmi.android.tv.ui.custom; + +import android.app.Activity; +import android.content.Context; +import android.media.AudioManager; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Util; + +public class CustomKeyDownLive extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener { + + private static final int DISTANCE = 250; + private static final int VELOCITY = 10; + + private final ScaleGestureDetector scaleDetector; + private final GestureDetector detector; + private final AudioManager manager; + private final Listener listener; + private final Activity activity; + private final View videoView; + private boolean changeBright; + private boolean changeVolume; + private boolean changeSpeed; + private boolean changeScale; + private boolean changeTime; + private boolean animating; + private boolean center; + private boolean touch; + private boolean lock; + private float bright; + private float volume; + private float scale; + private long time; + + public static CustomKeyDownLive create(Activity activity, View videoView) { + return new CustomKeyDownLive(activity, videoView); + } + + private CustomKeyDownLive(Activity activity, View videoView) { + this.manager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); + this.scaleDetector = new ScaleGestureDetector(activity, this); + this.detector = new GestureDetector(activity, this); + this.listener = (Listener) activity; + this.videoView = videoView; + this.activity = activity; + this.scale = 1.0f; + } + + public boolean onTouchEvent(MotionEvent e) { + if (changeTime && e.getAction() == MotionEvent.ACTION_UP) onSeekEnd(); + if (changeSpeed && e.getAction() == MotionEvent.ACTION_UP) listener.onSpeedEnd(); + if (changeBright && e.getAction() == MotionEvent.ACTION_UP) listener.onBrightEnd(); + if (changeVolume && e.getAction() == MotionEvent.ACTION_UP) listener.onVolumeEnd(); + return e.getPointerCount() == 2 ? scaleDetector.onTouchEvent(e) : detector.onTouchEvent(e); + } + + public void resetScale() { + if (scale == 1.0f) return; + videoView.animate().scaleX(1.0f).scaleY(1.0f).translationX(0f).translationY(0f).setDuration(250).withEndAction(() -> { + videoView.setPivotY(videoView.getHeight() / 2f); + videoView.setPivotX(videoView.getWidth() / 2f); + scale = 1.0f; + }).start(); + } + + public void setLock(boolean lock) { + this.lock = lock; + } + + public float getScale() { + return scale; + } + + private boolean isEdge(MotionEvent e) { + return ResUtil.isEdge(activity, e, ResUtil.dp2px(24)); + } + + @Override + public boolean onDown(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || lock || e.getPointerCount() > 1) return true; + volume = manager.getStreamVolume(AudioManager.STREAM_MUSIC); + bright = Util.getBrightness(activity); + changeBright = false; + changeVolume = false; + changeSpeed = false; + changeTime = false; + center = false; + touch = true; + return true; + } + + @Override + public void onLongPress(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || lock || e.getPointerCount() > 1) return; + changeSpeed = true; + listener.onSpeedUp(); + } + + @Override + public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { + if (isEdge(e1) || changeScale || lock || e1.getPointerCount() > 1) return true; + float deltaX = e2.getX() - e1.getX(); + float deltaY = e1.getY() - e2.getY(); + if (touch) checkFunc(distanceX, distanceY, e2); + if (changeTime) listener.onSeek(time = (long) (deltaX * 50)); + if (changeBright) setBright(deltaY); + if (changeVolume) setVolume(deltaY); + return true; + } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || e.getPointerCount() > 1) return true; + listener.onDoubleTap(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || e.getPointerCount() > 1) return true; + int half = ResUtil.getScreenWidth(activity) / 2; + if (e.getX() > half || lock) listener.onDoubleTap(); + else listener.onSingleTap(); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { + if (isEdge(e1) || changeScale || !center || animating || e1.getPointerCount() > 1) return true; + checkFunc(e1, e2, velocityX, velocityY); + return true; + } + + private void onSeekEnd() { + listener.onSeekEnd(time); + changeTime = false; + time = 0; + } + + private void checkFunc(float distanceX, float distanceY, MotionEvent e2) { + int four = ResUtil.getScreenWidth(activity) / 4; + if (e2.getX() > four && e2.getX() < four * 3) center = true; + else if (Math.abs(distanceX) < Math.abs(distanceY)) checkSide(e2); + if (Math.abs(distanceX) >= Math.abs(distanceY)) changeTime = true; + touch = false; + } + + private void checkFunc(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (e1.getX() - e2.getX() > DISTANCE && Math.abs(velocityX) > VELOCITY) { + listener.onFlingLeft(); + } else if (e2.getX() - e1.getX() > DISTANCE && Math.abs(velocityX) > VELOCITY) { + listener.onFlingRight(); + } else if (e1.getY() - e2.getY() > DISTANCE && Math.abs(velocityY) > VELOCITY) { + videoView.animate().translationYBy(-ResUtil.dp2px(24)).setDuration(150).withStartAction(() -> animating = true).withEndAction(() -> videoView.animate().translationY(0).setDuration(100).withStartAction(listener::onFlingUp).withEndAction(() -> animating = false).start()).start(); + } else if (e2.getY() - e1.getY() > DISTANCE && Math.abs(velocityY) > VELOCITY) { + videoView.animate().translationYBy(ResUtil.dp2px(24)).setDuration(150).withStartAction(() -> animating = true).withEndAction(() -> videoView.animate().translationY(0).setDuration(100).withStartAction(listener::onFlingDown).withEndAction(() -> animating = false).start()).start(); + } + } + + private void checkSide(MotionEvent e2) { + int half = ResUtil.getScreenWidth(activity) / 2; + if (e2.getX() > half) changeVolume = true; + else changeBright = true; + } + + private void setBright(float deltaY) { + if (bright == -1.0f) bright = 0.5f; + int height = videoView.getMeasuredHeight(); + float brightness = deltaY * 2 / height + bright; + if (brightness < 0) brightness = 0f; + if (brightness > 1.0f) brightness = 1.0f; + WindowManager.LayoutParams attributes = activity.getWindow().getAttributes(); + attributes.screenBrightness = brightness; + activity.getWindow().setAttributes(attributes); + listener.onBright((int) (brightness * 100)); + } + + private void setVolume(float deltaY) { + int height = videoView.getMeasuredHeight(); + int maxVolume = manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + float deltaV = deltaY * 2 / height * maxVolume; + float index = volume + deltaV; + if (index > maxVolume) index = maxVolume; + if (index < 0) index = 0; + manager.setStreamVolume(AudioManager.STREAM_MUSIC, (int) index, 0); + listener.onVolume((int) (index / maxVolume * 100)); + } + + @Override + public boolean onScaleBegin(@NonNull ScaleGestureDetector detector) { + if (changeBright || changeVolume || changeSpeed || changeTime || lock) return changeScale = false; + return changeScale = true; + } + + @Override + public void onScaleEnd(@NonNull ScaleGestureDetector detector) { + App.post(() -> changeScale = false, 500); + } + + @Override + public boolean onScale(@NonNull ScaleGestureDetector detector) { + scale *= detector.getScaleFactor(); + scale = Math.max(1.0f, Math.min(scale, 5.0f)); + videoView.setPivotX(detector.getFocusX()); + videoView.setPivotY(detector.getFocusY()); + videoView.setScaleX(scale); + videoView.setScaleY(scale); + return true; + } + + public interface Listener { + + void onSpeedUp(); + + void onSpeedEnd(); + + void onBright(int progress); + + void onBrightEnd(); + + void onVolume(int progress); + + void onVolumeEnd(); + + void onFlingUp(); + + void onFlingDown(); + + void onFlingLeft(); + + void onFlingRight(); + + void onSeek(long time); + + void onSeekEnd(long time); + + void onSingleTap(); + + void onDoubleTap(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomKeyDownVod.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomKeyDownVod.java new file mode 100644 index 00000000..ce371d73 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomKeyDownVod.java @@ -0,0 +1,239 @@ +package com.fongmi.android.tv.ui.custom; + +import android.app.Activity; +import android.content.Context; +import android.media.AudioManager; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.ScaleGestureDetector; +import android.view.View; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Util; + +public class CustomKeyDownVod extends GestureDetector.SimpleOnGestureListener implements ScaleGestureDetector.OnScaleGestureListener { + + private static final int DISTANCE = 250; + private static final int VELOCITY = 10; + + private final ScaleGestureDetector scaleDetector; + private final GestureDetector detector; + private final AudioManager manager; + private final Listener listener; + private final Activity activity; + private final View videoView; + private boolean changeBright; + private boolean changeVolume; + private boolean changeSpeed; + private boolean changeScale; + private boolean changeTime; + private boolean animating; + private boolean center; + private boolean touch; + private boolean lock; + private float bright; + private float volume; + private float scale; + private long time; + + public static CustomKeyDownVod create(Activity activity, View videoView) { + return new CustomKeyDownVod(activity, videoView); + } + + private CustomKeyDownVod(Activity activity, View videoView) { + this.manager = (AudioManager) activity.getSystemService(Context.AUDIO_SERVICE); + this.scaleDetector = new ScaleGestureDetector(activity, this); + this.detector = new GestureDetector(activity, this); + this.listener = (Listener) activity; + this.videoView = videoView; + this.activity = activity; + this.scale = 1.0f; + } + + public boolean onTouchEvent(MotionEvent e) { + if (changeTime && e.getAction() == MotionEvent.ACTION_UP) onSeekEnd(); + if (changeSpeed && e.getAction() == MotionEvent.ACTION_UP) listener.onSpeedEnd(); + if (changeBright && e.getAction() == MotionEvent.ACTION_UP) listener.onBrightEnd(); + if (changeVolume && e.getAction() == MotionEvent.ACTION_UP) listener.onVolumeEnd(); + return e.getPointerCount() == 2 ? scaleDetector.onTouchEvent(e) : detector.onTouchEvent(e); + } + + public void resetScale() { + if (scale == 1.0f) return; + videoView.animate().scaleX(1.0f).scaleY(1.0f).translationX(0f).translationY(0f).setDuration(250).withEndAction(() -> { + videoView.setPivotY(videoView.getHeight() / 2f); + videoView.setPivotX(videoView.getWidth() / 2f); + scale = 1.0f; + }).start(); + } + + public void setLock(boolean lock) { + this.lock = lock; + } + + public float getScale() { + return scale; + } + + private boolean isEdge(MotionEvent e) { + return ResUtil.isEdge(activity, e, ResUtil.dp2px(24)); + } + + @Override + public boolean onDown(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || lock || e.getPointerCount() > 1) return true; + volume = manager.getStreamVolume(AudioManager.STREAM_MUSIC); + bright = Util.getBrightness(activity); + changeBright = false; + changeVolume = false; + changeSpeed = false; + changeTime = false; + center = false; + touch = true; + return true; + } + + @Override + public void onLongPress(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || lock || e.getPointerCount() > 1) return; + changeSpeed = true; + listener.onSpeedUp(); + } + + @Override + public boolean onScroll(MotionEvent e1, @NonNull MotionEvent e2, float distanceX, float distanceY) { + if (isEdge(e1) || changeScale || lock || e1.getPointerCount() > 1) return true; + float deltaX = e2.getX() - e1.getX(); + float deltaY = e1.getY() - e2.getY(); + if (touch) checkFunc(distanceX, distanceY, e2); + if (changeTime) listener.onSeek(time = (long) (deltaX * 50)); + if (changeBright) setBright(deltaY); + if (changeVolume) setVolume(deltaY); + return true; + } + + @Override + public boolean onDoubleTap(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || e.getPointerCount() > 1) return true; + if (!lock) listener.onDoubleTap(); + return true; + } + + @Override + public boolean onSingleTapConfirmed(@NonNull MotionEvent e) { + if (isEdge(e) || changeScale || e.getPointerCount() > 1) return true; + listener.onSingleTap(); + return true; + } + + @Override + public boolean onFling(MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { + if (isEdge(e1) || changeScale || !center || animating || e1.getPointerCount() > 1) return true; + checkFunc(e1, e2, velocityY); + return true; + } + + private void onSeekEnd() { + listener.onSeekEnd(time); + changeTime = false; + time = 0; + } + + private void checkFunc(float distanceX, float distanceY, MotionEvent e2) { + int four = ResUtil.getScreenWidth(activity) / 4; + if (e2.getX() > four && e2.getX() < four * 3) center = true; + else if (Math.abs(distanceX) < Math.abs(distanceY)) checkSide(e2); + if (Math.abs(distanceX) >= Math.abs(distanceY)) changeTime = true; + touch = false; + } + + private void checkFunc(MotionEvent e1, MotionEvent e2, float velocityY) { + if (e1.getY() - e2.getY() > DISTANCE && Math.abs(velocityY) > VELOCITY) { + videoView.animate().translationYBy(-ResUtil.dp2px(24)).setDuration(150).withStartAction(() -> animating = true).withEndAction(() -> videoView.animate().translationY(0).setDuration(100).withStartAction(listener::onFlingUp).withEndAction(() -> animating = false).start()).start(); + } else if (e2.getY() - e1.getY() > DISTANCE && Math.abs(velocityY) > VELOCITY) { + videoView.animate().translationYBy(ResUtil.dp2px(24)).setDuration(150).withStartAction(() -> animating = true).withEndAction(() -> videoView.animate().translationY(0).setDuration(100).withStartAction(listener::onFlingDown).withEndAction(() -> animating = false).start()).start(); + } + } + + private void checkSide(MotionEvent e2) { + int half = ResUtil.getScreenWidth(activity) / 2; + if (e2.getX() > half) changeVolume = true; + else changeBright = true; + } + + private void setBright(float deltaY) { + if (bright == -1.0f) bright = 0.5f; + int height = videoView.getMeasuredHeight(); + float brightness = deltaY * 2 / height + bright; + if (brightness < 0) brightness = 0f; + if (brightness > 1.0f) brightness = 1.0f; + WindowManager.LayoutParams attributes = activity.getWindow().getAttributes(); + attributes.screenBrightness = brightness; + activity.getWindow().setAttributes(attributes); + listener.onBright((int) (brightness * 100)); + } + + private void setVolume(float deltaY) { + int height = videoView.getMeasuredHeight(); + int maxVolume = manager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); + float deltaV = deltaY * 2 / height * maxVolume; + float index = volume + deltaV; + if (index > maxVolume) index = maxVolume; + if (index < 0) index = 0; + manager.setStreamVolume(AudioManager.STREAM_MUSIC, (int) index, 0); + listener.onVolume((int) (index / maxVolume * 100)); + } + + @Override + public boolean onScaleBegin(@NonNull ScaleGestureDetector detector) { + if (changeBright || changeVolume || changeSpeed || changeTime || lock) return changeScale = false; + return changeScale = true; + } + + @Override + public void onScaleEnd(@NonNull ScaleGestureDetector detector) { + App.post(() -> changeScale = false, 500); + } + + @Override + public boolean onScale(@NonNull ScaleGestureDetector detector) { + scale *= detector.getScaleFactor(); + scale = Math.max(1.0f, Math.min(scale, 5.0f)); + videoView.setPivotX(detector.getFocusX()); + videoView.setPivotY(detector.getFocusY()); + videoView.setScaleX(scale); + videoView.setScaleY(scale); + return true; + } + + public interface Listener { + + void onSpeedUp(); + + void onSpeedEnd(); + + void onBright(int progress); + + void onBrightEnd(); + + void onVolume(int progress); + + void onVolumeEnd(); + + void onFlingUp(); + + void onFlingDown(); + + void onSeek(long time); + + void onSeekEnd(long time); + + void onSingleTap(); + + void onDoubleTap(); + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomScroller.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomScroller.java new file mode 100644 index 00000000..2c2938f7 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomScroller.java @@ -0,0 +1,69 @@ +package com.fongmi.android.tv.ui.custom; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.fongmi.android.tv.bean.Result; + +public class CustomScroller extends RecyclerView.OnScrollListener { + + private final Callback callback; + private boolean loading; + private boolean enable; + private int page; + + public CustomScroller(Callback callback) { + this.callback = callback; + this.enable = true; + this.page = 1; + } + + @Override + public void onScrolled(@NonNull RecyclerView view, int dx, int dy) { + if (isDisable() || isLoading() || view.getScrollState() == RecyclerView.SCROLL_STATE_IDLE || callback == null) return; + if (!view.canScrollVertically(1) && dy > 0) callback.onLoadMore(String.valueOf(++page)); + } + + public void reset() { + enable = true; + page = 1; + } + + public int addPage() { + return ++page; + } + + public boolean first() { + return page == 1; + } + + public void setPage(int page) { + this.page = page; + } + + public boolean isLoading() { + return loading; + } + + public void setLoading(boolean loading) { + this.loading = loading; + } + + public boolean isDisable() { + return !enable; + } + + public void setEnable(int pageCount) { + this.enable = page < pageCount || pageCount == 0; + } + + public void endLoading(Result result) { + if (result.getList().isEmpty()) page--; + setEnable(result.getPageCount()); + setLoading(false); + } + + public interface Callback { + void onLoadMore(String page); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomTextListener.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomTextListener.java new file mode 100644 index 00000000..9dcd8074 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomTextListener.java @@ -0,0 +1,19 @@ +package com.fongmi.android.tv.ui.custom; + +import android.text.Editable; +import android.text.TextWatcher; + +public class CustomTextListener implements TextWatcher { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomViewPager.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomViewPager.java new file mode 100644 index 00000000..4bef29a5 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/CustomViewPager.java @@ -0,0 +1,29 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.MotionEvent; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewpager.widget.ViewPager; + +public class CustomViewPager extends ViewPager { + + public CustomViewPager(@NonNull Context context) { + super(context); + } + + public CustomViewPager(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + try { + return super.onInterceptTouchEvent(ev); + } catch (Exception e) { + return false; + } + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/FragmentStateManager.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/FragmentStateManager.java new file mode 100644 index 00000000..6988df5f --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/FragmentStateManager.java @@ -0,0 +1,53 @@ +package com.fongmi.android.tv.ui.custom; + +import android.view.ViewGroup; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; + +import com.fongmi.android.tv.ui.base.BaseFragment; + +public abstract class FragmentStateManager { + + private final FragmentManager fm; + private final ViewGroup container; + + public FragmentStateManager(ViewGroup container, FragmentManager fm) { + this.container = container; + this.fm = fm; + } + + public abstract Fragment getItem(int position); + + public boolean change(int position) { + FragmentTransaction ft = fm.beginTransaction(); + Fragment fragment = fm.findFragmentByTag(getTag(position)); + if (fragment == null) ft.add(container.getId(), fragment = getItem(position), getTag(position)); + else ft.show(fragment); + Fragment current = fm.getPrimaryNavigationFragment(); + if (current != null) ft.hide(current); + ft.setPrimaryNavigationFragment(fragment); + ft.setReorderingAllowed(true); + ft.commitNowAllowingStateLoss(); + return true; + } + + private String getTag(int position) { + return "android:switcher:" + position; + } + + public BaseFragment getFragment(int position) { + return (BaseFragment) fm.findFragmentByTag(getTag(position)); + } + + public boolean isVisible(int position) { + Fragment fragment = getFragment(position); + return fragment != null && fragment.isVisible(); + } + + public boolean canBack(int position) { + BaseFragment fragment = getFragment(position); + return fragment != null && (fragment.canBack() || fragment.isHidden()); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/MarqueeLayout.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/MarqueeLayout.java new file mode 100644 index 00000000..ec95950c --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/MarqueeLayout.java @@ -0,0 +1,41 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; + +import com.fongmi.android.tv.R; + +public class MarqueeLayout extends FrameLayout { + + private MarqueeTextView text; + + public MarqueeLayout(Context context) { + super(context); + initView(); + } + + public MarqueeLayout(Context context, AttributeSet attrs) { + super(context, attrs); + initView(); + } + + public MarqueeLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initView(); + } + + private void initView() { + LayoutInflater.from(getContext()).inflate(R.layout.view_marquee, this); + text = findViewById(R.id.text); + } + + public void setText(String text) { + this.text.setText(text); + } + + public void setText(int resId) { + this.text.setText(resId); + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/custom/MarqueeTextView.java b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/MarqueeTextView.java new file mode 100644 index 00000000..026f2490 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/custom/MarqueeTextView.java @@ -0,0 +1,35 @@ +package com.fongmi.android.tv.ui.custom; + +import android.content.Context; +import android.util.AttributeSet; + +import androidx.appcompat.widget.AppCompatTextView; + +public class MarqueeTextView extends AppCompatTextView { + + public MarqueeTextView(Context context) { + super(context); + init(); + } + + public MarqueeTextView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public MarqueeTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setSingleLine(); + setEllipsize(android.text.TextUtils.TruncateAt.MARQUEE); + setMarqueeRepeatLimit(-1); + } + + @Override + public boolean isFocused() { + return true; + } +} \ No newline at end of file diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/BufferDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/BufferDialog.java new file mode 100644 index 00000000..2cc3da78 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/BufferDialog.java @@ -0,0 +1,54 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogBufferBinding; +import com.fongmi.android.tv.impl.BufferCallback; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class BufferDialog { + + private final DialogBufferBinding binding; + private final BufferCallback callback; + private int value; + + public static BufferDialog create(Fragment fragment) { + return new BufferDialog(fragment); + } + + public BufferDialog(Fragment fragment) { + this.callback = (BufferCallback) fragment; + this.binding = DialogBufferBinding.inflate(LayoutInflater.from(fragment.getContext())); + } + + public void show() { + initDialog(); + initView(); + } + + private void initDialog() { + AlertDialog dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setTitle(R.string.player_buffer).setView(binding.getRoot()).setPositiveButton(R.string.dialog_positive, this::onPositive).setNegativeButton(R.string.dialog_negative, this::onNegative).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + binding.slider.setValue(value = Setting.getBuffer()); + } + + private void onPositive(DialogInterface dialog, int which) { + callback.setBuffer((int) binding.slider.getValue()); + dialog.dismiss(); + } + + private void onNegative(DialogInterface dialog, int which) { + callback.setBuffer(value); + dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/CastDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/CastDialog.java new file mode 100644 index 00000000..9f2231c9 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/CastDialog.java @@ -0,0 +1,253 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.android.cast.dlna.dmc.DLNACastManager; +import com.android.cast.dlna.dmc.OnDeviceRegistryListener; +import com.android.cast.dlna.dmc.control.DeviceControl; +import com.android.cast.dlna.dmc.control.OnDeviceControlListener; +import com.android.cast.dlna.dmc.control.ServiceActionCallback; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.CastVideo; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.databinding.DialogDeviceBinding; +import com.fongmi.android.tv.event.ScanEvent; +import com.fongmi.android.tv.server.Server; +import com.fongmi.android.tv.ui.activity.ScanActivity; +import com.fongmi.android.tv.ui.adapter.DeviceAdapter; +import com.fongmi.android.tv.utils.DLNADevice; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ScanTask; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Path; +import com.github.catvod.utils.Util; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import org.fourthline.cling.support.lastchange.EventedValue; +import org.fourthline.cling.support.model.TransportState; +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.IOException; +import java.util.List; + +import kotlin.Unit; +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +public class CastDialog extends BaseDialog implements DeviceAdapter.OnClickListener, ScanTask.Listener, OnDeviceRegistryListener, OnDeviceControlListener, ServiceActionCallback, okhttp3.Callback { + + private final FormBody.Builder body; + private final OkHttpClient client; + private final ScanTask scanTask; + + private DialogDeviceBinding binding; + private DeviceAdapter adapter; + private DeviceControl control; + private Listener listener; + private CastVideo video; + private boolean fm; + + public static CastDialog create() { + return new CastDialog(); + } + + public CastDialog() { + scanTask = new ScanTask(this); + body = new FormBody.Builder(); + body.add("device", Device.get().toString()); + body.add("config", Config.vod().toString()); + client = OkHttp.client(Constant.TIMEOUT_SYNC); + } + + public CastDialog history(History history) { + String id = history.getVodId(); + String fd = history.getVodId(); + if (fd.startsWith("/")) fd = Server.get().getAddress() + "/file" + fd.replace(Path.rootPath(), ""); + if (fd.startsWith("file")) fd = Server.get().getAddress() + "/" + fd.replace(Path.rootPath(), "").replace("://", ""); + if (fd.contains("127.0.0.1")) fd = fd.replace("127.0.0.1", Util.getIp()); + body.add("history", history.toString().replace(id, fd)); + return this; + } + + public CastDialog video(CastVideo video) { + this.video = video; + return this; + } + + public CastDialog fm(boolean fm) { + this.fm = fm; + return this; + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + this.listener = (Listener) activity; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogDeviceBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + binding.scan.setVisibility(fm ? View.VISIBLE : View.GONE); + EventBus.getDefault().register(this); + setRecyclerView(); + getDevice(); + initDLNA(); + } + + @Override + protected void initEvent() { + binding.scan.setOnClickListener(v -> onScan()); + binding.refresh.setOnClickListener(v -> onRefresh()); + } + + private void setRecyclerView() { + binding.recycler.setHasFixedSize(true); + binding.recycler.setAdapter(adapter = new DeviceAdapter(this)); + } + + private void getDevice() { + if (fm) adapter.addAll(Device.getAll()); + adapter.addAll(DLNADevice.get().getAll()); + } + + private void initDLNA() { + DLNACastManager.INSTANCE.bindCastService(App.get()); + DLNACastManager.INSTANCE.registerDeviceListener(this); + } + + private void onScan() { + ScanActivity.start(getActivity()); + } + + private void onRefresh() { + if (fm) scanTask.start(adapter.getIps()); + DLNACastManager.INSTANCE.search(null); + adapter.clear(); + } + + private void onCasted() { + listener.onCasted(); + dismiss(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onScanEvent(ScanEvent event) { + scanTask.start(event.getAddress()); + } + + @Override + public void onFind(List devices) { + if (!devices.isEmpty()) adapter.addAll(devices); + } + + @Override + public void onDeviceAdded(@NonNull org.fourthline.cling.model.meta.Device device) { + adapter.addAll(DLNADevice.get().add(device)); + } + + @Override + public void onDeviceRemoved(@NonNull org.fourthline.cling.model.meta.Device device) { + adapter.remove(DLNADevice.get().remove(device)); + } + + @Override + public void onConnected(@NonNull org.fourthline.cling.model.meta.Device device) { + control.setAVTransportURI(video.getUrl(), video.getName(), this); + } + + @Override + public void onDisconnected(@NonNull org.fourthline.cling.model.meta.Device device) { + Notify.show(R.string.device_offline); + } + + @Override + public void onSuccess(Unit unit) { + control.seek(video.getPosition(), null); + control.play("1", null); + onCasted(); + } + + @Override + public void onFailure(@NonNull String s) { + Notify.show(s); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + App.post(() -> Notify.show(e.getMessage())); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + if (response.body().string().equals("OK")) App.post(this::onCasted); + else App.post(() -> Notify.show(R.string.device_offline)); + } + + @Override + public void onItemClick(Device item) { + if (item.isDLNA()) control = DLNACastManager.INSTANCE.connectDevice(DLNADevice.get().find(item), this); + else OkHttp.newCall(client, item.getIp().concat("/action?do=cast"), body.build()).enqueue(this); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + DLNADevice.get().disconnect(); + EventBus.getDefault().unregister(this); + DLNACastManager.INSTANCE.unregisterListener(this); + DLNACastManager.INSTANCE.unbindCastService(App.get()); + } + + @Override + public void onDestroy() { + super.onDestroy(); + scanTask.stop(); + } + + @Override + public void onAvTransportStateChanged(@NonNull TransportState state) { + } + + @Override + public void onEventChanged(@NonNull EventedValue event) { + } + + @Override + public void onRendererVolumeChanged(int volume) { + } + + @Override + public void onRendererVolumeMuteChanged(boolean mute) { + } + + @Override + public boolean onLongClick(Device item) { + return false; + } + + public interface Listener { + + void onCasted(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java new file mode 100644 index 00000000..e61daa8d --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ConfigDialog.java @@ -0,0 +1,156 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.EditorInfo; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.DialogConfigBinding; +import com.fongmi.android.tv.impl.ConfigCallback; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.fongmi.android.tv.utils.FileChooser; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class ConfigDialog { + + private final DialogConfigBinding binding; + private final ConfigCallback callback; + private final Fragment fragment; + private AlertDialog dialog; + private boolean append; + private boolean edit; + private String ori; + private int type; + + public static ConfigDialog create(Fragment fragment) { + return new ConfigDialog(fragment); + } + + public ConfigDialog type(int type) { + this.type = type; + return this; + } + + public ConfigDialog edit() { + this.edit = true; + return this; + } + + public ConfigDialog(Fragment fragment) { + this.fragment = fragment; + this.callback = (ConfigCallback) fragment; + this.binding = DialogConfigBinding.inflate(LayoutInflater.from(fragment.getContext())); + this.append = true; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setTitle(type == 0 ? R.string.setting_vod : type == 1 ? R.string.setting_live : R.string.setting_wall).setView(binding.getRoot()).setPositiveButton(edit ? R.string.dialog_edit : R.string.dialog_positive, this::onPositive).setNegativeButton(R.string.dialog_negative, this::onNegative).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + binding.name.setText(getConfig().getName()); + binding.url.setText(ori = getConfig().getUrl()); + binding.input.setVisibility(edit ? View.VISIBLE : View.GONE); + binding.url.setSelection(TextUtils.isEmpty(ori) ? 0 : ori.length()); + } + + private void initEvent() { + binding.choose.setEndIconOnClickListener(this::onChoose); + binding.paste.setOnClickListener(this::onPaste); + binding.url.addTextChangedListener(new CustomTextListener() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + detect(s.toString()); + } + }); + binding.url.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + return true; + }); + } + + private Config getConfig() { + switch (type) { + case 0: + return VodConfig.get().getConfig(); + case 1: + return LiveConfig.get().getConfig(); + case 2: + return WallConfig.get().getConfig(); + default: + return null; + } + } + + private void onChoose(View view) { + FileChooser.from(fragment).show(); + dialog.dismiss(); + } + + private void onPaste(View view) { + try { + ClipboardManager clipboard = (ClipboardManager) fragment.getContext().getSystemService(Context.CLIPBOARD_SERVICE); + if (clipboard != null && clipboard.hasPrimaryClip() && clipboard.getPrimaryClip() != null) { + CharSequence text = clipboard.getPrimaryClip().getItemAt(0).getText(); + if (text != null) { + binding.url.setText(text); + binding.url.setSelection(text.length()); + } + } + } catch (Exception e) { + Toast.makeText(fragment.getContext(), e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + private void detect(String s) { + if (append && "h".equalsIgnoreCase(s)) { + append = false; + binding.url.append("ttp://"); + } else if (append && "f".equalsIgnoreCase(s)) { + append = false; + binding.url.append("ile://"); + } else if (append && "a".equalsIgnoreCase(s)) { + append = false; + binding.url.append("ssets://"); + } else if (s.length() > 1) { + append = false; + } else if (s.length() == 0) { + append = true; + } + } + + private void onPositive(DialogInterface dialog, int which) { + String url = binding.url.getText().toString().trim(); + String name = binding.name.getText().toString().trim(); + if (edit) Config.find(ori, type).url(url).name(name).update(); + if (url.isEmpty()) Config.delete(ori, type); + callback.setConfig(Config.find(url, type)); + dialog.dismiss(); + } + + private void onNegative(DialogInterface dialog, int which) { + dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java new file mode 100644 index 00000000..953ab5ea --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ControlDialog.java @@ -0,0 +1,208 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.bean.Parse; +import com.fongmi.android.tv.databinding.ActivityVideoBinding; +import com.fongmi.android.tv.databinding.DialogControlBinding; +import com.fongmi.android.tv.player.Players; +import com.fongmi.android.tv.ui.adapter.ParseAdapter; +import com.fongmi.android.tv.ui.base.ViewType; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.Timer; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.slider.Slider; + +import java.util.Arrays; +import java.util.List; + +public class ControlDialog extends BaseDialog implements ParseAdapter.OnClickListener { + + private DialogControlBinding binding; + private ActivityVideoBinding parent; + private FragmentActivity activity; + private List scales; + private final String[] scale; + private Listener listener; + private History history; + private Players player; + private boolean parse; + + public static ControlDialog create() { + return new ControlDialog(); + } + + public ControlDialog() { + this.scale = ResUtil.getStringArray(R.array.select_scale); + } + + public ControlDialog parent(ActivityVideoBinding parent) { + this.parent = parent; + return this; + } + + public ControlDialog history(History history) { + this.history = history; + return this; + } + + public ControlDialog player(Players player) { + this.player = player; + return this; + } + + public ControlDialog parse(boolean parse) { + this.parse = parse; + return this; + } + + public ControlDialog show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return this; + show(activity.getSupportFragmentManager(), null); + this.listener = (Listener) activity; + this.activity = activity; + return this; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + binding = DialogControlBinding.inflate(inflater, container, false); + scales = Arrays.asList(binding.scale0, binding.scale1, binding.scale2, binding.scale3, binding.scale4); + return binding; + } + + @Override + protected void initView() { + if (player == null) dismiss(); + if (player == null) return; + binding.decode.setText(parent.control.action.decode.getText()); + binding.ending.setText(parent.control.action.ending.getText()); + binding.opening.setText(parent.control.action.opening.getText()); + binding.loop.setActivated(parent.control.action.loop.isActivated()); + binding.timer.setActivated(Timer.get().isRunning()); + setTrackVisible(); + setScaleText(); + setPlayer(); + setParse(); + } + + @Override + protected void initEvent() { + binding.timer.setOnClickListener(this::onTimer); + binding.speed.addOnChangeListener(this::setSpeed); + for (TextView view : scales) view.setOnClickListener(this::setScale); + binding.text.setOnClickListener(v -> dismiss(parent.control.action.text)); + binding.audio.setOnClickListener(v -> dismiss(parent.control.action.audio)); + binding.video.setOnClickListener(v -> dismiss(parent.control.action.video)); + binding.player.setOnClickListener(v -> dismiss(parent.control.action.player)); + binding.danmaku.setOnClickListener(v -> dismiss(parent.control.action.danmaku)); + binding.loop.setOnClickListener(v -> active(binding.loop, parent.control.action.loop)); + binding.decode.setOnClickListener(v -> click(binding.decode, parent.control.action.decode)); + binding.ending.setOnClickListener(v -> click(binding.ending, parent.control.action.ending)); + binding.opening.setOnClickListener(v -> click(binding.opening, parent.control.action.opening)); + binding.player.setOnLongClickListener(v -> longClick(binding.player, parent.control.action.player)); + binding.ending.setOnLongClickListener(v -> longClick(binding.ending, parent.control.action.ending)); + binding.opening.setOnLongClickListener(v -> longClick(binding.opening, parent.control.action.opening)); + } + + private void onTimer(View view) { + App.post(() -> TimerDialog.create().show(activity), 200); + dismiss(); + } + + private void setSpeed(@NonNull Slider slider, float value, boolean fromUser) { + parent.control.action.speed.setText(player.setSpeed(value)); + if (history != null) history.setSpeed(player.getSpeed()); + } + + private void setScaleText() { + for (int i = 0; i < scales.size(); i++) { + scales.get(i).setText(scale[i]); + scales.get(i).setActivated(scales.get(i).getText().equals(parent.control.action.scale.getText())); + } + } + + private void setParse() { + setParseVisible(parse); + binding.parse.setHasFixedSize(true); + binding.parse.setItemAnimator(null); + binding.parse.addItemDecoration(new SpaceItemDecoration(8)); + binding.parse.setAdapter(new ParseAdapter(this, ViewType.LIGHT)); + } + + private void setScale(View view) { + for (TextView textView : scales) textView.setActivated(false); + listener.onScale(Integer.parseInt(view.getTag().toString())); + view.setActivated(true); + } + + private void active(View view, TextView target) { + target.performClick(); + view.setActivated(target.isActivated()); + } + + private void click(TextView view, TextView target) { + target.performClick(); + view.setText(target.getText()); + } + + private boolean longClick(TextView view, TextView target) { + target.performLongClick(); + view.setText(target.getText()); + return true; + } + + private void dismiss(View view) { + App.post(view::performClick, 200); + dismiss(); + } + + public void updateParse() { + binding.parse.getAdapter().notifyItemRangeChanged(0, binding.parse.getAdapter().getItemCount()); + } + + public void setPlayer() { + binding.speed.setValue(Math.max(player.getSpeed(), 0.5f)); + binding.player.setText(parent.control.action.player.getText()); + binding.decode.setVisibility(parent.control.action.decode.getVisibility()); + binding.danmaku.setVisibility(parent.control.action.danmaku.getVisibility()); + } + + public void setParseVisible(boolean visible) { + binding.parse.setVisibility(visible ? View.VISIBLE : View.GONE); + binding.parseText.setVisibility(visible ? View.VISIBLE : View.GONE); + } + + public void setTrackVisible() { + binding.text.setVisibility(parent.control.action.text.getVisibility()); + binding.audio.setVisibility(parent.control.action.audio.getVisibility()); + binding.video.setVisibility(parent.control.action.video.getVisibility()); + binding.track.setVisibility(binding.text.getVisibility() == View.GONE && binding.audio.getVisibility() == View.GONE && binding.video.getVisibility() == View.GONE ? View.GONE : View.VISIBLE); + } + + @Override + public void onItemClick(Parse item) { + listener.onParse(item); + binding.parse.getAdapter().notifyItemRangeChanged(0, binding.parse.getAdapter().getItemCount()); + } + + public interface Listener { + + void onScale(int tag); + + void onParse(Parse item); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/EpisodeGridDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/EpisodeGridDialog.java new file mode 100644 index 00000000..7a94d06e --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/EpisodeGridDialog.java @@ -0,0 +1,117 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; +import androidx.viewpager2.adapter.FragmentStateAdapter; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.DialogEpisodeGridBinding; +import com.fongmi.android.tv.ui.fragment.EpisodeFragment; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.tabs.TabLayoutMediator; + +import java.util.ArrayList; +import java.util.List; + +public class EpisodeGridDialog extends BaseDialog { + + private DialogEpisodeGridBinding binding; + private List episodes; + private final List titles; + private boolean reverse; + private int spanCount; + private int itemCount; + + public static EpisodeGridDialog create() { + return new EpisodeGridDialog(); + } + + public EpisodeGridDialog() { + this.titles = new ArrayList<>(); + this.spanCount = 5; + } + + public EpisodeGridDialog reverse(boolean reverse) { + this.reverse = reverse; + return this; + } + + public EpisodeGridDialog episodes(List episodes) { + this.episodes = episodes; + return this; + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogEpisodeGridBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + setSpanCount(); + setTitles(); + setPager(); + } + + private void setSpanCount() { + int total = 0; + int row = ResUtil.isLand(getActivity()) ? 5 : 10; + for (Episode item : episodes) total += item.getName().length(); + int offset = (int) Math.ceil((double) total / episodes.size()); + if (offset >= 12) spanCount = 1; + else if (offset >= 8) spanCount = 2; + else if (offset >= 4) spanCount = 3; + else if (offset >= 2) spanCount = 4; + itemCount = spanCount * row; + } + + private void setTitles() { + if (reverse) for (int i = episodes.size(); i > 0; i -= itemCount) titles.add(i + " - " + Math.max(i - itemCount - 1, 1)); + else for (int i = 0; i < episodes.size(); i += itemCount) titles.add((i + 1) + " - " + Math.min(i + itemCount, episodes.size())); + } + + private void setPager() { + binding.pager.setAdapter(new PageAdapter(getActivity())); + new TabLayoutMediator(binding.tabs, binding.pager, (tab, position) -> tab.setText(titles.get(position))).attach(); + setCurrentPage(); + } + + private void setCurrentPage() { + for (int i = 0; i < episodes.size(); i++) { + if (episodes.get(i).isActivated()) { + binding.pager.setCurrentItem(i / itemCount); + break; + } + } + } + + class PageAdapter extends FragmentStateAdapter { + + public PageAdapter(@NonNull FragmentActivity activity) { + super(activity); + } + + @NonNull + @Override + public Fragment createFragment(int position) { + return EpisodeFragment.newInstance(spanCount, episodes.subList(position * itemCount, Math.min(position * itemCount + itemCount, episodes.size()))); + } + + @Override + public int getItemCount() { + return titles.size(); + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/EpisodeListDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/EpisodeListDialog.java new file mode 100644 index 00000000..9ffe8e6b --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/EpisodeListDialog.java @@ -0,0 +1,80 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.ViewModelProvider; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.DialogEpisodeListBinding; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; +import com.fongmi.android.tv.ui.base.ViewType; +import com.google.android.material.sidesheet.SideSheetDialog; + +import java.util.List; + +public class EpisodeListDialog implements EpisodeAdapter.OnClickListener { + + private final FragmentActivity activity; + private DialogEpisodeListBinding binding; + private List episodes; + private SiteViewModel viewModel; + private EpisodeAdapter adapter; + private SideSheetDialog dialog; + + public static EpisodeListDialog create(FragmentActivity activity) { + return new EpisodeListDialog(activity); + } + + public EpisodeListDialog(FragmentActivity activity) { + this.activity = activity; + } + + public EpisodeListDialog episodes(List episodes) { + this.episodes = episodes; + return this; + } + + public SideSheetDialog show() { + initDialog(); + initView(); + return dialog; + } + + private void initDialog() { + binding = DialogEpisodeListBinding.inflate(LayoutInflater.from(activity)); + dialog = new SideSheetDialog(activity); + dialog.setContentView(binding.getRoot()); + dialog.getBehavior().setDraggable(false); + dialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); + dialog.show(); + } + + private void initView() { + setRecyclerView(); + setViewModel(); + setEpisode(); + } + + private void setRecyclerView() { + binding.recycler.setHasFixedSize(true); + binding.recycler.setItemAnimator(null); + binding.recycler.setAdapter(adapter = new EpisodeAdapter(this, ViewType.GRID)); + } + + private void setViewModel() { + viewModel = new ViewModelProvider(activity).get(SiteViewModel.class); + } + + private void setEpisode() { + adapter.addAll(episodes); + binding.recycler.scrollToPosition(adapter.getPosition()); + } + + @Override + public void onItemClick(Episode item) { + viewModel.setEpisode(item); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/FilterDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/FilterDialog.java new file mode 100644 index 00000000..553934ec --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/FilterDialog.java @@ -0,0 +1,50 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.bean.Filter; +import com.fongmi.android.tv.databinding.DialogFilterBinding; +import com.fongmi.android.tv.impl.FilterCallback; +import com.fongmi.android.tv.ui.adapter.FilterAdapter; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import java.util.List; + +public class FilterDialog extends BaseDialog { + + private DialogFilterBinding binding; + private FilterCallback callback; + private List filter; + + public static FilterDialog create() { + return new FilterDialog(); + } + + public FilterDialog filter(List filter) { + this.filter = filter; + return this; + } + + public void show(Fragment fragment) { + for (Fragment f : fragment.getChildFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(fragment.getChildFragmentManager(), null); + this.callback = (FilterCallback) fragment; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogFilterBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + binding.recycler.setAdapter(new FilterAdapter(callback, filter)); + binding.recycler.setHasFixedSize(true); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java new file mode 100644 index 00000000..5494ebf5 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/HistoryDialog.java @@ -0,0 +1,67 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.databinding.DialogHistoryBinding; +import com.fongmi.android.tv.impl.ConfigCallback; +import com.fongmi.android.tv.ui.adapter.ConfigAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class HistoryDialog implements ConfigAdapter.OnClickListener { + + private final DialogHistoryBinding binding; + private final ConfigCallback callback; + private final ConfigAdapter adapter; + private final AlertDialog dialog; + private int type; + + public static HistoryDialog create(Fragment fragment) { + return new HistoryDialog(fragment); + } + + public HistoryDialog type(int type) { + this.type = type; + return this; + } + + public HistoryDialog(Fragment fragment) { + this.callback = (ConfigCallback) fragment; + this.binding = DialogHistoryBinding.inflate(LayoutInflater.from(fragment.getContext())); + this.dialog = new MaterialAlertDialogBuilder(fragment.getActivity()).setView(binding.getRoot()).create(); + this.adapter = new ConfigAdapter(this); + } + + public void show() { + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setHasFixedSize(true); + binding.recycler.setAdapter(adapter.addAll(type)); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 8)); + binding.recycler.post(() -> binding.recycler.scrollToPosition(0)); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onTextClick(Config item) { + callback.setConfig(item); + dialog.dismiss(); + } + + @Override + public void onDeleteClick(Config item) { + if (adapter.remove(item) == 0) dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/InfoDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/InfoDialog.java new file mode 100644 index 00000000..6f756990 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/InfoDialog.java @@ -0,0 +1,96 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.databinding.DialogInfoBinding; +import com.fongmi.android.tv.utils.Util; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import java.util.Map; + +public class InfoDialog { + + private final DialogInfoBinding binding; + private final Listener callback; + private AlertDialog dialog; + private CharSequence title; + private String header; + private String url; + + public static InfoDialog create(Activity activity) { + return new InfoDialog(activity); + } + + public InfoDialog(Activity activity) { + this.binding = DialogInfoBinding.inflate(LayoutInflater.from(activity)); + this.callback = (Listener) activity; + } + + public InfoDialog title(CharSequence title) { + this.title = title; + return this; + } + + public InfoDialog headers(Map headers) { + StringBuilder sb = new StringBuilder(); + for (String key : headers.keySet()) sb.append(key).append(" : ").append(headers.get(key)).append("\n"); + this.header = Util.substring(sb.toString()); + return this; + } + + public InfoDialog url(String url) { + this.url = fix(url); + return this; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setView(binding.getRoot()).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + binding.url.setText(url); + binding.title.setText(title); + binding.header.setText(header); + binding.title.setSingleLine(title.toString().contains(url)); + binding.url.setVisibility(TextUtils.isEmpty(url) ? View.GONE : View.VISIBLE); + binding.header.setVisibility(TextUtils.isEmpty(header) ? View.GONE : View.VISIBLE); + } + + private void initEvent() { + binding.url.setOnClickListener(this::onShare); + binding.url.setOnLongClickListener(v -> onCopy(url)); + binding.header.setOnLongClickListener(v -> onCopy(header)); + } + + private String fix(String url) { + return TextUtils.isEmpty(url) ? "" : url.startsWith("data") ? url.substring(0, Math.min(url.length(), 128)).concat("...") : url; + } + + private void onShare(View view) { + callback.onShare(title); + dialog.dismiss(); + } + + private boolean onCopy(String text) { + Util.copy(text); + return true; + } + + public interface Listener { + + void onShare(CharSequence title); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/LinkDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/LinkDialog.java new file mode 100644 index 00000000..9e92db3b --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/LinkDialog.java @@ -0,0 +1,76 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.text.InputFilter; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.databinding.DialogLinkBinding; +import com.fongmi.android.tv.ui.activity.VideoActivity; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.Sniffer; +import com.fongmi.android.tv.utils.Util; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class LinkDialog { + + private final DialogLinkBinding binding; + private final Fragment fragment; + private AlertDialog dialog; + + public static LinkDialog create(Fragment fragment) { + return new LinkDialog(fragment); + } + + public LinkDialog(Fragment fragment) { + this.fragment = fragment; + this.binding = DialogLinkBinding.inflate(LayoutInflater.from(fragment.getContext())); + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setTitle(R.string.play).setView(binding.getRoot()).setPositiveButton(R.string.dialog_positive, this::onPositive).setNegativeButton(R.string.dialog_negative, this::onNegative).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + CharSequence text = Util.getClipText(); + binding.text.setFilters(new InputFilter[]{new InputFilter.LengthFilter(Integer.MAX_VALUE)}); + if (!TextUtils.isEmpty(text)) binding.text.setText(Sniffer.getUrl(text.toString())); + } + + private void initEvent() { + binding.input.setEndIconOnClickListener(this::onChoose); + binding.text.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + return true; + }); + } + + private void onChoose(View view) { + FileChooser.from(fragment).show(); + dialog.dismiss(); + } + + private void onPositive(DialogInterface dialog, int which) { + String text = binding.text.getText().toString().trim(); + if (!text.isEmpty()) VideoActivity.start(fragment.getActivity(), text); + dialog.dismiss(); + } + + private void onNegative(DialogInterface dialog, int which) { + dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/LiveDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/LiveDialog.java new file mode 100644 index 00000000..8c4bcb9b --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/LiveDialog.java @@ -0,0 +1,113 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.WindowManager; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.databinding.DialogLiveBinding; +import com.fongmi.android.tv.impl.LiveCallback; +import com.fongmi.android.tv.ui.adapter.LiveAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class LiveDialog implements LiveAdapter.OnClickListener { + + private final LiveCallback callback; + private DialogLiveBinding binding; + private LiveAdapter adapter; + private AlertDialog dialog; + private boolean full; + + public static LiveDialog create(Activity activity) { + return new LiveDialog(activity); + } + + public static LiveDialog create(Fragment fragment) { + return new LiveDialog(fragment); + } + + private LiveDialog(Activity activity) { + this.callback = (LiveCallback) activity; + this.full = true; + init(activity); + } + + private LiveDialog(Fragment fragment) { + this.callback = (LiveCallback) fragment; + init(fragment.getActivity()); + } + + private void init(Activity activity) { + this.binding = DialogLiveBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.adapter = new LiveAdapter(this); + } + + public LiveDialog action() { + adapter.setAction(true); + return this; + } + + public void show() { + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setAdapter(adapter); + binding.recycler.setItemAnimator(null); + binding.recycler.setHasFixedSize(true); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 8)); + binding.recycler.post(() -> binding.recycler.scrollToPosition(LiveConfig.getHomeIndex())); + if (full) binding.recycler.setMaxHeight(264); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); + if (full && ResUtil.isLand(dialog.getContext())) params.width = (int) (ResUtil.getScreenWidth() * 0.5f); + dialog.getWindow().setAttributes(params); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onItemClick(Live item) { + callback.setLive(item); + dialog.dismiss(); + } + + @Override + public void onBootClick(int position, Live item) { + item.boot(!item.isBoot()).save(); + adapter.notifyItemChanged(position); + } + + @Override + public void onPassClick(int position, Live item) { + item.pass(!item.isPass()).save(); + adapter.notifyItemChanged(position); + } + + @Override + public boolean onBootLongClick(Live item) { + boolean result = !item.isBoot(); + for (Live live : LiveConfig.get().getLives()) live.boot(result).save(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + return true; + } + + @Override + public boolean onPassLongClick(Live item) { + boolean result = !item.isPass(); + for (Live live : LiveConfig.get().getLives()) live.pass(result).save(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + return true; + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/PassDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/PassDialog.java new file mode 100644 index 00000000..5e1e724b --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/PassDialog.java @@ -0,0 +1,61 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.ViewGroup; +import android.view.inputmethod.EditorInfo; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.DialogPassBinding; +import com.fongmi.android.tv.impl.PassCallback; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +public class PassDialog extends BaseDialog { + + private DialogPassBinding binding; + private PassCallback callback; + + public static PassDialog create() { + return new PassDialog(); + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + this.callback = (PassCallback) activity; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogPassBinding.inflate(inflater, container, false); + } + + @Override + protected void initEvent() { + binding.pass.setOnEditorActionListener(this::onDone); + } + + private void onPass() { + String pass = binding.pass.getText().toString().trim(); + if (pass.length() > 0) callback.setPass(pass); + dismiss(); + } + + private boolean onDone(TextView view, int actionId, KeyEvent event) { + if (actionId == EditorInfo.IME_ACTION_DONE) onPass(); + return true; + } + + @Override + public void onResume() { + super.onResume(); + getDialog().getWindow().setLayout(ResUtil.dp2px(250), -1); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ProxyDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ProxyDialog.java new file mode 100644 index 00000000..2f6e96e7 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ProxyDialog.java @@ -0,0 +1,91 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogProxyBinding; +import com.fongmi.android.tv.impl.ProxyCallback; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class ProxyDialog { + + private final DialogProxyBinding binding; + private final ProxyCallback callback; + private AlertDialog dialog; + private boolean append; + + public static ProxyDialog create(Fragment fragment) { + return new ProxyDialog(fragment); + } + + public ProxyDialog(Fragment fragment) { + this.callback = (ProxyCallback) fragment; + this.binding = DialogProxyBinding.inflate(LayoutInflater.from(fragment.getContext())); + this.append = true; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setTitle(R.string.setting_proxy).setView(binding.getRoot()).setPositiveButton(R.string.dialog_positive, this::onPositive).setNegativeButton(R.string.dialog_negative, this::onNegative).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + String text = Setting.getProxy(); + binding.text.setText(text); + binding.text.setSelection(TextUtils.isEmpty(text) ? 0 : text.length()); + } + + private void initEvent() { + binding.text.addTextChangedListener(new CustomTextListener() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + detect(s.toString()); + } + }); + binding.text.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + return true; + }); + } + + private void detect(String s) { + if (append && "h".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ttp://"); + } else if (append && "s".equalsIgnoreCase(s)) { + append = false; + binding.text.append("ocks5://"); + } else if (append && s.length() == 1) { + append = false; + binding.text.getText().insert(0, "socks5://"); + } else if (s.length() > 1) { + append = false; + } else if (s.length() == 0) { + append = true; + } + } + + private void onPositive(DialogInterface dialog, int which) { + callback.setProxy(binding.text.getText().toString().trim()); + dialog.dismiss(); + } + + private void onNegative(DialogInterface dialog, int which) { + dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ReceiveDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ReceiveDialog.java new file mode 100644 index 00000000..f147b875 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/ReceiveDialog.java @@ -0,0 +1,105 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.databinding.DialogReceiveBinding; +import com.fongmi.android.tv.event.CastEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.activity.VideoActivity; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.Notify; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +public class ReceiveDialog extends BaseDialog { + + private DialogReceiveBinding binding; + private CastEvent event; + + public static ReceiveDialog create() { + return new ReceiveDialog(); + } + + public ReceiveDialog event(CastEvent event) { + this.event = event; + return this; + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + } + + public void show(Fragment fragment) { + for (Fragment f : fragment.getChildFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(fragment.getChildFragmentManager(), null); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogReceiveBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + History item = event.getHistory(); + binding.name.setText(item.getVodName()); + binding.from.setText(event.getDevice().getName()); + ImgUtil.loadVod(item.getVodName(), item.getVodPic(), binding.image); + } + + @Override + protected void initEvent() { + binding.frame.setOnClickListener(v -> onReceiveCast()); + } + + private void showProgress() { + binding.frame.setEnabled(false); + binding.play.setVisibility(View.GONE); + binding.progress.getRoot().setVisibility(View.VISIBLE); + } + + private void hideProgress() { + binding.frame.setEnabled(true); + binding.play.setVisibility(View.VISIBLE); + binding.progress.getRoot().setVisibility(View.GONE); + } + + private void onReceiveCast() { + if (VodConfig.get().getConfig().equals(event.getConfig())) { + VideoActivity.cast(getActivity(), event.getHistory().update(VodConfig.getCid())); + dismiss(); + } else { + showProgress(); + VodConfig.load(event.getConfig(), getCallback()); + } + } + + private Callback getCallback() { + return new Callback() { + @Override + public void success() { + RefreshEvent.config(); + RefreshEvent.video(); + onReceiveCast(); + hideProgress(); + } + + @Override + public void error(String msg) { + Notify.show(msg); + hideProgress(); + } + }; + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/RestoreDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/RestoreDialog.java new file mode 100644 index 00000000..11328d22 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/RestoreDialog.java @@ -0,0 +1,62 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.DialogRestoreBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.adapter.RestoreAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import java.io.File; + +public class RestoreDialog extends BaseDialog implements RestoreAdapter.OnClickListener { + + private DialogRestoreBinding binding; + private RestoreAdapter adapter; + private Callback callback; + + public static RestoreDialog create() { + return new RestoreDialog(); + } + + public void show(FragmentActivity activity, Callback callback) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + this.callback = callback; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogRestoreBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + binding.recycler.setItemAnimator(null); + binding.recycler.setHasFixedSize(false); + binding.recycler.setAdapter(adapter = new RestoreAdapter(this)); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 16)); + binding.recycler.setVisibility(adapter.getItemCount() == 0 ? View.GONE : View.VISIBLE); + } + + @Override + public void onItemClick(File item) { + AppDatabase.restore(item, callback); + dismiss(); + } + + @Override + public void onDeleteClick(File item) { + if (adapter.remove(item) == 0) dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java new file mode 100644 index 00000000..4958a193 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SiteDialog.java @@ -0,0 +1,119 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.app.Activity; +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.DialogSiteBinding; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.ui.adapter.SiteAdapter; +import com.fongmi.android.tv.ui.custom.SpaceItemDecoration; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class SiteDialog implements SiteAdapter.OnClickListener { + + private final SiteCallback callback; + private DialogSiteBinding binding; + private SiteAdapter adapter; + private AlertDialog dialog; + + public static SiteDialog create(Activity activity) { + return new SiteDialog(activity); + } + + public static SiteDialog create(Fragment fragment) { + return new SiteDialog(fragment); + } + + public SiteDialog(Activity activity) { + this.callback = (SiteCallback) activity; + init(activity); + } + + public SiteDialog(Fragment fragment) { + this.callback = (SiteCallback) fragment; + init(fragment.getActivity()); + } + + private void init(Activity activity) { + this.binding = DialogSiteBinding.inflate(LayoutInflater.from(activity)); + this.dialog = new MaterialAlertDialogBuilder(activity).setView(binding.getRoot()).create(); + this.adapter = new SiteAdapter(this); + } + + public SiteDialog search() { + this.adapter.search(true); + return this; + } + + public SiteDialog change() { + this.adapter.change(true); + return this; + } + + public SiteDialog all() { + this.adapter.search(true); + this.adapter.change(true); + return this; + } + + public void show() { + setRecyclerView(); + setDialog(); + } + + private void setRecyclerView() { + binding.recycler.setAdapter(adapter); + binding.recycler.setItemAnimator(null); + binding.recycler.setHasFixedSize(true); + binding.recycler.addItemDecoration(new SpaceItemDecoration(1, 8)); + binding.recycler.post(() -> binding.recycler.scrollToPosition(VodConfig.getHomeIndex())); + } + + private void setDialog() { + if (adapter.getItemCount() == 0) return; + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + @Override + public void onTextClick(Site item) { + if (callback == null) return; + callback.setSite(item); + dialog.dismiss(); + } + + @Override + public void onSearchClick(int position, Site item) { + item.setSearchable(!item.isSearchable()).save(); + adapter.notifyItemChanged(position); + callback.onChanged(); + } + + @Override + public void onChangeClick(int position, Site item) { + item.setChangeable(!item.isChangeable()).save(); + adapter.notifyItemChanged(position); + } + + @Override + public boolean onSearchLongClick(Site item) { + boolean result = !item.isSearchable(); + for (Site site : adapter.getItems()) site.setSearchable(result).save(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + callback.onChanged(); + return true; + } + + @Override + public boolean onChangeLongClick(Site item) { + boolean result = !item.isChangeable(); + for (Site site : adapter.getItems()) site.setChangeable(result).save(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + return true; + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SpeedDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SpeedDialog.java new file mode 100644 index 00000000..d0ef1efd --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SpeedDialog.java @@ -0,0 +1,54 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.view.LayoutInflater; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogSpeedBinding; +import com.fongmi.android.tv.impl.SpeedCallback; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class SpeedDialog { + + private final DialogSpeedBinding binding; + private final SpeedCallback callback; + private float value; + + public static SpeedDialog create(Fragment fragment) { + return new SpeedDialog(fragment); + } + + public SpeedDialog(Fragment fragment) { + this.callback = (SpeedCallback) fragment; + this.binding = DialogSpeedBinding.inflate(LayoutInflater.from(fragment.getContext())); + } + + public void show() { + initDialog(); + initView(); + } + + private void initDialog() { + AlertDialog dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setTitle(R.string.player_speed).setView(binding.getRoot()).setPositiveButton(R.string.dialog_positive, this::onPositive).setNegativeButton(R.string.dialog_negative, this::onNegative).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + binding.slider.setValue(value = Setting.getSpeed()); + } + + private void onPositive(DialogInterface dialog, int which) { + callback.setSpeed(binding.slider.getValue()); + dialog.dismiss(); + } + + private void onNegative(DialogInterface dialog, int which) { + callback.setSpeed(value); + dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java new file mode 100644 index 00000000..e628d472 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/SyncDialog.java @@ -0,0 +1,199 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.res.TypedArray; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.bean.History; +import com.fongmi.android.tv.bean.Keep; +import com.fongmi.android.tv.databinding.DialogDeviceBinding; +import com.fongmi.android.tv.event.ScanEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.ui.activity.ScanActivity; +import com.fongmi.android.tv.ui.adapter.DeviceAdapter; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.ScanTask; +import com.github.catvod.net.OkHttp; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.OkHttpClient; +import okhttp3.Response; + +public class SyncDialog extends BaseDialog implements DeviceAdapter.OnClickListener, ScanTask.Listener { + + private final FormBody.Builder body; + private final OkHttpClient client; + private final ScanTask scanTask; + private final TypedArray mode; + private DialogDeviceBinding binding; + private DeviceAdapter adapter; + private String type; + + public static SyncDialog create() { + return new SyncDialog(); + } + + public SyncDialog() { + body = new FormBody.Builder(); + scanTask = new ScanTask(this); + client = OkHttp.client(Constant.TIMEOUT_SYNC); + mode = ResUtil.getTypedArray(R.array.cast_mode); + } + + public SyncDialog history() { + body.add("device", Device.get().toString()); + body.add("config", Config.vod().toString()); + body.add("targets", App.gson().toJson(History.get())); + return type("history"); + } + + public SyncDialog keep() { + body.add("device", Device.get().toString()); + body.add("targets", App.gson().toJson(Keep.getVod())); + body.add("configs", App.gson().toJson(Config.findUrls())); + return type("keep"); + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + } + + private SyncDialog type(String type) { + this.type = type; + return this; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogDeviceBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + binding.mode.setVisibility(View.VISIBLE); + EventBus.getDefault().register(this); + setRecyclerView(); + getDevice(); + setMode(); + } + + @Override + protected void initEvent() { + binding.mode.setOnClickListener(v -> onMode()); + binding.scan.setOnClickListener(v -> onScan()); + binding.refresh.setOnClickListener(v -> onRefresh()); + } + + private void setRecyclerView() { + binding.recycler.setHasFixedSize(true); + binding.recycler.setAdapter(adapter = new DeviceAdapter(this)); + } + + private void getDevice() { + adapter.addAll(Device.getAll()); + if (adapter.getItemCount() == 0) App.post(this::onRefresh, 1000); + } + + private void setMode() { + int index = Setting.getSyncMode(); + binding.mode.setImageResource(mode.getResourceId(index, 0)); + binding.mode.setTag(String.valueOf(index)); + } + + private void onMode() { + int index = Setting.getSyncMode(); + Setting.putSyncMode(index = index == mode.length() - 1 ? 0 : ++index); + binding.mode.setImageResource(mode.getResourceId(index, 0)); + binding.mode.setTag(String.valueOf(index)); + } + + private void onScan() { + ScanActivity.start(getActivity()); + } + + private void onRefresh() { + scanTask.start(adapter.getIps()); + adapter.clear(); + } + + private void onSuccess() { + dismiss(); + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onScanEvent(ScanEvent event) { + scanTask.start(event.getAddress()); + } + + @Override + public void onFind(List devices) { + if (!devices.isEmpty()) adapter.addAll(devices); + } + + @Override + public void onItemClick(Device item) { + OkHttp.newCall(client, String.format(Locale.getDefault(), "%s/action?do=sync&mode=%s&type=%s", item.getIp(), binding.mode.getTag().toString(), type), body.build()).enqueue(getCallback()); + } + + @Override + public boolean onLongClick(Device item) { + String mode = binding.mode.getTag().toString(); + if (mode.equals("0")) return false; + if (mode.equals("2") && type.equals("keep")) Keep.deleteAll(); + if (mode.equals("2") && type.equals("history")) History.delete(VodConfig.getCid()); + OkHttp.newCall(client, String.format(Locale.getDefault(), "%s/action?do=sync&mode=%s&type=%s&force=true", item.getIp(), binding.mode.getTag().toString(), type), body.build()).enqueue(getCallback()); + return true; + } + + private Callback getCallback() { + return new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + App.post(() -> onSuccess()); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + App.post(() -> Notify.show(e.getMessage())); + } + }; + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + EventBus.getDefault().unregister(this); + } + + @Override + public void onDestroy() { + super.onDestroy(); + scanTask.stop(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/TimerDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/TimerDialog.java new file mode 100644 index 00000000..3bf0b129 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/TimerDialog.java @@ -0,0 +1,95 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.databinding.DialogTimerBinding; +import com.fongmi.android.tv.utils.Timer; +import com.fongmi.android.tv.utils.Util; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import java.util.Formatter; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +public class TimerDialog extends BaseDialog implements Timer.Callback { + + private DialogTimerBinding binding; + private StringBuilder builder; + private Formatter formatter; + + public static TimerDialog create() { + return new TimerDialog(); + } + + public TimerDialog() { + builder = new StringBuilder(); + formatter = new Formatter(builder, Locale.getDefault()); + } + + public void show(FragmentActivity activity) { + for (Fragment f : activity.getSupportFragmentManager().getFragments()) if (f instanceof BottomSheetDialogFragment) return; + show(activity.getSupportFragmentManager(), null); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return binding = DialogTimerBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + onTick(Timer.get().getTick()); + binding.list.setVisibility(Timer.get().isRunning() ? View.GONE : View.VISIBLE); + binding.timer.setVisibility(Timer.get().isRunning() ? View.VISIBLE : View.GONE); + } + + @Override + protected void initEvent() { + Timer.get().setCallback(this); + binding.delay.setOnClickListener(this::onDelay); + binding.reset.setOnClickListener(this::onReset); + binding.time1.setOnClickListener(this::setTimer); + binding.time2.setOnClickListener(this::setTimer); + binding.time3.setOnClickListener(this::setTimer); + binding.time4.setOnClickListener(this::setTimer); + } + + private void setTimer(View view) { + int minutes = Integer.parseInt(view.getTag().toString()); + Timer.get().set(TimeUnit.MINUTES.toMillis(minutes)); + dismiss(); + } + + private void onDelay(View view) { + Timer.get().delay(); + } + + private void onReset(View view) { + Timer.get().reset(); + dismiss(); + } + + @Override + public void onTick(long tick) { + binding.tick.setText(Util.format(builder, formatter, tick)); + } + + @Override + public void onFinish() { + dismiss(); + } + + @Override + public void dismiss() { + Timer.get().setCallback(null); + super.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/UaDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/UaDialog.java new file mode 100644 index 00000000..7b1cb59a --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/UaDialog.java @@ -0,0 +1,89 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.inputmethod.EditorInfo; + +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.Fragment; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.DialogUaBinding; +import com.fongmi.android.tv.impl.UaCallback; +import com.fongmi.android.tv.ui.custom.CustomTextListener; +import com.github.catvod.utils.Util; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class UaDialog { + + private final DialogUaBinding binding; + private final UaCallback callback; + private AlertDialog dialog; + private boolean append; + + public static UaDialog create(Fragment fragment) { + return new UaDialog(fragment); + } + + public UaDialog(Fragment fragment) { + this.callback = (UaCallback) fragment; + this.binding = DialogUaBinding.inflate(LayoutInflater.from(fragment.getContext())); + this.append = true; + } + + public void show() { + initDialog(); + initView(); + initEvent(); + } + + private void initDialog() { + dialog = new MaterialAlertDialogBuilder(binding.getRoot().getContext()).setTitle(R.string.player_ua).setView(binding.getRoot()).setPositiveButton(R.string.dialog_positive, this::onPositive).setNegativeButton(R.string.dialog_negative, this::onNegative).create(); + dialog.getWindow().setDimAmount(0); + dialog.show(); + } + + private void initView() { + String text = Setting.getUa(); + binding.text.setText(text); + binding.text.setSelection(TextUtils.isEmpty(text) ? 0 : text.length()); + } + + private void initEvent() { + binding.text.addTextChangedListener(new CustomTextListener() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + detect(s.toString()); + } + }); + binding.text.setOnEditorActionListener((textView, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick(); + return true; + }); + } + + private void detect(String s) { + if (append && "c".equalsIgnoreCase(s)) { + append = false; + binding.text.setText(Util.CHROME); + } else if (append && "o".equalsIgnoreCase(s)) { + append = false; + binding.text.setText(Util.OKHTTP); + } else if (s.length() > 1) { + append = false; + } else if (s.length() == 0) { + append = true; + } + } + + private void onPositive(DialogInterface dialog, int which) { + callback.setUa(binding.text.getText().toString().trim()); + dialog.dismiss(); + } + + private void onNegative(DialogInterface dialog, int which) { + dialog.dismiss(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/WebDialog.java b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/WebDialog.java new file mode 100644 index 00000000..97ac4ba5 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/dialog/WebDialog.java @@ -0,0 +1,38 @@ +package com.fongmi.android.tv.ui.dialog; + +import android.content.DialogInterface; +import android.view.View; + +import androidx.appcompat.app.AlertDialog; + +import com.fongmi.android.tv.App; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +public class WebDialog { + + private final AlertDialog dialog; + + public static WebDialog create(View view) { + return new WebDialog(view); + } + + public WebDialog(View view) { + this.dialog = new MaterialAlertDialogBuilder(App.activity()).setView(view).create(); + this.dialog.setOnDismissListener((DialogInterface.OnDismissListener) view); + } + + public WebDialog show() { + initDialog(); + return this; + } + + public void dismiss() { + dialog.setOnDismissListener(null); + dialog.dismiss(); + } + + private void initDialog() { + dialog.getWindow().setDimAmount(0); + dialog.show(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/EpisodeFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/EpisodeFragment.java new file mode 100644 index 00000000..4bef84b5 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/EpisodeFragment.java @@ -0,0 +1,73 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.FragmentEpisodeBinding; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.base.ViewType; + +import java.util.ArrayList; +import java.util.List; + +public class EpisodeFragment extends BaseFragment implements EpisodeAdapter.OnClickListener { + + private FragmentEpisodeBinding mBinding; + private SiteViewModel mViewModel; + + private int getSpanCount() { + return getArguments().getInt("spanCount"); + } + + private ArrayList getItems() { + return getArguments().getParcelableArrayList("items"); + } + + public static EpisodeFragment newInstance(int spanCount, List items) { + Bundle args = new Bundle(); + args.putInt("spanCount", spanCount); + args.putParcelableArrayList("items", new ArrayList<>(items)); + EpisodeFragment fragment = new EpisodeFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentEpisodeBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + setRecyclerView(); + setViewModel(); + } + + private void setRecyclerView() { + EpisodeAdapter adapter; + mBinding.recycler.setHasFixedSize(true); + mBinding.recycler.setItemAnimator(null); + mBinding.recycler.setLayoutManager(new GridLayoutManager(getContext(), getSpanCount())); + mBinding.recycler.setAdapter(adapter = new EpisodeAdapter(this, ViewType.GRID, getItems())); + mBinding.recycler.scrollToPosition(adapter.getPosition()); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(requireActivity()).get(SiteViewModel.class); + } + + @Override + public void onItemClick(Episode item) { + mViewModel.setEpisode(item); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java new file mode 100644 index 00000000..b582d6d7 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingFragment.java @@ -0,0 +1,430 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.Manifest; +import android.app.Activity; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.BuildConfig; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.Updater; +import com.fongmi.android.tv.api.config.LiveConfig; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.bean.Config; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.databinding.FragmentSettingBinding; +import com.fongmi.android.tv.db.AppDatabase; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.ConfigCallback; +import com.fongmi.android.tv.impl.LiveCallback; +import com.fongmi.android.tv.impl.ProxyCallback; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.player.Source; +import com.fongmi.android.tv.ui.activity.HomeActivity; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.dialog.ConfigDialog; +import com.fongmi.android.tv.ui.dialog.HistoryDialog; +import com.fongmi.android.tv.ui.dialog.LiveDialog; +import com.fongmi.android.tv.ui.dialog.ProxyDialog; +import com.fongmi.android.tv.ui.dialog.RestoreDialog; +import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Notify; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.bean.Doh; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Path; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.permissionx.guolindev.PermissionX; + +import java.util.ArrayList; +import java.util.List; + +public class SettingFragment extends BaseFragment implements ConfigCallback, SiteCallback, LiveCallback, ProxyCallback { + + private FragmentSettingBinding mBinding; + private String[] size; + private int type; + + public static SettingFragment newInstance() { + return new SettingFragment(); + } + + private String getSwitch(boolean value) { + return getString(value ? R.string.setting_on : R.string.setting_off); + } + + private String getProxy(String proxy) { + return proxy.isEmpty() ? getString(R.string.none) : UrlUtil.scheme(proxy); + } + + private int getDohIndex() { + return Math.max(0, VodConfig.get().getDoh().indexOf(Doh.objectFrom(Setting.getDoh()))); + } + + private String[] getDohList() { + List list = new ArrayList<>(); + for (Doh item : VodConfig.get().getDoh()) list.add(item.getName()); + return list.toArray(new String[0]); + } + + private HomeActivity getRoot() { + return (HomeActivity) getActivity(); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentSettingBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + mBinding.vodUrl.setText(VodConfig.getDesc()); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + mBinding.wallUrl.setText(WallConfig.getDesc()); + mBinding.versionText.setText(BuildConfig.VERSION_NAME); + + // 设置开关的颜色为黄色 + int accentColor = getResources().getColor(R.color.accent); + android.content.res.ColorStateList colorStateList = new android.content.res.ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_checked}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + 0x66FFFFFF, // 未选中时的颜色 + accentColor // 选中时的颜色 + } + ); + mBinding.incognitoSwitch.setThumbTintList(android.content.res.ColorStateList.valueOf(android.graphics.Color.WHITE)); + mBinding.incognitoSwitch.setTrackTintList(colorStateList); + + setOtherText(); + setCacheText(); + String[] quotes = getResources().getStringArray(R.array.motivational_quotes); + int randomIndex = new java.util.Random().nextInt(quotes.length); + mBinding.marquee.setText(quotes[randomIndex]); + } + + private void setOtherText() { + mBinding.dohText.setText(getDohList()[getDohIndex()]); + mBinding.proxyText.setText(getProxy(Setting.getProxy())); + mBinding.incognitoSwitch.setChecked(Setting.isIncognito()); + mBinding.sizeText.setText((size = ResUtil.getStringArray(R.array.select_size))[Setting.getSize()]); + } + + private void setCacheText() { + FileUtil.getCacheSize(new Callback() { + @Override + public void success(String result) { + mBinding.cacheText.setText(result); + } + }); + } + + @Override + protected void initEvent() { + mBinding.vod.setOnClickListener(this::onVod); + mBinding.live.setOnClickListener(this::onLive); + mBinding.wall.setOnClickListener(this::onWall); + mBinding.proxy.setOnClickListener(this::onProxy); + mBinding.cache.setOnClickListener(this::onCache); + mBinding.backup.setOnClickListener(this::onBackup); + mBinding.player.setOnClickListener(this::onPlayer); + mBinding.restore.setOnClickListener(this::onRestore); + mBinding.version.setOnClickListener(this::onVersion); + mBinding.vod.setOnLongClickListener(this::onVodEdit); + mBinding.vodHome.setOnClickListener(this::onVodHome); + mBinding.live.setOnLongClickListener(this::onLiveEdit); + mBinding.liveHome.setOnClickListener(this::onLiveHome); + mBinding.wall.setOnLongClickListener(this::onWallEdit); + mBinding.vodHistory.setOnClickListener(this::onVodHistory); + mBinding.version.setOnLongClickListener(this::onVersionDev); + mBinding.liveHistory.setOnClickListener(this::onLiveHistory); + mBinding.wallDefault.setOnClickListener(this::setWallDefault); + mBinding.wallRefresh.setOnClickListener(this::setWallRefresh); + mBinding.incognito.setOnClickListener(this::setIncognito); + mBinding.size.setOnClickListener(this::setSize); + mBinding.doh.setOnClickListener(this::setDoh); + } + + @Override + public void setConfig(Config config) { + if (config.getUrl().startsWith("file") && !PermissionX.isGranted(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> load(config)); + } else { + load(config); + } + } + + private void load(Config config) { + switch (config.getType()) { + case 0: + Notify.progress(getActivity()); + VodConfig.load(config, getCallback(0)); + mBinding.vodUrl.setText(config.getDesc()); + break; + case 1: + Notify.progress(getActivity()); + LiveConfig.load(config, getCallback(1)); + mBinding.liveUrl.setText(config.getDesc()); + break; + case 2: + Notify.progress(getActivity()); + WallConfig.load(config, getCallback(2)); + mBinding.wallUrl.setText(config.getDesc()); + break; + } + } + + private Callback getCallback(int type) { + return new Callback() { + @Override + public void success(String result) { + Notify.show(result); + } + + @Override + public void success() { + setConfig(type); + } + + @Override + public void error(String msg) { + Notify.show(msg); + setConfig(type); + } + }; + } + + private void setConfig(int type) { + switch (type) { + case 0: + setCacheText(); + Notify.dismiss(); + RefreshEvent.video(); + RefreshEvent.config(); + mBinding.vodUrl.setText(VodConfig.getDesc()); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + mBinding.wallUrl.setText(WallConfig.getDesc()); + break; + case 1: + setCacheText(); + Notify.dismiss(); + RefreshEvent.config(); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + break; + case 2: + setCacheText(); + Notify.dismiss(); + mBinding.wallUrl.setText(WallConfig.getDesc()); + break; + } + } + + @Override + public void setSite(Site item) { + VodConfig.get().setHome(item); + RefreshEvent.video(); + } + + @Override + public void onChanged() { + } + + @Override + public void setLive(Live item) { + LiveConfig.get().setHome(item); + } + + private void onVod(View view) { + ConfigDialog.create(this).type(type = 0).show(); + } + + private void onLive(View view) { + ConfigDialog.create(this).type(type = 1).show(); + } + + private void onWall(View view) { + ConfigDialog.create(this).type(type = 2).show(); + } + + private boolean onVodEdit(View view) { + ConfigDialog.create(this).type(type = 0).edit().show(); + return true; + } + + private boolean onLiveEdit(View view) { + ConfigDialog.create(this).type(type = 1).edit().show(); + return true; + } + + private boolean onWallEdit(View view) { + ConfigDialog.create(this).type(type = 2).edit().show(); + return true; + } + + private void onVodHome(View view) { + SiteDialog.create(this).all().show(); + } + + private void onLiveHome(View view) { + LiveDialog.create(this).action().show(); + } + + private void onVodHistory(View view) { + HistoryDialog.create(this).type(type = 0).show(); + } + + private void onLiveHistory(View view) { + HistoryDialog.create(this).type(type = 1).show(); + } + + private void onPlayer(View view) { + getRoot().change(2); + } + + private void onVersion(View view) { + Updater.create().force().release().start(getActivity()); + } + + private boolean onVersionDev(View view) { + Updater.create().force().dev().start(getActivity()); + return true; + } + + private void setWallDefault(View view) { + WallConfig.refresh(Setting.getWall() == 4 ? 1 : Setting.getWall() + 1); + } + + private void setWallRefresh(View view) { + Notify.progress(getActivity()); + WallConfig.get().load(new Callback() { + @Override + public void success() { + Notify.dismiss(); + setCacheText(); + } + }); + } + + private void setIncognito(View view) { + boolean isChecked = !Setting.isIncognito(); + Setting.putIncognito(isChecked); + mBinding.incognitoSwitch.setChecked(isChecked); + } + + private void setSize(View view) { + new MaterialAlertDialogBuilder(getActivity()).setTitle(R.string.setting_size).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(size, Setting.getSize(), (dialog, which) -> { + mBinding.sizeText.setText(size[which]); + Setting.putSize(which); + RefreshEvent.size(); + dialog.dismiss(); + }).show(); + } + + private void setDoh(View view) { + new MaterialAlertDialogBuilder(getActivity()).setTitle(R.string.setting_doh).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(getDohList(), getDohIndex(), (dialog, which) -> { + setDoh(VodConfig.get().getDoh().get(which)); + dialog.dismiss(); + }).show(); + } + + private void setDoh(Doh doh) { + Source.get().stop(); + OkHttp.get().setDoh(doh); + Notify.progress(getActivity()); + Setting.putDoh(doh.toString()); + mBinding.dohText.setText(doh.getName()); + VodConfig.load(Config.vod(), getCallback(0)); + } + + private void onProxy(View view) { + ProxyDialog.create(this).show(); + } + + @Override + public void setProxy(String proxy) { + Source.get().stop(); + Setting.putProxy(proxy); + OkHttp.selector().clear(); + OkHttp.get().setProxy(proxy); + Notify.progress(getActivity()); + mBinding.proxyText.setText(getProxy(proxy)); + VodConfig.load(Config.vod(), getCallback(0)); + } + + private void onCache(View view) { + FileUtil.clearCache(new Callback() { + @Override + public void success() { + setCacheText(); + } + }); + } + + private void onBackup(View view) { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> AppDatabase.backup(new Callback() { + @Override + public void success() { + Notify.show(R.string.backup_success); + } + + @Override + public void error() { + Notify.show(R.string.backup_fail); + } + })); + } + + private void onRestore(View view) { + PermissionX.init(this).permissions(Manifest.permission.WRITE_EXTERNAL_STORAGE).request((allGranted, grantedList, deniedList) -> RestoreDialog.create().show(getActivity(), new Callback() { + @Override + public void success() { + Notify.show(R.string.restore_success); + Notify.progress(getActivity()); + setOtherText(); + initConfig(); + } + + @Override + public void error() { + Notify.show(R.string.restore_fail); + } + })); + } + + private void initConfig() { + WallConfig.get().init(); + LiveConfig.get().init().load(); + VodConfig.get().init().load(getCallback(0)); + } + + @Override + public void onHiddenChanged(boolean hidden) { + if (hidden) return; + mBinding.vodUrl.setText(VodConfig.getDesc()); + mBinding.liveUrl.setText(LiveConfig.getDesc()); + mBinding.wallUrl.setText(WallConfig.getDesc()); + setCacheText(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != Activity.RESULT_OK || requestCode != FileChooser.REQUEST_PICK_FILE) return; + setConfig(Config.find("file:/" + FileChooser.getPathFromUri(getContext(), data.getData()).replace(Path.rootPath(), ""), type)); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingPlayerFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingPlayerFragment.java new file mode 100644 index 00000000..1543fa7c --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/SettingPlayerFragment.java @@ -0,0 +1,198 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.content.Intent; +import android.provider.Settings; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.databinding.FragmentSettingPlayerBinding; +import com.fongmi.android.tv.impl.BufferCallback; +import com.fongmi.android.tv.impl.SpeedCallback; +import com.fongmi.android.tv.impl.UaCallback; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.dialog.BufferDialog; +import com.fongmi.android.tv.ui.dialog.SpeedDialog; +import com.fongmi.android.tv.ui.dialog.UaDialog; +import com.fongmi.android.tv.utils.ResUtil; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import java.text.DecimalFormat; + +public class SettingPlayerFragment extends BaseFragment implements UaCallback, BufferCallback, SpeedCallback { + + private FragmentSettingPlayerBinding mBinding; + private DecimalFormat format; + private String[] background; + private String[] caption; + private String[] render; + private String[] scale; + + public static SettingPlayerFragment newInstance() { + return new SettingPlayerFragment(); + } + + private String getSwitch(boolean value) { + return getString(value ? R.string.setting_on : R.string.setting_off); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentSettingPlayerBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + format = new DecimalFormat("0.#"); + mBinding.back.setOnClickListener(v -> requireActivity().onBackPressed()); + mBinding.uaText.setText(Setting.getUa()); + mBinding.tunnelSwitch.setChecked(Setting.isTunnel()); + mBinding.audioDecodeSwitch.setChecked(Setting.isAudioPrefer()); + mBinding.aacSwitch.setChecked(Setting.isPreferAAC()); + mBinding.danmakuLoadSwitch.setChecked(Setting.isDanmakuLoad()); + mBinding.speedText.setText(format.format(Setting.getSpeed())); + mBinding.bufferText.setText(String.valueOf(Setting.getBuffer())); + mBinding.caption.setVisibility(Setting.hasCaption() ? View.VISIBLE : View.GONE); + mBinding.scaleText.setText((scale = ResUtil.getStringArray(R.array.select_scale))[Setting.getScale()]); + mBinding.renderText.setText((render = ResUtil.getStringArray(R.array.select_render))[Setting.getRender()]); + mBinding.captionText.setText((caption = ResUtil.getStringArray(R.array.select_caption))[Setting.isCaption() ? 1 : 0]); + mBinding.backgroundText.setText((background = ResUtil.getStringArray(R.array.select_background))[Setting.getBackground()]); + + // 设置开关的颜色为黄色 + int accentColor = getResources().getColor(R.color.accent); + android.content.res.ColorStateList colorStateList = new android.content.res.ColorStateList( + new int[][]{ + new int[]{-android.R.attr.state_checked}, + new int[]{android.R.attr.state_checked} + }, + new int[]{ + 0x66FFFFFF, // 未选中时的颜色 + accentColor // 选中时的颜色 + } + ); + + mBinding.tunnelSwitch.setThumbTintList(android.content.res.ColorStateList.valueOf(android.graphics.Color.WHITE)); + mBinding.tunnelSwitch.setTrackTintList(colorStateList); + mBinding.audioDecodeSwitch.setThumbTintList(android.content.res.ColorStateList.valueOf(android.graphics.Color.WHITE)); + mBinding.audioDecodeSwitch.setTrackTintList(colorStateList); + mBinding.aacSwitch.setThumbTintList(android.content.res.ColorStateList.valueOf(android.graphics.Color.WHITE)); + mBinding.aacSwitch.setTrackTintList(colorStateList); + mBinding.danmakuLoadSwitch.setThumbTintList(android.content.res.ColorStateList.valueOf(android.graphics.Color.WHITE)); + mBinding.danmakuLoadSwitch.setTrackTintList(colorStateList); + } + + @Override + protected void initEvent() { + mBinding.ua.setOnClickListener(this::onUa); + mBinding.aac.setOnClickListener(this::setAAC); + mBinding.scale.setOnClickListener(this::onScale); + mBinding.speed.setOnClickListener(this::onSpeed); + mBinding.buffer.setOnClickListener(this::onBuffer); + mBinding.render.setOnClickListener(this::setRender); + mBinding.tunnel.setOnClickListener(this::setTunnel); + mBinding.caption.setOnClickListener(this::setCaption); + mBinding.caption.setOnLongClickListener(this::onCaption); + mBinding.background.setOnClickListener(this::onBackground); + mBinding.audioDecode.setOnClickListener(this::setAudioDecode); + mBinding.danmakuLoad.setOnClickListener(this::setDanmakuLoad); + } + + private void onUa(View view) { + UaDialog.create(this).show(); + } + + @Override + public void setUa(String ua) { + mBinding.uaText.setText(ua); + Setting.putUa(ua); + } + + private void setAAC(View view) { + boolean isChecked = !Setting.isPreferAAC(); + Setting.putPreferAAC(isChecked); + mBinding.aacSwitch.setChecked(isChecked); + } + + private void onScale(View view) { + new MaterialAlertDialogBuilder(getActivity()).setTitle(R.string.player_scale).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(scale, Setting.getScale(), (dialog, which) -> { + mBinding.scaleText.setText(scale[which]); + Setting.putScale(which); + dialog.dismiss(); + }).show(); + } + + private void onSpeed(View view) { + SpeedDialog.create(this).show(); + } + + @Override + public void setSpeed(float speed) { + mBinding.speedText.setText(format.format(speed)); + Setting.putSpeed(speed); + } + + private void onBuffer(View view) { + BufferDialog.create(this).show(); + } + + @Override + public void setBuffer(int times) { + mBinding.bufferText.setText(String.valueOf(times)); + Setting.putBuffer(times); + } + + private void setRender(View view) { + int index = Setting.getRender(); + Setting.putRender(index = index == render.length - 1 ? 0 : ++index); + mBinding.renderText.setText(render[index]); + if (Setting.isTunnel() && Setting.getRender() == 1) setTunnel(view); + } + + private void setTunnel(View view) { + boolean isChecked = !Setting.isTunnel(); + Setting.putTunnel(isChecked); + mBinding.tunnelSwitch.setChecked(isChecked); + if (isChecked && Setting.getRender() == 1) setRender(view); + } + + private void setCaption(View view) { + Setting.putCaption(!Setting.isCaption()); + mBinding.captionText.setText(caption[Setting.isCaption() ? 1 : 0]); + } + + private boolean onCaption(View view) { + if (Setting.isCaption()) startActivity(new Intent(Settings.ACTION_CAPTIONING_SETTINGS)); + return Setting.isCaption(); + } + + private void onBackground(View view) { + new MaterialAlertDialogBuilder(getActivity()).setTitle(R.string.player_background).setNegativeButton(R.string.dialog_negative, null).setSingleChoiceItems(background, Setting.getBackground(), (dialog, which) -> { + mBinding.backgroundText.setText(background[which]); + Setting.putBackground(which); + dialog.dismiss(); + }).show(); + } + + private void setAudioDecode(View view) { + boolean isChecked = !Setting.isAudioPrefer(); + Setting.putAudioPrefer(isChecked); + mBinding.audioDecodeSwitch.setChecked(isChecked); + } + + private void setDanmakuLoad(View view) { + boolean isChecked = !Setting.isDanmakuLoad(); + Setting.putDanmakuLoad(isChecked); + mBinding.danmakuLoadSwitch.setChecked(isChecked); + } + + @Override + public void onHiddenChanged(boolean hidden) { + if (!hidden) initView(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/TypeFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/TypeFragment.java new file mode 100644 index 00000000..9155878c --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/TypeFragment.java @@ -0,0 +1,252 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import androidx.viewbinding.ViewBinding; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Page; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Style; +import com.fongmi.android.tv.bean.Value; +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.FragmentTypeBinding; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.activity.CollectActivity; +import com.fongmi.android.tv.ui.activity.VideoActivity; +import com.fongmi.android.tv.ui.adapter.VodAdapter; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.custom.CustomScroller; +import com.fongmi.android.tv.utils.Notify; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class TypeFragment extends BaseFragment implements CustomScroller.Callback, VodAdapter.OnClickListener, SwipeRefreshLayout.OnRefreshListener { + + private HashMap mExtends; + private FragmentTypeBinding mBinding; + private CustomScroller mScroller; + private SiteViewModel mViewModel; + private VodAdapter mAdapter; + private List mPages; + private Page mPage; + + public static TypeFragment newInstance(String key, String typeId, Style style, HashMap extend, boolean folder) { + Bundle args = new Bundle(); + args.putString("key", key); + args.putString("typeId", typeId); + args.putBoolean("folder", folder); + args.putParcelable("style", style); + args.putSerializable("extend", extend); + TypeFragment fragment = new TypeFragment(); + fragment.setArguments(args); + return fragment; + } + + private String getKey() { + return getArguments().getString("key"); + } + + private String getTypeId() { + return mPages.isEmpty() ? getArguments().getString("typeId") : getLastPage().getVodId(); + } + + private Style getStyle() { + return isFolder() ? Style.list() : getSite().getStyle(mPages.isEmpty() ? getArguments().getParcelable("style") : getLastPage().getStyle()); + } + + private HashMap getExtend() { + Serializable extend = getArguments().getSerializable("extend"); + return extend == null ? new HashMap<>() : (HashMap) extend; + } + + private boolean isFolder() { + return getArguments().getBoolean("folder"); + } + + private boolean isHome() { + return "home".equals(getTypeId()); + } + + private Site getSite() { + return VodConfig.get().getSite(getKey()); + } + + private VodFragment getParent() { + return (VodFragment) getParentFragment(); + } + + private Page getLastPage() { + return mPages.get(mPages.size() - 1); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentTypeBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + mScroller = new CustomScroller(this); + mPages = new ArrayList<>(); + mExtends = getExtend(); + mBinding.swipeLayout.setProgressBackgroundColorSchemeColor(0xFF1A1A1A); + mBinding.swipeLayout.setColorSchemeColors(0xFFFFEB3B); + setRecyclerView(); + setViewModel(); + } + + @Override + protected void initEvent() { + mBinding.swipeLayout.setOnRefreshListener(this); + mBinding.recycler.addOnScrollListener(mScroller = new CustomScroller(this)); + } + + @Override + protected void initData() { + mBinding.progressLayout.showProgress(); + getVideo(); + } + + private void setRecyclerView() { + mBinding.recycler.setHasFixedSize(true); + setStyle(getStyle()); + } + + private void setStyle(Style style) { + mBinding.recycler.setAdapter(mAdapter = new VodAdapter(this, style, Product.getSpec(getActivity(), style))); + mBinding.recycler.setLayoutManager(style.isList() ? new LinearLayoutManager(getActivity()) : new GridLayoutManager(getContext(), Product.getColumn(getActivity(), style))); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.result.observe(getViewLifecycleOwner(), this::setAdapter); + mViewModel.action.observe(getViewLifecycleOwner(), result -> Notify.show(result.getMsg())); + } + + private void getHome() { + mViewModel.homeContent(); + mAdapter.clear(); + } + + private void getVideo() { + mScroller.reset(); + getVideo(getTypeId(), "1"); + } + + private void getVideo(String typeId, String page) { + if ("1".equals(page)) mAdapter.clear(); + if ("1".equals(page) && !mBinding.swipeLayout.isRefreshing()) mBinding.progressLayout.showProgress(); + if (isHome() && "1".equals(page)) setAdapter(getParent().getResult()); + else mViewModel.categoryContent(getKey(), typeId, page, true, mExtends); + } + + private void setAdapter(Result result) { + boolean first = mScroller.first(); + int size = result.getList().size(); + mBinding.progressLayout.showContent(first, size); + mBinding.swipeLayout.setRefreshing(false); + if (size > 0) addVideo(result); + mScroller.endLoading(result); + checkPosition(first); + checkMore(size); + } + + private void addVideo(Result result) { + Style style = result.getList().get(0).getStyle(getStyle()); + if (!style.equals(mAdapter.getStyle())) setStyle(style); + mAdapter.addAll(result.getList()); + } + + private void checkPosition(boolean first) { + if (mPage != null) scrollToPosition(mPage.getPosition()); + else if (first) mBinding.recycler.scrollToPosition(0); + mPage = null; + } + + private void checkMore(int count) { + if (mScroller.isDisable() || count == 0 || mBinding.recycler.canScrollVertically(1) || mBinding.recycler.getScrollState() > 0 || isHome()) return; + getVideo(getTypeId(), String.valueOf(mScroller.addPage())); + } + + private int findPosition() { + if (mBinding.recycler.getLayoutManager() instanceof LinearLayoutManager) { + return ((LinearLayoutManager) mBinding.recycler.getLayoutManager()).findFirstVisibleItemPosition(); + } else if (mBinding.recycler.getLayoutManager() instanceof GridLayoutManager) { + return ((GridLayoutManager) mBinding.recycler.getLayoutManager()).findFirstVisibleItemPosition(); + } else { + return 0; + } + } + + private void scrollToPosition(int position) { + if (mBinding.recycler.getLayoutManager() instanceof LinearLayoutManager) { + ((LinearLayoutManager) mBinding.recycler.getLayoutManager()).scrollToPositionWithOffset(position, 0); + } else if (mBinding.recycler.getLayoutManager() instanceof GridLayoutManager) { + ((GridLayoutManager) mBinding.recycler.getLayoutManager()).scrollToPositionWithOffset(position, 0); + } + } + + public void scrollToTop() { + mBinding.recycler.smoothScrollToPosition(0); + } + + public void setFilter(String key, Value value) { + if (value.isActivated()) mExtends.put(key, value.getV()); + else mExtends.remove(key); + onRefresh(); + } + + @Override + public void onRefresh() { + if (isHome()) getHome(); + else getVideo(); + } + + @Override + public void onLoadMore(String page) { + if (isHome()) return; + mScroller.setLoading(true); + getVideo(getTypeId(), page); + } + + @Override + public void onItemClick(Vod item) { + if (item.isAction()) { + mViewModel.action(getKey(), item.getAction()); + } else if (item.isFolder()) { + mPages.add(Page.get(item, findPosition())); + getVideo(item.getVodId(), "1"); + } else { + if (getSite().isIndex()) CollectActivity.start(getActivity(), item.getVodName()); + else VideoActivity.start(getActivity(), getKey(), item.getVodId(), item.getVodName(), item.getVodPic(), isFolder() ? item.getVodName() : null); + } + } + + @Override + public boolean onLongClick(Vod item) { + CollectActivity.start(getActivity(), item.getVodName()); + return true; + } + + @Override + public boolean canBack() { + if (mPages.isEmpty()) return true; + mPages.remove(mPage = getLastPage()); + onRefresh(); + return false; + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java new file mode 100644 index 00000000..db455ee3 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/fragment/VodFragment.java @@ -0,0 +1,375 @@ +package com.fongmi.android.tv.ui.fragment; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.lifecycle.ViewModelProvider; +import androidx.viewbinding.ViewBinding; +import androidx.viewpager.widget.ViewPager; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.Target; +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.VodConfig; +import com.fongmi.android.tv.bean.Class; +import com.fongmi.android.tv.bean.Hot; +import com.fongmi.android.tv.bean.Result; +import com.fongmi.android.tv.bean.Site; +import com.fongmi.android.tv.bean.Value; +import com.fongmi.android.tv.databinding.FragmentVodBinding; +import com.fongmi.android.tv.event.CastEvent; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.event.StateEvent; +import com.fongmi.android.tv.impl.Callback; +import com.fongmi.android.tv.impl.FilterCallback; +import com.fongmi.android.tv.impl.SiteCallback; +import com.fongmi.android.tv.model.SiteViewModel; +import com.fongmi.android.tv.ui.activity.CollectActivity; +import com.fongmi.android.tv.ui.activity.HistoryActivity; +import com.fongmi.android.tv.ui.activity.KeepActivity; +import com.fongmi.android.tv.ui.activity.VideoActivity; +import com.fongmi.android.tv.ui.adapter.TypeAdapter; +import com.fongmi.android.tv.ui.base.BaseFragment; +import com.fongmi.android.tv.ui.dialog.FilterDialog; +import com.fongmi.android.tv.ui.dialog.LinkDialog; +import com.fongmi.android.tv.ui.dialog.ReceiveDialog; +import com.fongmi.android.tv.ui.dialog.SiteDialog; +import com.fongmi.android.tv.utils.FileChooser; +import com.fongmi.android.tv.utils.ResUtil; +import com.fongmi.android.tv.utils.UrlUtil; +import com.github.catvod.net.OkHttp; +import com.google.common.net.HttpHeaders; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.concurrent.TimeUnit; + +import okhttp3.Call; +import okhttp3.Headers; +import okhttp3.Response; + +public class VodFragment extends BaseFragment implements SiteCallback, FilterCallback, TypeAdapter.OnClickListener { + + private FragmentVodBinding mBinding; + private SiteViewModel mViewModel; + private TypeAdapter mAdapter; + private Runnable mRunnable; + private List mHots; + private Result mResult; + + public static VodFragment newInstance() { + return new VodFragment(); + } + + private TypeFragment getFragment() { + return (TypeFragment) mBinding.pager.getAdapter().instantiateItem(mBinding.pager, mBinding.pager.getCurrentItem()); + } + + private Site getSite() { + return VodConfig.get().getHome(); + } + + @Override + protected ViewBinding getBinding(@NonNull LayoutInflater inflater, @Nullable ViewGroup container) { + return mBinding = FragmentVodBinding.inflate(inflater, container, false); + } + + @Override + protected void initView() { + EventBus.getDefault().register(this); + setRecyclerView(); + setViewModel(); + showProgress(); + setLogo(); + initHot(); + getHot(); + } + + @Override + protected void initEvent() { + mBinding.hot.setOnClickListener(this::onHot); + mBinding.top.setOnClickListener(this::onTop); + mBinding.link.setOnClickListener(this::onLink); + mBinding.logo.setOnClickListener(this::onLogo); + mBinding.keep.setOnClickListener(this::onKeep); + mBinding.retry.setOnClickListener(this::onRetry); + mBinding.filter.setOnClickListener(this::onFilter); + mBinding.search.setOnClickListener(this::onSearch); + mBinding.history.setOnClickListener(this::onHistory); + mBinding.filter.setOnLongClickListener(this::onLink); + mBinding.pager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageSelected(int position) { + mBinding.type.smoothScrollToPosition(position); + mAdapter.setActivated(position); + setFabVisible(position); + } + }); + } + + private void setRecyclerView() { + mBinding.type.setHasFixedSize(true); + mBinding.type.setItemAnimator(null); + mBinding.type.setAdapter(mAdapter = new TypeAdapter(this)); + mBinding.pager.setAdapter(new PageAdapter(getChildFragmentManager())); + } + + private void setViewModel() { + mViewModel = new ViewModelProvider(this).get(SiteViewModel.class); + mViewModel.result.observe(getViewLifecycleOwner(), result -> setAdapter(mResult = result)); + } + + private void initHot() { + mHots = Hot.get(Setting.getHot()); + App.post(mRunnable = this::updateHot, 0); + } + + private void getHot() { + OkHttp.newCall("https://api.web.360kan.com/v1/rank?cat=1", Headers.of(HttpHeaders.REFERER, "https://www.360kan.com/rank/general")).enqueue(new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { + mHots = Hot.get(response.body().string()); + } + }); + } + + private void updateHot() { + App.post(mRunnable, TimeUnit.SECONDS.toMillis(10)); + if (mHots.isEmpty() || mHots.size() < 10) return; + mBinding.hot.setText(mHots.get(new Random().nextInt(11))); + } + + private Result handle(Result result) { + List types = new ArrayList<>(); + for (Class type : result.getTypes()) if (result.getFilters().containsKey(type.getTypeId())) type.setFilters(result.getFilters().get(type.getTypeId())); + for (String cate : getSite().getCategories()) for (Class type : result.getTypes()) if (cate.equals(type.getTypeName())) types.add(type); + result.setTypes(types); + return result; + } + + private void setAdapter(Result result) { + mAdapter.addAll(handle(result)); + mBinding.pager.getAdapter().notifyDataSetChanged(); + setFabVisible(0); + hideProgress(); + checkRetry(); + } + + private void setFabVisible(int position) { + if (mAdapter.getItemCount() == 0) { + mBinding.top.setVisibility(View.INVISIBLE); + mBinding.link.setVisibility(View.VISIBLE); + mBinding.filter.setVisibility(View.GONE); + } else if (!mAdapter.get(position).getFilters().isEmpty()) { + mBinding.top.setVisibility(View.INVISIBLE); + mBinding.link.setVisibility(View.GONE); + mBinding.filter.show(); + } else if (position == 0 || mAdapter.get(position).getFilters().isEmpty()) { + mBinding.top.setVisibility(View.INVISIBLE); + mBinding.filter.setVisibility(View.GONE); + mBinding.link.show(); + } + } + + private void checkRetry() { + mBinding.retry.setVisibility(mAdapter.getItemCount() == 0 ? View.VISIBLE : View.GONE); + } + + private void onTop(View view) { + getFragment().scrollToTop(); + mBinding.top.setVisibility(View.INVISIBLE); + if (mBinding.filter.getVisibility() == View.INVISIBLE) mBinding.filter.show(); + else if (mBinding.link.getVisibility() == View.INVISIBLE) mBinding.link.show(); + } + + private boolean onLink(View view) { + LinkDialog.create(this).show(); + return true; + } + + private void onLogo(View view) { + SiteDialog.create(this).change().show(); + } + + private void onKeep(View view) { + KeepActivity.start(getActivity()); + } + + private void onRetry(View view) { + homeContent(); + } + + private void onFilter(View view) { + if (mAdapter.getItemCount() > 0) FilterDialog.create().filter(mAdapter.get(mBinding.pager.getCurrentItem()).getFilters()).show(this); + } + + private void onHot(View view) { + CollectActivity.start(getActivity()); + } + + private void onSearch(View view) { + CollectActivity.start(getActivity(), mBinding.hot.getText().toString()); + } + + private void onHistory(View view) { + HistoryActivity.start(getActivity()); + } + + private void showProgress() { + mBinding.retry.setVisibility(View.GONE); + mBinding.progress.getRoot().setVisibility(View.VISIBLE); + } + + private void hideProgress() { + mBinding.progress.getRoot().setVisibility(View.GONE); + } + + private void homeContent() { + showProgress(); + setFabVisible(0); + mAdapter.clear(); + mViewModel.homeContent(); + mBinding.pager.setAdapter(new PageAdapter(getChildFragmentManager())); + } + + public Result getResult() { + return mResult == null ? new Result() : mResult; + } + + private void setLogo() { + Glide.with(App.get()).load(UrlUtil.convert(VodConfig.get().getConfig().getLogo())).circleCrop().override(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).error(R.drawable.ic_logo).listener(getListener()).into(mBinding.logo); + } + + private RequestListener getListener() { + return new RequestListener<>() { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, @NonNull Target target, boolean isFirstResource) { + mBinding.logo.getLayoutParams().width = ResUtil.dp2px(24); + mBinding.logo.getLayoutParams().height = ResUtil.dp2px(24); + return false; + } + + @Override + public boolean onResourceReady(@NonNull Drawable resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + mBinding.logo.getLayoutParams().width = ResUtil.dp2px(36); + mBinding.logo.getLayoutParams().height = ResUtil.dp2px(36); + return false; + } + }; + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + switch (event.getType()) { + case CONFIG: + setLogo(); + break; + case VIDEO: + case SIZE: + homeContent(); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onStateEvent(StateEvent event) { + switch (event.getType()) { + case EMPTY: + hideProgress(); + break; + case PROGRESS: + showProgress(); + break; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onCastEvent(CastEvent event) { + ReceiveDialog.create().event(event).show(this); + } + + @Override + public void setSite(Site item) { + VodConfig.get().setHome(item); + homeContent(); + } + + @Override + public void onChanged() { + } + + @Override + public void onItemClick(int position, Class item) { + mBinding.pager.setCurrentItem(position); + mAdapter.setActivated(position); + } + + @Override + public void setFilter(String key, Value value) { + getFragment().setFilter(key, value); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (resultCode != Activity.RESULT_OK || requestCode != FileChooser.REQUEST_PICK_FILE) return; + VideoActivity.file(getActivity(), FileChooser.getPathFromUri(getContext(), data.getData())); + } + + @Override + public boolean canBack() { + if (mBinding.pager.getAdapter() == null) return true; + if (mBinding.pager.getAdapter().getCount() == 0) return true; + return getFragment().canBack(); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + App.removeCallbacks(mRunnable); + EventBus.getDefault().unregister(this); + } + + class PageAdapter extends FragmentStatePagerAdapter { + + public PageAdapter(@NonNull FragmentManager fm) { + super(fm); + } + + @NonNull + @Override + public Fragment getItem(int position) { + Class type = mAdapter.get(position); + return TypeFragment.newInstance(getSite().getKey(), type.getTypeId(), type.getStyle(), type.getExtend(true), "1".equals(type.getTypeFlag())); + } + + @Override + public int getCount() { + return mAdapter.getItemCount(); + } + + @Override + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { + } + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeGridHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeGridHolder.java new file mode 100644 index 00000000..bc5e5403 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeGridHolder.java @@ -0,0 +1,28 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.AdapterEpisodeGridBinding; +import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; +import com.fongmi.android.tv.ui.base.BaseEpisodeHolder; + +public class EpisodeGridHolder extends BaseEpisodeHolder { + + private final EpisodeAdapter.OnClickListener listener; + private final AdapterEpisodeGridBinding binding; + + public EpisodeGridHolder(@NonNull AdapterEpisodeGridBinding binding, EpisodeAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + @Override + public void initView(Episode item) { + binding.text.setSelected(item.isSelected()); + binding.text.setActivated(item.isActivated()); + binding.text.setText(item.getDesc().concat(item.getName())); + binding.text.setOnClickListener(v -> listener.onItemClick(item)); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeHoriHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeHoriHolder.java new file mode 100644 index 00000000..7acdfd45 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeHoriHolder.java @@ -0,0 +1,30 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.Product; +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.AdapterEpisodeHoriBinding; +import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; +import com.fongmi.android.tv.ui.base.BaseEpisodeHolder; + +public class EpisodeHoriHolder extends BaseEpisodeHolder { + + private final EpisodeAdapter.OnClickListener listener; + private final AdapterEpisodeHoriBinding binding; + + public EpisodeHoriHolder(@NonNull AdapterEpisodeHoriBinding binding, EpisodeAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + @Override + public void initView(Episode item) { + binding.text.setMaxEms(Product.getEms()); + binding.text.setSelected(item.isSelected()); + binding.text.setActivated(item.isActivated()); + binding.text.setText(item.getDesc().concat(item.getName())); + binding.text.setOnClickListener(v -> listener.onItemClick(item)); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeVertHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeVertHolder.java new file mode 100644 index 00000000..06691e74 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/EpisodeVertHolder.java @@ -0,0 +1,28 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Episode; +import com.fongmi.android.tv.databinding.AdapterEpisodeVertBinding; +import com.fongmi.android.tv.ui.adapter.EpisodeAdapter; +import com.fongmi.android.tv.ui.base.BaseEpisodeHolder; + +public class EpisodeVertHolder extends BaseEpisodeHolder { + + private final EpisodeAdapter.OnClickListener listener; + private final AdapterEpisodeVertBinding binding; + + public EpisodeVertHolder(@NonNull AdapterEpisodeVertBinding binding, EpisodeAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + @Override + public void initView(Episode item) { + binding.text.setSelected(item.isActivated()); + binding.text.setActivated(item.isActivated()); + binding.text.setText(item.getDesc().concat(item.getName())); + binding.text.setOnClickListener(v -> listener.onItemClick(item)); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodListHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodListHolder.java new file mode 100644 index 00000000..27f1df41 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodListHolder.java @@ -0,0 +1,34 @@ +package com.fongmi.android.tv.ui.holder; + +import android.widget.ImageView; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodListBinding; +import com.fongmi.android.tv.ui.adapter.VodAdapter; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodListHolder extends BaseVodHolder { + + private final VodAdapter.OnClickListener listener; + private final AdapterVodListBinding binding; + + public VodListHolder(@NonNull AdapterVodListBinding binding, VodAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.remark.setText(item.getVodRemarks()); + binding.name.setVisibility(item.getNameVisible()); + binding.remark.setVisibility(item.getRemarkVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.load(item.getVodName(), item.getVodPic(), binding.image, ImageView.ScaleType.FIT_CENTER, false); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodOneHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodOneHolder.java new file mode 100644 index 00000000..0330ab86 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodOneHolder.java @@ -0,0 +1,33 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodOneBinding; +import com.fongmi.android.tv.ui.adapter.VodAdapter; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodOneHolder extends BaseVodHolder { + + private final VodAdapter.OnClickListener listener; + private final AdapterVodOneBinding binding; + + public VodOneHolder(@NonNull AdapterVodOneBinding binding, VodAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.site.setText(item.getSiteName()); + binding.remark.setText(item.getVodRemarks()); + binding.site.setVisibility(item.getSiteVisible()); + binding.remark.setVisibility(item.getRemarkVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.rect(item.getVodName(), item.getVodPic(), binding.image); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodOvalHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodOvalHolder.java new file mode 100644 index 00000000..c2e14701 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodOvalHolder.java @@ -0,0 +1,36 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodOvalBinding; +import com.fongmi.android.tv.ui.adapter.VodAdapter; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodOvalHolder extends BaseVodHolder { + + private final VodAdapter.OnClickListener listener; + private final AdapterVodOvalBinding binding; + + public VodOvalHolder(@NonNull AdapterVodOvalBinding binding, VodAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + public VodOvalHolder size(int[] size) { + binding.image.getLayoutParams().width = size[0]; + binding.image.getLayoutParams().height = size[1]; + return this; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.name.setVisibility(item.getNameVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.oval(item.getVodName(), item.getVodPic(), binding.image); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodRectHolder.java b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodRectHolder.java new file mode 100644 index 00000000..5422cec3 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/holder/VodRectHolder.java @@ -0,0 +1,42 @@ +package com.fongmi.android.tv.ui.holder; + +import androidx.annotation.NonNull; + +import com.fongmi.android.tv.bean.Vod; +import com.fongmi.android.tv.databinding.AdapterVodRectBinding; +import com.fongmi.android.tv.ui.adapter.VodAdapter; +import com.fongmi.android.tv.ui.base.BaseVodHolder; +import com.fongmi.android.tv.utils.ImgUtil; + +public class VodRectHolder extends BaseVodHolder { + + private final VodAdapter.OnClickListener listener; + private final AdapterVodRectBinding binding; + + public VodRectHolder(@NonNull AdapterVodRectBinding binding, VodAdapter.OnClickListener listener) { + super(binding.getRoot()); + this.binding = binding; + this.listener = listener; + } + + public VodRectHolder size(int[] size) { + binding.getRoot().getLayoutParams().width = size[0]; + binding.getRoot().getLayoutParams().height = size[1]; + return this; + } + + @Override + public void initView(Vod item) { + binding.name.setText(item.getVodName()); + binding.year.setText(item.getVodYear()); + binding.site.setText(item.getSiteName()); + binding.remark.setText(item.getVodRemarks()); + binding.site.setVisibility(item.getSiteVisible()); + binding.name.setVisibility(item.getNameVisible()); + binding.year.setVisibility(item.getYearVisible()); + binding.remark.setVisibility(item.getRemarkVisible()); + binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); + binding.getRoot().setOnLongClickListener(v -> listener.onLongClick(item)); + ImgUtil.rect(item.getVodName(), item.getVodPic(), binding.image); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/utils/Biometric.java b/app/src/mobile/java/com/fongmi/android/tv/utils/Biometric.java new file mode 100644 index 00000000..33bedfc4 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/utils/Biometric.java @@ -0,0 +1,48 @@ +package com.fongmi.android.tv.utils; + +import androidx.annotation.NonNull; +import androidx.biometric.BiometricManager; +import androidx.biometric.BiometricPrompt; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentActivity; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; + +public class Biometric { + + private static BiometricManager getManager() { + return BiometricManager.from(App.get()); + } + + private static int canAuthenticate() { + return getManager().canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_STRONG); + } + + public static boolean enable() { + return canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS; + } + + public static void show(FragmentActivity activity) { + prompt(activity).authenticate(new BiometricPrompt.PromptInfo.Builder().setTitle(ResUtil.getString(R.string.app_name)).setNegativeButtonText(ResUtil.getString(R.string.dialog_negative)).build()); + } + + private static BiometricPrompt prompt(FragmentActivity activity) { + return new BiometricPrompt(activity, ContextCompat.getMainExecutor(activity), new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) { + Notify.show(errString.toString()); + } + + @Override + public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { + ((Callback) activity).onBiometricSuccess(); + } + }); + } + + public interface Callback { + + void onBiometricSuccess(); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/utils/DLNADevice.java b/app/src/mobile/java/com/fongmi/android/tv/utils/DLNADevice.java new file mode 100644 index 00000000..4594fc64 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/utils/DLNADevice.java @@ -0,0 +1,50 @@ +package com.fongmi.android.tv.utils; + +import com.android.cast.dlna.dmc.DLNACastManager; +import com.fongmi.android.tv.bean.Device; + +import java.util.ArrayList; +import java.util.List; + +public class DLNADevice { + + private final List> devices; + + private static class Loader { + static volatile DLNADevice INSTANCE = new DLNADevice(); + } + + public static DLNADevice get() { + return Loader.INSTANCE; + } + + public DLNADevice() { + this.devices = new ArrayList<>(); + } + + public List getAll() { + List items = new ArrayList<>(); + for (org.fourthline.cling.model.meta.Device item : devices) items.add(Device.get(item)); + return items; + } + + public List add(org.fourthline.cling.model.meta.Device item) { + devices.remove(item); + devices.add(item); + return getAll(); + } + + public Device remove(org.fourthline.cling.model.meta.Device device) { + devices.remove(device); + return Device.get(device); + } + + public void disconnect() { + for (org.fourthline.cling.model.meta.Device device : devices) DLNACastManager.INSTANCE.disconnectDevice(device); + } + + public org.fourthline.cling.model.meta.Device find(com.fongmi.android.tv.bean.Device item) { + for (org.fourthline.cling.model.meta.Device device : devices) if (device.getIdentity().getUdn().getIdentifierString().equals(item.getUuid())) return device; + return null; + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/utils/PiP.java b/app/src/mobile/java/com/fongmi/android/tv/utils/PiP.java new file mode 100644 index 00000000..86951236 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/utils/PiP.java @@ -0,0 +1,100 @@ +package com.fongmi.android.tv.utils; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.PictureInPictureParams; +import android.app.RemoteAction; +import android.content.pm.PackageManager; +import android.graphics.Rect; +import android.graphics.drawable.Icon; +import android.os.Build; +import android.util.Rational; +import android.view.View; + +import androidx.annotation.DrawableRes; +import androidx.annotation.StringRes; +import androidx.media3.ui.R; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.event.ActionEvent; +import com.fongmi.android.tv.receiver.ActionReceiver; + +import java.util.ArrayList; +import java.util.List; + +public class PiP { + + private PictureInPictureParams.Builder builder; + + public static boolean noPiP() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !App.get().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); + } + + @TargetApi(Build.VERSION_CODES.O) + private RemoteAction buildRemoteAction(Activity activity, @DrawableRes int icon, @StringRes int title, String action) { + return new RemoteAction(Icon.createWithResource(activity, icon), activity.getString(title), "", ActionReceiver.getPendingIntent(activity, action)); + } + + private RemoteAction getPlayPauseAction(Activity activity, boolean play) { + if (play) return buildRemoteAction(activity, R.drawable.exo_icon_pause, R.string.exo_controls_pause_description, ActionEvent.PAUSE); + return buildRemoteAction(activity, R.drawable.exo_icon_play, R.string.exo_controls_play_description, ActionEvent.PLAY); + } + + public PiP() { + if (noPiP()) return; + this.builder = new PictureInPictureParams.Builder(); + } + + public boolean isInMode(Activity activity) { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && activity.isInPictureInPictureMode(); + } + + public void update(Activity activity, View view) { + if (noPiP()) return; + Rect sourceRectHint = new Rect(); + view.getGlobalVisibleRect(sourceRectHint); + builder.setSourceRectHint(sourceRectHint); + try { + activity.setPictureInPictureParams(builder.build()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void update(Activity activity, boolean play) { + if (noPiP()) return; + List actions = new ArrayList<>(); + actions.add(buildRemoteAction(activity, R.drawable.exo_icon_previous, R.string.exo_controls_previous_description, ActionEvent.PREV)); + actions.add(getPlayPauseAction(activity, play)); + actions.add(buildRemoteAction(activity, R.drawable.exo_icon_next, R.string.exo_controls_next_description, ActionEvent.NEXT)); + try { + activity.setPictureInPictureParams(builder.setActions(actions).build()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void enter(Activity activity, int width, int height, int scale) { + try { + if (noPiP() || activity.isInPictureInPictureMode() || !Setting.isBackgroundPiP()) return; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) builder.setSeamlessResizeEnabled(true); + if (scale == 1) builder.setAspectRatio(new Rational(16, 9)); + else if (scale == 2) builder.setAspectRatio(new Rational(4, 3)); + else builder.setAspectRatio(getRational(width, height)); + activity.enterPictureInPictureMode(builder.build()); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private Rational getRational(int width, int height) { + Rational limitWide = new Rational(239, 100); + Rational limitTall = new Rational(100, 239); + Rational rational = new Rational(width, height); + if (rational.isInfinite()) return new Rational(16, 9); + if (rational.floatValue() > limitWide.floatValue()) return limitWide; + if (rational.floatValue() < limitTall.floatValue()) return limitTall; + return rational; + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/utils/ScanTask.java b/app/src/mobile/java/com/fongmi/android/tv/utils/ScanTask.java new file mode 100644 index 00000000..d6c8a9dd --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/utils/ScanTask.java @@ -0,0 +1,96 @@ +package com.fongmi.android.tv.utils; + +import com.fongmi.android.tv.App; +import com.fongmi.android.tv.bean.Device; +import com.fongmi.android.tv.server.Server; +import com.github.catvod.net.OkHttp; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import okhttp3.OkHttpClient; +import okhttp3.Response; + +public class ScanTask { + + private final List devices; + private final OkHttpClient client; + + private ExecutorService executor; + private Listener listener; + + public ScanTask(Listener listener) { + this.devices = Collections.synchronizedList(new ArrayList<>()); + this.client = OkHttp.client(1000); + this.listener = listener; + } + + public void start(List ips) { + App.execute(() -> run(getUrl(ips))); + } + + public void start(String url) { + App.execute(() -> run(List.of(url))); + } + + public void stop() { + if (executor != null) executor.shutdownNow(); + executor = null; + listener = null; + } + + private void init() { + if (executor != null) executor.shutdownNow(); + executor = Executors.newCachedThreadPool(); + devices.clear(); + } + + private void run(List items) { + try { + init(); + getDevice(items); + } catch (Exception e) { + e.printStackTrace(); + } finally { + App.post(() -> { + if (listener != null) listener.onFind(devices); + }); + } + } + + private void getDevice(List urls) throws Exception { + CountDownLatch cd = new CountDownLatch(urls.size() - 1); + for (String url : urls) executor.execute(() -> findDevice(cd, url)); + cd.await(); + } + + private List getUrl(List ips) { + Set urls = new HashSet<>(ips); + String local = Server.get().getAddress(); + String base = local.substring(0, local.lastIndexOf(".") + 1); + for (int i = 1; i < 256; i++) urls.add(base + i + ":9978"); + return new ArrayList<>(urls); + } + + private void findDevice(CountDownLatch cd, String url) { + if (url.contains(Server.get().getAddress())) return; + try (Response res = OkHttp.newCall(client, url.concat("/device")).execute()) { + Device device = Device.objectFrom(res.body().string()); + if (device != null) devices.add(device.save()); + } catch (Exception ignored) { + } finally { + cd.countDown(); + } + } + + public interface Listener { + + void onFind(List devices); + } +} diff --git a/app/src/mobile/java/com/fongmi/android/tv/utils/Timer.java b/app/src/mobile/java/com/fongmi/android/tv/utils/Timer.java new file mode 100644 index 00000000..8bb8d8f8 --- /dev/null +++ b/app/src/mobile/java/com/fongmi/android/tv/utils/Timer.java @@ -0,0 +1,81 @@ +package com.fongmi.android.tv.utils; + +import android.os.CountDownTimer; + +import com.fongmi.android.tv.event.ActionEvent; + +import java.util.concurrent.TimeUnit; + +public class Timer { + + private CountDownTimer timer; + private Callback callback; + private long tick; + + private static class Loader { + static volatile Timer INSTANCE = new Timer(); + } + + public static Timer get() { + return Loader.INSTANCE; + } + + public boolean isRunning() { + return timer != null; + } + + public void setCallback(Callback callback) { + this.callback = callback; + } + + public long getTick() { + return tick; + } + + public void set(long t) { + timer = new CountDownTimer(t, 1000) { + @Override + public void onTick(long tick) { + Timer.this.onTick(tick); + } + + @Override + public void onFinish() { + Timer.this.onFinish(); + } + }.start(); + } + + private void onTick(long tick) { + this.tick = tick; + if (callback != null) callback.onTick(tick); + } + + private void onFinish() { + if (callback != null) callback.onFinish(); + ActionEvent.pause(); + reset(); + } + + public void delay() { + cancel(); + set(TimeUnit.MINUTES.toMillis(5) + tick); + } + + public void reset() { + tick = 0; + cancel(); + } + + public void cancel() { + if (timer != null) timer.cancel(); + timer = null; + } + + public interface Callback { + + void onTick(long tick); + + void onFinish(); + } +} diff --git a/app/src/mobile/res/color-night/nav.xml b/app/src/mobile/res/color-night/nav.xml new file mode 100644 index 00000000..1d6ea80e --- /dev/null +++ b/app/src/mobile/res/color-night/nav.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/channel.xml b/app/src/mobile/res/color/channel.xml new file mode 100644 index 00000000..9b756c52 --- /dev/null +++ b/app/src/mobile/res/color/channel.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/control.xml b/app/src/mobile/res/color/control.xml new file mode 100644 index 00000000..c3f80226 --- /dev/null +++ b/app/src/mobile/res/color/control.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/dialog_button_text.xml b/app/src/mobile/res/color/dialog_button_text.xml new file mode 100644 index 00000000..b9cf3f81 --- /dev/null +++ b/app/src/mobile/res/color/dialog_button_text.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/epg.xml b/app/src/mobile/res/color/epg.xml new file mode 100644 index 00000000..024612b9 --- /dev/null +++ b/app/src/mobile/res/color/epg.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/episode_text.xml b/app/src/mobile/res/color/episode_text.xml new file mode 100644 index 00000000..b9cf3f81 --- /dev/null +++ b/app/src/mobile/res/color/episode_text.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/flag_text.xml b/app/src/mobile/res/color/flag_text.xml new file mode 100644 index 00000000..b9cf3f81 --- /dev/null +++ b/app/src/mobile/res/color/flag_text.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/group.xml b/app/src/mobile/res/color/group.xml new file mode 100644 index 00000000..6be396f5 --- /dev/null +++ b/app/src/mobile/res/color/group.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/nav.xml b/app/src/mobile/res/color/nav.xml new file mode 100644 index 00000000..1d6ea80e --- /dev/null +++ b/app/src/mobile/res/color/nav.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/color/type_text_color.xml b/app/src/mobile/res/color/type_text_color.xml new file mode 100644 index 00000000..007c8461 --- /dev/null +++ b/app/src/mobile/res/color/type_text_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable-nodpi/wallpaper_1.webp b/app/src/mobile/res/drawable-nodpi/wallpaper_1.webp new file mode 100644 index 00000000..56d1c7d0 Binary files /dev/null and b/app/src/mobile/res/drawable-nodpi/wallpaper_1.webp differ diff --git a/app/src/mobile/res/drawable-nodpi/wallpaper_2.webp b/app/src/mobile/res/drawable-nodpi/wallpaper_2.webp new file mode 100644 index 00000000..b9220c97 Binary files /dev/null and b/app/src/mobile/res/drawable-nodpi/wallpaper_2.webp differ diff --git a/app/src/mobile/res/drawable-nodpi/wallpaper_3.webp b/app/src/mobile/res/drawable-nodpi/wallpaper_3.webp new file mode 100644 index 00000000..6159230f Binary files /dev/null and b/app/src/mobile/res/drawable-nodpi/wallpaper_3.webp differ diff --git a/app/src/mobile/res/drawable-nodpi/wallpaper_4.webp b/app/src/mobile/res/drawable-nodpi/wallpaper_4.webp new file mode 100644 index 00000000..883d843b Binary files /dev/null and b/app/src/mobile/res/drawable-nodpi/wallpaper_4.webp differ diff --git a/app/src/mobile/res/drawable/ic_action_choose.xml b/app/src/mobile/res/drawable/ic_action_choose.xml new file mode 100644 index 00000000..a56e7056 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_choose.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_delete.xml b/app/src/mobile/res/drawable/ic_action_delete.xml new file mode 100644 index 00000000..a1efe1df --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_grid.xml b/app/src/mobile/res/drawable/ic_action_grid.xml new file mode 100644 index 00000000..b77482e7 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_grid.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_history.xml b/app/src/mobile/res/drawable/ic_action_history.xml new file mode 100644 index 00000000..8763b73b --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_history.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_keep.xml b/app/src/mobile/res/drawable/ic_action_keep.xml new file mode 100644 index 00000000..8b3ea1cc --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_keep.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_list.xml b/app/src/mobile/res/drawable/ic_action_list.xml new file mode 100644 index 00000000..5c19726b --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_list.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_refresh.xml b/app/src/mobile/res/drawable/ic_action_refresh.xml new file mode 100644 index 00000000..0d768f68 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_refresh.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_retry.xml b/app/src/mobile/res/drawable/ic_action_retry.xml new file mode 100644 index 00000000..b524e450 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_retry.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_scan.xml b/app/src/mobile/res/drawable/ic_action_scan.xml new file mode 100644 index 00000000..f49fd018 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_scan.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + diff --git a/app/src/mobile/res/drawable/ic_action_search.xml b/app/src/mobile/res/drawable/ic_action_search.xml new file mode 100644 index 00000000..5023dc87 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_site.xml b/app/src/mobile/res/drawable/ic_action_site.xml new file mode 100644 index 00000000..41e9bad0 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_site.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_subtitle.xml b/app/src/mobile/res/drawable/ic_action_subtitle.xml new file mode 100644 index 00000000..f218cf12 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_subtitle.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_action_sync.xml b/app/src/mobile/res/drawable/ic_action_sync.xml new file mode 100644 index 00000000..49e364ba --- /dev/null +++ b/app/src/mobile/res/drawable/ic_action_sync.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_back.xml b/app/src/mobile/res/drawable/ic_back.xml new file mode 100644 index 00000000..13b0036e --- /dev/null +++ b/app/src/mobile/res/drawable/ic_back.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/ic_cast_mobile.xml b/app/src/mobile/res/drawable/ic_cast_mobile.xml new file mode 100644 index 00000000..2f9f9429 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_cast_mobile.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_cast_tv.xml b/app/src/mobile/res/drawable/ic_cast_tv.xml new file mode 100644 index 00000000..5e58f3be --- /dev/null +++ b/app/src/mobile/res/drawable/ic_cast_tv.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_back.xml b/app/src/mobile/res/drawable/ic_control_back.xml new file mode 100644 index 00000000..541189c3 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_back.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_cast.xml b/app/src/mobile/res/drawable/ic_control_cast.xml new file mode 100644 index 00000000..7a6c2c76 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_cast.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_danmaku_off.xml b/app/src/mobile/res/drawable/ic_control_danmaku_off.xml new file mode 100644 index 00000000..d53a8dc1 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_danmaku_off.xml @@ -0,0 +1,13 @@ + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/ic_control_danmaku_on.xml b/app/src/mobile/res/drawable/ic_control_danmaku_on.xml new file mode 100644 index 00000000..05a81f11 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_danmaku_on.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/ic_control_full.xml b/app/src/mobile/res/drawable/ic_control_full.xml new file mode 100644 index 00000000..d44128f3 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_full.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_info.xml b/app/src/mobile/res/drawable/ic_control_info.xml new file mode 100644 index 00000000..b08088ea --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_info.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_keep_off.xml b/app/src/mobile/res/drawable/ic_control_keep_off.xml new file mode 100644 index 00000000..ce0358bd --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_keep_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_keep_on.xml b/app/src/mobile/res/drawable/ic_control_keep_on.xml new file mode 100644 index 00000000..62515cde --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_keep_on.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_lock_off.xml b/app/src/mobile/res/drawable/ic_control_lock_off.xml new file mode 100644 index 00000000..2555e56b --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_lock_off.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_lock_on.xml b/app/src/mobile/res/drawable/ic_control_lock_on.xml new file mode 100644 index 00000000..de29a595 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_lock_on.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_rotate.xml b/app/src/mobile/res/drawable/ic_control_rotate.xml new file mode 100644 index 00000000..02342875 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_rotate.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_control_setting.xml b/app/src/mobile/res/drawable/ic_control_setting.xml new file mode 100644 index 00000000..ef479c29 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_control_setting.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_detail_more.xml b/app/src/mobile/res/drawable/ic_detail_more.xml new file mode 100644 index 00000000..02e1af4b --- /dev/null +++ b/app/src/mobile/res/drawable/ic_detail_more.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_detail_reverse.xml b/app/src/mobile/res/drawable/ic_detail_reverse.xml new file mode 100644 index 00000000..1cfa112a --- /dev/null +++ b/app/src/mobile/res/drawable/ic_detail_reverse.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/mobile/res/drawable/ic_fab_filter.xml b/app/src/mobile/res/drawable/ic_fab_filter.xml new file mode 100644 index 00000000..5b252c5c --- /dev/null +++ b/app/src/mobile/res/drawable/ic_fab_filter.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_fab_link.xml b/app/src/mobile/res/drawable/ic_fab_link.xml new file mode 100644 index 00000000..59081740 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_fab_link.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_fab_top.xml b/app/src/mobile/res/drawable/ic_fab_top.xml new file mode 100644 index 00000000..f90da115 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_fab_top.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_live_block.xml b/app/src/mobile/res/drawable/ic_live_block.xml new file mode 100644 index 00000000..63f329f3 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_live_block.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_live_boot.xml b/app/src/mobile/res/drawable/ic_live_boot.xml new file mode 100644 index 00000000..8b40e9c2 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_live_boot.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_live_pass.xml b/app/src/mobile/res/drawable/ic_live_pass.xml new file mode 100644 index 00000000..523610ad --- /dev/null +++ b/app/src/mobile/res/drawable/ic_live_pass.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_nav_live.xml b/app/src/mobile/res/drawable/ic_nav_live.xml new file mode 100644 index 00000000..ffcecf33 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_nav_live.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_nav_setting.xml b/app/src/mobile/res/drawable/ic_nav_setting.xml new file mode 100644 index 00000000..aed85331 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_nav_setting.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_nav_vod.xml b/app/src/mobile/res/drawable/ic_nav_vod.xml new file mode 100644 index 00000000..2a9172ca --- /dev/null +++ b/app/src/mobile/res/drawable/ic_nav_vod.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_setting_delete.xml b/app/src/mobile/res/drawable/ic_setting_delete.xml new file mode 100644 index 00000000..7603e286 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_setting_delete.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_setting_paste.xml b/app/src/mobile/res/drawable/ic_setting_paste.xml new file mode 100644 index 00000000..9080f30b --- /dev/null +++ b/app/src/mobile/res/drawable/ic_setting_paste.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/ic_setting_switch.xml b/app/src/mobile/res/drawable/ic_setting_switch.xml new file mode 100644 index 00000000..dbb73447 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_setting_switch.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/ic_site_block.xml b/app/src/mobile/res/drawable/ic_site_block.xml new file mode 100644 index 00000000..63f329f3 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_site_block.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_site_change.xml b/app/src/mobile/res/drawable/ic_site_change.xml new file mode 100644 index 00000000..a6f6fb15 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_site_change.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_site_search.xml b/app/src/mobile/res/drawable/ic_site_search.xml new file mode 100644 index 00000000..cbf0cc71 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_site_search.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_subtitle_down.xml b/app/src/mobile/res/drawable/ic_subtitle_down.xml new file mode 100644 index 00000000..5252b67f --- /dev/null +++ b/app/src/mobile/res/drawable/ic_subtitle_down.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_subtitle_large.xml b/app/src/mobile/res/drawable/ic_subtitle_large.xml new file mode 100644 index 00000000..40c23537 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_subtitle_large.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_subtitle_reset.xml b/app/src/mobile/res/drawable/ic_subtitle_reset.xml new file mode 100644 index 00000000..dbd38d71 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_subtitle_reset.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_subtitle_small.xml b/app/src/mobile/res/drawable/ic_subtitle_small.xml new file mode 100644 index 00000000..454078be --- /dev/null +++ b/app/src/mobile/res/drawable/ic_subtitle_small.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_subtitle_up.xml b/app/src/mobile/res/drawable/ic_subtitle_up.xml new file mode 100644 index 00000000..ee521048 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_subtitle_up.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_sync_download.xml b/app/src/mobile/res/drawable/ic_sync_download.xml new file mode 100644 index 00000000..5252b67f --- /dev/null +++ b/app/src/mobile/res/drawable/ic_sync_download.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_sync_two.xml b/app/src/mobile/res/drawable/ic_sync_two.xml new file mode 100644 index 00000000..205b8023 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_sync_two.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_sync_upload.xml b/app/src/mobile/res/drawable/ic_sync_upload.xml new file mode 100644 index 00000000..ee521048 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_sync_upload.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_widget_bright_high.xml b/app/src/mobile/res/drawable/ic_widget_bright_high.xml new file mode 100644 index 00000000..27ceface --- /dev/null +++ b/app/src/mobile/res/drawable/ic_widget_bright_high.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_widget_bright_low.xml b/app/src/mobile/res/drawable/ic_widget_bright_low.xml new file mode 100644 index 00000000..90829a90 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_widget_bright_low.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_widget_bright_medium.xml b/app/src/mobile/res/drawable/ic_widget_bright_medium.xml new file mode 100644 index 00000000..63b5780b --- /dev/null +++ b/app/src/mobile/res/drawable/ic_widget_bright_medium.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_widget_volume_high.xml b/app/src/mobile/res/drawable/ic_widget_volume_high.xml new file mode 100644 index 00000000..477478e5 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_widget_volume_high.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_widget_volume_low.xml b/app/src/mobile/res/drawable/ic_widget_volume_low.xml new file mode 100644 index 00000000..c0e6d2b2 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_widget_volume_low.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/ic_widget_volume_medium.xml b/app/src/mobile/res/drawable/ic_widget_volume_medium.xml new file mode 100644 index 00000000..1e12cca6 --- /dev/null +++ b/app/src/mobile/res/drawable/ic_widget_volume_medium.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/mobile/res/drawable/m3_switch_thumb.xml b/app/src/mobile/res/drawable/m3_switch_thumb.xml new file mode 100644 index 00000000..31130dd9 --- /dev/null +++ b/app/src/mobile/res/drawable/m3_switch_thumb.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/m3_switch_track.xml b/app/src/mobile/res/drawable/m3_switch_track.xml new file mode 100644 index 00000000..d36c3f4f --- /dev/null +++ b/app/src/mobile/res/drawable/m3_switch_track.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/selector_dialog_button_bg.xml b/app/src/mobile/res/drawable/selector_dialog_button_bg.xml new file mode 100644 index 00000000..9c8add72 --- /dev/null +++ b/app/src/mobile/res/drawable/selector_dialog_button_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/selector_episode_bg.xml b/app/src/mobile/res/drawable/selector_episode_bg.xml new file mode 100644 index 00000000..fb219a4c --- /dev/null +++ b/app/src/mobile/res/drawable/selector_episode_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/selector_episode_hori_bg.xml b/app/src/mobile/res/drawable/selector_episode_hori_bg.xml new file mode 100644 index 00000000..01673e3a --- /dev/null +++ b/app/src/mobile/res/drawable/selector_episode_hori_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/selector_flag_bg.xml b/app/src/mobile/res/drawable/selector_flag_bg.xml new file mode 100644 index 00000000..9fcf8c7f --- /dev/null +++ b/app/src/mobile/res/drawable/selector_flag_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/selector_item_round.xml b/app/src/mobile/res/drawable/selector_item_round.xml new file mode 100644 index 00000000..b345ec0a --- /dev/null +++ b/app/src/mobile/res/drawable/selector_item_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shadow.xml b/app/src/mobile/res/drawable/shadow.xml new file mode 100644 index 00000000..7b584562 --- /dev/null +++ b/app/src/mobile/res/drawable/shadow.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_accent.xml b/app/src/mobile/res/drawable/shape_accent.xml new file mode 100644 index 00000000..068c5106 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_accent.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/mobile/res/drawable/shape_channel.xml b/app/src/mobile/res/drawable/shape_channel.xml new file mode 100644 index 00000000..04ec9b5a --- /dev/null +++ b/app/src/mobile/res/drawable/shape_channel.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_control.xml b/app/src/mobile/res/drawable/shape_control.xml new file mode 100644 index 00000000..bdddab46 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_control.xml @@ -0,0 +1,13 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_dialog_button.xml b/app/src/mobile/res/drawable/shape_dialog_button.xml new file mode 100644 index 00000000..2a52f166 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_dialog_button.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_dialog_button_selected.xml b/app/src/mobile/res/drawable/shape_dialog_button_selected.xml new file mode 100644 index 00000000..bc3b24f9 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_dialog_button_selected.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_episode.xml b/app/src/mobile/res/drawable/shape_episode.xml new file mode 100644 index 00000000..76f228e7 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_episode.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_episode_hori.xml b/app/src/mobile/res/drawable/shape_episode_hori.xml new file mode 100644 index 00000000..5ab135a8 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_episode_hori.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_episode_selected.xml b/app/src/mobile/res/drawable/shape_episode_selected.xml new file mode 100644 index 00000000..11be8eab --- /dev/null +++ b/app/src/mobile/res/drawable/shape_episode_selected.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_group.xml b/app/src/mobile/res/drawable/shape_group.xml new file mode 100644 index 00000000..0bbb932a --- /dev/null +++ b/app/src/mobile/res/drawable/shape_group.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_item.xml b/app/src/mobile/res/drawable/shape_item.xml new file mode 100644 index 00000000..8bf974bd --- /dev/null +++ b/app/src/mobile/res/drawable/shape_item.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_item_round_activated.xml b/app/src/mobile/res/drawable/shape_item_round_activated.xml new file mode 100644 index 00000000..a0504df9 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_item_round_activated.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_item_round_normal.xml b/app/src/mobile/res/drawable/shape_item_round_normal.xml new file mode 100644 index 00000000..09ab3c80 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_item_round_normal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_item_selected.xml b/app/src/mobile/res/drawable/shape_item_selected.xml new file mode 100644 index 00000000..11be8eab --- /dev/null +++ b/app/src/mobile/res/drawable/shape_item_selected.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_live_info.xml b/app/src/mobile/res/drawable/shape_live_info.xml new file mode 100644 index 00000000..dc4b2e12 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_live_info.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_live_list.xml b/app/src/mobile/res/drawable/shape_live_list.xml new file mode 100644 index 00000000..4a1d94d7 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_live_list.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_vod.xml b/app/src/mobile/res/drawable/shape_vod.xml new file mode 100644 index 00000000..82520e6d --- /dev/null +++ b/app/src/mobile/res/drawable/shape_vod.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_vod_list.xml b/app/src/mobile/res/drawable/shape_vod_list.xml new file mode 100644 index 00000000..d0324ba5 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_vod_list.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_vod_name.xml b/app/src/mobile/res/drawable/shape_vod_name.xml new file mode 100644 index 00000000..a2c02176 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_vod_name.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_vod_oval.xml b/app/src/mobile/res/drawable/shape_vod_oval.xml new file mode 100644 index 00000000..3eb6c84a --- /dev/null +++ b/app/src/mobile/res/drawable/shape_vod_oval.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_widget.xml b/app/src/mobile/res/drawable/shape_widget.xml new file mode 100644 index 00000000..aa030598 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_widget.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/drawable/shape_widget_error.xml b/app/src/mobile/res/drawable/shape_widget_error.xml new file mode 100644 index 00000000..ce9fc6b3 --- /dev/null +++ b/app/src/mobile/res/drawable/shape_widget_error.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout-sw600dp/activity_video.xml b/app/src/mobile/res/layout-sw600dp/activity_video.xml new file mode 100644 index 00000000..ce07a159 --- /dev/null +++ b/app/src/mobile/res/layout-sw600dp/activity_video.xml @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout-sw600dp/view_control_right.xml b/app/src/mobile/res/layout-sw600dp/view_control_right.xml new file mode 100644 index 00000000..342d41c7 --- /dev/null +++ b/app/src/mobile/res/layout-sw600dp/view_control_right.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_collect.xml b/app/src/mobile/res/layout/activity_collect.xml new file mode 100644 index 00000000..8fa395f6 --- /dev/null +++ b/app/src/mobile/res/layout/activity_collect.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_file.xml b/app/src/mobile/res/layout/activity_file.xml new file mode 100644 index 00000000..c2853e20 --- /dev/null +++ b/app/src/mobile/res/layout/activity_file.xml @@ -0,0 +1,21 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_folder.xml b/app/src/mobile/res/layout/activity_folder.xml new file mode 100644 index 00000000..f4edec1e --- /dev/null +++ b/app/src/mobile/res/layout/activity_folder.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_history.xml b/app/src/mobile/res/layout/activity_history.xml new file mode 100644 index 00000000..08125f00 --- /dev/null +++ b/app/src/mobile/res/layout/activity_history.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_home.xml b/app/src/mobile/res/layout/activity_home.xml new file mode 100644 index 00000000..277a91d6 --- /dev/null +++ b/app/src/mobile/res/layout/activity_home.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_keep.xml b/app/src/mobile/res/layout/activity_keep.xml new file mode 100644 index 00000000..1d775ee7 --- /dev/null +++ b/app/src/mobile/res/layout/activity_keep.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_live.xml b/app/src/mobile/res/layout/activity_live.xml new file mode 100644 index 00000000..9a013b4b --- /dev/null +++ b/app/src/mobile/res/layout/activity_live.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/mobile/res/layout/activity_scan.xml b/app/src/mobile/res/layout/activity_scan.xml new file mode 100644 index 00000000..a3841f7b --- /dev/null +++ b/app/src/mobile/res/layout/activity_scan.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/activity_video.xml b/app/src/mobile/res/layout/activity_video.xml new file mode 100644 index 00000000..d804a90e --- /dev/null +++ b/app/src/mobile/res/layout/activity_video.xml @@ -0,0 +1,312 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_channel.xml b/app/src/mobile/res/layout/adapter_channel.xml new file mode 100644 index 00000000..f093dc42 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_channel.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_collect.xml b/app/src/mobile/res/layout/adapter_collect.xml new file mode 100644 index 00000000..a44545e8 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_collect.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/mobile/res/layout/adapter_collect_record.xml b/app/src/mobile/res/layout/adapter_collect_record.xml new file mode 100644 index 00000000..f82f511b --- /dev/null +++ b/app/src/mobile/res/layout/adapter_collect_record.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_collect_word.xml b/app/src/mobile/res/layout/adapter_collect_word.xml new file mode 100644 index 00000000..a31820b2 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_collect_word.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_config.xml b/app/src/mobile/res/layout/adapter_config.xml new file mode 100644 index 00000000..4ddeb35e --- /dev/null +++ b/app/src/mobile/res/layout/adapter_config.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_danmaku.xml b/app/src/mobile/res/layout/adapter_danmaku.xml new file mode 100644 index 00000000..92550408 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_danmaku.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_device.xml b/app/src/mobile/res/layout/adapter_device.xml new file mode 100644 index 00000000..de2be2b8 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_device.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_epg_data.xml b/app/src/mobile/res/layout/adapter_epg_data.xml new file mode 100644 index 00000000..eeef3ef9 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_epg_data.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_episode_grid.xml b/app/src/mobile/res/layout/adapter_episode_grid.xml new file mode 100644 index 00000000..bfc96f13 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_episode_grid.xml @@ -0,0 +1,16 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_episode_hori.xml b/app/src/mobile/res/layout/adapter_episode_hori.xml new file mode 100644 index 00000000..06efe93f --- /dev/null +++ b/app/src/mobile/res/layout/adapter_episode_hori.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_episode_vert.xml b/app/src/mobile/res/layout/adapter_episode_vert.xml new file mode 100644 index 00000000..99ee319f --- /dev/null +++ b/app/src/mobile/res/layout/adapter_episode_vert.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_file.xml b/app/src/mobile/res/layout/adapter_file.xml new file mode 100644 index 00000000..9e9439e5 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_file.xml @@ -0,0 +1,30 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_filter.xml b/app/src/mobile/res/layout/adapter_filter.xml new file mode 100644 index 00000000..8ccb530b --- /dev/null +++ b/app/src/mobile/res/layout/adapter_filter.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_flag.xml b/app/src/mobile/res/layout/adapter_flag.xml new file mode 100644 index 00000000..d9df10ed --- /dev/null +++ b/app/src/mobile/res/layout/adapter_flag.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_group.xml b/app/src/mobile/res/layout/adapter_group.xml new file mode 100644 index 00000000..50dd4490 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_group.xml @@ -0,0 +1,27 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_live.xml b/app/src/mobile/res/layout/adapter_live.xml new file mode 100644 index 00000000..16fccb08 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_live.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_parse_dark.xml b/app/src/mobile/res/layout/adapter_parse_dark.xml new file mode 100644 index 00000000..6d510904 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_parse_dark.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_parse_light.xml b/app/src/mobile/res/layout/adapter_parse_light.xml new file mode 100644 index 00000000..6d510904 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_parse_light.xml @@ -0,0 +1,11 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_quality.xml b/app/src/mobile/res/layout/adapter_quality.xml new file mode 100644 index 00000000..7c40ba45 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_quality.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_quick.xml b/app/src/mobile/res/layout/adapter_quick.xml new file mode 100644 index 00000000..a0eab286 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_quick.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_restore.xml b/app/src/mobile/res/layout/adapter_restore.xml new file mode 100644 index 00000000..a1a12b45 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_restore.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_site.xml b/app/src/mobile/res/layout/adapter_site.xml new file mode 100644 index 00000000..31dd3176 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_site.xml @@ -0,0 +1,39 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_track.xml b/app/src/mobile/res/layout/adapter_track.xml new file mode 100644 index 00000000..6cd12d80 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_track.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_type.xml b/app/src/mobile/res/layout/adapter_type.xml new file mode 100644 index 00000000..fc9825a8 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_type.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_value.xml b/app/src/mobile/res/layout/adapter_value.xml new file mode 100644 index 00000000..6d124f8e --- /dev/null +++ b/app/src/mobile/res/layout/adapter_value.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_vod.xml b/app/src/mobile/res/layout/adapter_vod.xml new file mode 100644 index 00000000..710178bb --- /dev/null +++ b/app/src/mobile/res/layout/adapter_vod.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_vod_list.xml b/app/src/mobile/res/layout/adapter_vod_list.xml new file mode 100644 index 00000000..8ff35237 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_vod_list.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_vod_one.xml b/app/src/mobile/res/layout/adapter_vod_one.xml new file mode 100644 index 00000000..a6e4c284 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_vod_one.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_vod_oval.xml b/app/src/mobile/res/layout/adapter_vod_oval.xml new file mode 100644 index 00000000..97d8624f --- /dev/null +++ b/app/src/mobile/res/layout/adapter_vod_oval.xml @@ -0,0 +1,37 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_vod_rect.xml b/app/src/mobile/res/layout/adapter_vod_rect.xml new file mode 100644 index 00000000..ee8a9851 --- /dev/null +++ b/app/src/mobile/res/layout/adapter_vod_rect.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_buffer.xml b/app/src/mobile/res/layout/dialog_buffer.xml new file mode 100644 index 00000000..937f7993 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_buffer.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_config.xml b/app/src/mobile/res/layout/dialog_config.xml new file mode 100644 index 00000000..c5aeae4a --- /dev/null +++ b/app/src/mobile/res/layout/dialog_config.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_control.xml b/app/src/mobile/res/layout/dialog_control.xml new file mode 100644 index 00000000..306967c8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_control.xml @@ -0,0 +1,298 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_danmaku.xml b/app/src/mobile/res/layout/dialog_danmaku.xml new file mode 100644 index 00000000..bd44e281 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_danmaku.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_device.xml b/app/src/mobile/res/layout/dialog_device.xml new file mode 100644 index 00000000..6b770c51 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_device.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_episode_grid.xml b/app/src/mobile/res/layout/dialog_episode_grid.xml new file mode 100644 index 00000000..2a7b8e95 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_episode_grid.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_episode_list.xml b/app/src/mobile/res/layout/dialog_episode_list.xml new file mode 100644 index 00000000..05f5b9a8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_episode_list.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_filter.xml b/app/src/mobile/res/layout/dialog_filter.xml new file mode 100644 index 00000000..5712deda --- /dev/null +++ b/app/src/mobile/res/layout/dialog_filter.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_history.xml b/app/src/mobile/res/layout/dialog_history.xml new file mode 100644 index 00000000..fb114fe8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_history.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_info.xml b/app/src/mobile/res/layout/dialog_info.xml new file mode 100644 index 00000000..915565d8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_info.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_link.xml b/app/src/mobile/res/layout/dialog_link.xml new file mode 100644 index 00000000..c74900a5 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_link.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_live.xml b/app/src/mobile/res/layout/dialog_live.xml new file mode 100644 index 00000000..fb114fe8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_live.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_pass.xml b/app/src/mobile/res/layout/dialog_pass.xml new file mode 100644 index 00000000..f6e92c45 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_pass.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_proxy.xml b/app/src/mobile/res/layout/dialog_proxy.xml new file mode 100644 index 00000000..4d6825b8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_proxy.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_receive.xml b/app/src/mobile/res/layout/dialog_receive.xml new file mode 100644 index 00000000..14e02dcc --- /dev/null +++ b/app/src/mobile/res/layout/dialog_receive.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_restore.xml b/app/src/mobile/res/layout/dialog_restore.xml new file mode 100644 index 00000000..48330097 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_restore.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_site.xml b/app/src/mobile/res/layout/dialog_site.xml new file mode 100644 index 00000000..fb114fe8 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_site.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_speed.xml b/app/src/mobile/res/layout/dialog_speed.xml new file mode 100644 index 00000000..a6723559 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_speed.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_subtitle.xml b/app/src/mobile/res/layout/dialog_subtitle.xml new file mode 100644 index 00000000..cf27d4be --- /dev/null +++ b/app/src/mobile/res/layout/dialog_subtitle.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_timer.xml b/app/src/mobile/res/layout/dialog_timer.xml new file mode 100644 index 00000000..22c830a3 --- /dev/null +++ b/app/src/mobile/res/layout/dialog_timer.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_track.xml b/app/src/mobile/res/layout/dialog_track.xml new file mode 100644 index 00000000..75e8467a --- /dev/null +++ b/app/src/mobile/res/layout/dialog_track.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_ua.xml b/app/src/mobile/res/layout/dialog_ua.xml new file mode 100644 index 00000000..f69ec08f --- /dev/null +++ b/app/src/mobile/res/layout/dialog_ua.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/dialog_update.xml b/app/src/mobile/res/layout/dialog_update.xml new file mode 100644 index 00000000..247b0fab --- /dev/null +++ b/app/src/mobile/res/layout/dialog_update.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/fragment_episode.xml b/app/src/mobile/res/layout/fragment_episode.xml new file mode 100644 index 00000000..ec0f957a --- /dev/null +++ b/app/src/mobile/res/layout/fragment_episode.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/mobile/res/layout/fragment_setting.xml b/app/src/mobile/res/layout/fragment_setting.xml new file mode 100644 index 00000000..96a975a8 --- /dev/null +++ b/app/src/mobile/res/layout/fragment_setting.xml @@ -0,0 +1,496 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/fragment_setting_player.xml b/app/src/mobile/res/layout/fragment_setting_player.xml new file mode 100644 index 00000000..7fea954d --- /dev/null +++ b/app/src/mobile/res/layout/fragment_setting_player.xml @@ -0,0 +1,379 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/fragment_type.xml b/app/src/mobile/res/layout/fragment_type.xml new file mode 100644 index 00000000..1c4142fb --- /dev/null +++ b/app/src/mobile/res/layout/fragment_type.xml @@ -0,0 +1,22 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/fragment_vod.xml b/app/src/mobile/res/layout/fragment_vod.xml new file mode 100644 index 00000000..f1eeaca2 --- /dev/null +++ b/app/src/mobile/res/layout/fragment_vod.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_control_live.xml b/app/src/mobile/res/layout/view_control_live.xml new file mode 100644 index 00000000..eed96422 --- /dev/null +++ b/app/src/mobile/res/layout/view_control_live.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_control_live_action.xml b/app/src/mobile/res/layout/view_control_live_action.xml new file mode 100644 index 00000000..c25c5624 --- /dev/null +++ b/app/src/mobile/res/layout/view_control_live_action.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_control_right.xml b/app/src/mobile/res/layout/view_control_right.xml new file mode 100644 index 00000000..6c801152 --- /dev/null +++ b/app/src/mobile/res/layout/view_control_right.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_control_seek.xml b/app/src/mobile/res/layout/view_control_seek.xml new file mode 100644 index 00000000..9327f998 --- /dev/null +++ b/app/src/mobile/res/layout/view_control_seek.xml @@ -0,0 +1,38 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_control_vod.xml b/app/src/mobile/res/layout/view_control_vod.xml new file mode 100644 index 00000000..746f42e1 --- /dev/null +++ b/app/src/mobile/res/layout/view_control_vod.xml @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_control_vod_action.xml b/app/src/mobile/res/layout/view_control_vod_action.xml new file mode 100644 index 00000000..1e24a39f --- /dev/null +++ b/app/src/mobile/res/layout/view_control_vod_action.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_marquee.xml b/app/src/mobile/res/layout/view_marquee.xml new file mode 100644 index 00000000..5a3ad535 --- /dev/null +++ b/app/src/mobile/res/layout/view_marquee.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_scanner.xml b/app/src/mobile/res/layout/view_scanner.xml new file mode 100644 index 00000000..97416d0f --- /dev/null +++ b/app/src/mobile/res/layout/view_scanner.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/app/src/mobile/res/layout/view_shadow.xml b/app/src/mobile/res/layout/view_shadow.xml new file mode 100644 index 00000000..fa8b772d --- /dev/null +++ b/app/src/mobile/res/layout/view_shadow.xml @@ -0,0 +1,5 @@ + + diff --git a/app/src/mobile/res/layout/view_widget_live.xml b/app/src/mobile/res/layout/view_widget_live.xml new file mode 100644 index 00000000..555aaa10 --- /dev/null +++ b/app/src/mobile/res/layout/view_widget_live.xml @@ -0,0 +1,251 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/layout/view_widget_vod.xml b/app/src/mobile/res/layout/view_widget_vod.xml new file mode 100644 index 00000000..7fd19734 --- /dev/null +++ b/app/src/mobile/res/layout/view_widget_vod.xml @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/menu/menu_nav.xml b/app/src/mobile/res/menu/menu_nav.xml new file mode 100644 index 00000000..1f8c010f --- /dev/null +++ b/app/src/mobile/res/menu/menu_nav.xml @@ -0,0 +1,22 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/mobile/res/values-night/colors.xml b/app/src/mobile/res/values-night/colors.xml new file mode 100644 index 00000000..fa9896f2 --- /dev/null +++ b/app/src/mobile/res/values-night/colors.xml @@ -0,0 +1,8 @@ + + + #FDD835 + #FBC02D + #FDD835 + @color/black_50 + + \ No newline at end of file diff --git a/app/src/mobile/res/values-v27/styles.xml b/app/src/mobile/res/values-v27/styles.xml new file mode 100644 index 00000000..0fc60ad5 --- /dev/null +++ b/app/src/mobile/res/values-v27/styles.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/app/src/mobile/res/values-zh-rCN/strings.xml b/app/src/mobile/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..71c38b40 --- /dev/null +++ b/app/src/mobile/res/values-zh-rCN/strings.xml @@ -0,0 +1,58 @@ + + + + 我的收藏 + 最近观看 + + + 首页 + 收藏 + 视频 + 直播 + 设置 + + + 推荐 + + + 线路 + 更多 + 选集 + 画质 + 简介 + 快搜"%s" + + + 选择装置 + 装置已离线 + 请扫描影视 QRCode 进行绑定 + + + 倍速 + 缩放 + 轨道 + 其它 + + + 删除全部纪录? + 收藏纪录将会全部删除。 + 观看纪录将会全部删除。 + + + 5 分钟 + 15 分钟 + 30 分钟 + 1 小时 + 延长 5 分钟 + 取消定时器 + + + 已于主萤幕新增捷径。 + + + 关闭 + 开启 + 画中画 + + + \ No newline at end of file diff --git a/app/src/mobile/res/values-zh-rTW/strings.xml b/app/src/mobile/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..82c8f6f2 --- /dev/null +++ b/app/src/mobile/res/values-zh-rTW/strings.xml @@ -0,0 +1,56 @@ + + + + 我的收藏 + 最近觀看 + + + 點播 + 直播 + 設定 + + + 推薦 + + + 線路 + 更多 + 選集 + 畫質 + 簡介 + 快搜「%s + + + 選擇裝置 + 裝置已離線 + 請掃描影視 QRCode 進行綁定 + + + 倍速 + 縮放 + 軌道 + 其它 + + + 刪除全部紀錄? + 收藏紀錄將會全部刪除。 + 觀看紀錄將會全部刪除。 + + + 5 分鐘 + 15 分鐘 + 30 分鐘 + 1 小時 + 延長 5 分鐘 + 取消定時器 + + + 已於主螢幕新增捷徑。 + + + 關閉 + 開啟 + 子母畫面 + + + \ No newline at end of file diff --git a/app/src/mobile/res/values/arrays.xml b/app/src/mobile/res/values/arrays.xml new file mode 100644 index 00000000..db7a63a4 --- /dev/null +++ b/app/src/mobile/res/values/arrays.xml @@ -0,0 +1,10 @@ + + + + + @drawable/ic_sync_two + @drawable/ic_sync_upload + @drawable/ic_sync_download + + + \ No newline at end of file diff --git a/app/src/mobile/res/values/colors.xml b/app/src/mobile/res/values/colors.xml new file mode 100644 index 00000000..042fc82e --- /dev/null +++ b/app/src/mobile/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #FFEB3B + #FDD835 + #FFEB3B + @color/white_80 + #4DFFEB3B + + \ No newline at end of file diff --git a/app/src/mobile/res/values/strings.xml b/app/src/mobile/res/values/strings.xml new file mode 100644 index 00000000..4e2e2dfe --- /dev/null +++ b/app/src/mobile/res/values/strings.xml @@ -0,0 +1,58 @@ + + + + Keep + History + + + Home + Favorite + Video + Live + Setting + + + Home + + + Flag + More + Episode + Quality + Summary + Searching %s + + + Select devices + Device is offline + Please scan QR Code to bind + + + Speed + Scale + Track + Other + + + Delete all records? + All keep records will be removed. + All history records will be removed. + + + 5 minutes + 15 minutes + 30 minutes + 1 hour + Add 5 minutes + Cancel timer + + + Shortcut has been added. + + + Off + On + PiP + + + \ No newline at end of file diff --git a/app/src/mobile/res/values/styles.xml b/app/src/mobile/res/values/styles.xml new file mode 100644 index 00000000..576a520f --- /dev/null +++ b/app/src/mobile/res/values/styles.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..2192f79e --- /dev/null +++ b/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'com.android.application' version '8.8.2' apply false + id 'com.android.library' version '8.8.2' apply false + id 'com.chaquo.python' version '15.0.1' apply false +} + +tasks.register('clean', Delete) { + delete rootProject.layout.buildDirectory +} + +project.ext { + gsonVersion = '2.11.0' + media3Version = '1.6.1' + okhttpVersion = '5.0.0-alpha.14' +} diff --git a/catvod/.gitignore b/catvod/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/catvod/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/catvod/build.gradle b/catvod/build.gradle new file mode 100644 index 00000000..bba383c4 --- /dev/null +++ b/catvod/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.github.catvod.crawler' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +dependencies { + api 'androidx.annotation:annotation:1.6.0' + api 'androidx.preference:preference:1.2.1' + api 'com.google.code.gson:gson:' + gsonVersion + api 'com.google.net.cronet:cronet-okhttp:0.1.0' + api 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' + api 'com.orhanobut:logger:2.2.0' + api 'com.squareup.okhttp3:okhttp:' + okhttpVersion + api 'com.squareup.okhttp3:okhttp-dnsoverhttps:' + okhttpVersion + api 'com.squareup.okhttp3:logging-interceptor:' + okhttpVersion + api 'org.chromium.net:cronet-embedded:76.3809.111' + api('com.google.guava:guava:33.3.1-android') { + exclude group: 'com.google.code.findbugs', module: 'jsr305' + exclude group: 'org.checkerframework', module: 'checker-compat-qual' + exclude group: 'org.checkerframework', module: 'checker-qual' + exclude group: 'com.google.errorprone', module: 'error_prone_annotations' + exclude group: 'com.google.j2objc', module: 'j2objc-annotations' + exclude group: 'org.codehaus.mojo', module: 'animal-sniffer-annotations' + } +} \ No newline at end of file diff --git a/catvod/src/main/AndroidManifest.xml b/catvod/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/catvod/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/catvod/src/main/java/com/github/catvod/Init.java b/catvod/src/main/java/com/github/catvod/Init.java new file mode 100644 index 00000000..2d6207a2 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/Init.java @@ -0,0 +1,26 @@ +package com.github.catvod; + +import android.content.Context; + +import java.lang.ref.WeakReference; + +public class Init { + + private WeakReference context; + + private static class Loader { + static volatile Init INSTANCE = new Init(); + } + + private static Init get() { + return Loader.INSTANCE; + } + + public static void set(Context context) { + get().context = new WeakReference<>(context); + } + + public static Context context() { + return get().context.get(); + } +} diff --git a/catvod/src/main/java/com/github/catvod/Proxy.java b/catvod/src/main/java/com/github/catvod/Proxy.java new file mode 100644 index 00000000..25b8a593 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/Proxy.java @@ -0,0 +1,20 @@ +package com.github.catvod; + +import com.github.catvod.utils.Util; + +public class Proxy { + + private static int port = -1; + + public static void set(int port) { + Proxy.port = port; + } + + public static int getPort() { + return port; + } + + public static String getUrl(boolean local) { + return "http://" + (local ? "127.0.0.1" : Util.getIp()) + ":" + getPort() + "/proxy"; + } +} diff --git a/catvod/src/main/java/com/github/catvod/bean/Doh.java b/catvod/src/main/java/com/github/catvod/bean/Doh.java new file mode 100644 index 00000000..605e56ce --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/bean/Doh.java @@ -0,0 +1,93 @@ +package com.github.catvod.bean; + +import android.content.Context; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import com.github.catvod.crawler.R; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Doh { + + @SerializedName("name") + private String name; + @SerializedName("url") + private String url; + @SerializedName("ips") + private List ips; + + public static List get(Context context) { + List items = new ArrayList<>(); + String[] urls = context.getResources().getStringArray(R.array.doh_url); + String[] names = context.getResources().getStringArray(R.array.doh_name); + for (int i = 0; i < names.length; i++) items.add(new Doh().name(names[i]).url(urls[i])); + return items; + } + + public static Doh objectFrom(String str) { + Doh item = new Gson().fromJson(str, Doh.class); + return item == null ? new Doh() : item; + } + + public static List arrayFrom(JsonElement element) { + Type listType = new TypeToken>() {}.getType(); + List items = new Gson().fromJson(element, listType); + return items == null ? new ArrayList<>() : items; + } + + public Doh name(String name) { + this.name = name; + return this; + } + + public Doh url(String url) { + this.url = url; + return this; + } + + public String getName() { + return TextUtils.isEmpty(name) ? "" : name; + } + + public String getUrl() { + return TextUtils.isEmpty(url) ? "" : url; + } + + public List getIps() { + return ips == null ? Collections.emptyList() : ips; + } + + public List getHosts() { + try { + List list = new ArrayList<>(); + for (String ip : getIps()) list.add(InetAddress.getByName(ip)); + return list.isEmpty() ? null : list; + } catch (Exception ignored) { + return null; + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Doh)) return false; + Doh it = (Doh) obj; + return getUrl().equals(it.getUrl()); + } + + @NonNull + @Override + public String toString() { + return new Gson().toJson(this); + } +} diff --git a/catvod/src/main/java/com/github/catvod/crawler/Spider.java b/catvod/src/main/java/com/github/catvod/crawler/Spider.java new file mode 100644 index 00000000..8b8e39fe --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/crawler/Spider.java @@ -0,0 +1,81 @@ +package com.github.catvod.crawler; + +import android.content.Context; + +import com.github.catvod.net.OkHttp; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import okhttp3.Dns; +import okhttp3.OkHttpClient; + +public abstract class Spider { + + public void init(Context context) throws Exception { + } + + public void init(Context context, String extend) throws Exception { + init(context); + } + + public String homeContent(boolean filter) throws Exception { + return ""; + } + + public String homeVideoContent() throws Exception { + return ""; + } + + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) throws Exception { + return ""; + } + + public String detailContent(List ids) throws Exception { + return ""; + } + + public String searchContent(String key, boolean quick) throws Exception { + return ""; + } + + public String searchContent(String key, boolean quick, String pg) throws Exception { + return ""; + } + + public String playerContent(String flag, String id, List vipFlags) throws Exception { + return ""; + } + + public String liveContent(String url) throws Exception { + return ""; + } + + public boolean manualVideoCheck() throws Exception { + return false; + } + + public boolean isVideoFormat(String url) throws Exception { + return false; + } + + public Object[] proxyLocal(Map params) throws Exception { + return null; + } + + public String action(String action) throws Exception { + return null; + } + + public void destroy() { + } + + public static Dns safeDns() { + return OkHttp.dns(); + } + + public static OkHttpClient client() { + return OkHttp.client(); + } +} diff --git a/catvod/src/main/java/com/github/catvod/crawler/SpiderDebug.java b/catvod/src/main/java/com/github/catvod/crawler/SpiderDebug.java new file mode 100644 index 00000000..5cfcf700 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/crawler/SpiderDebug.java @@ -0,0 +1,18 @@ +package com.github.catvod.crawler; + +import android.text.TextUtils; + +import com.orhanobut.logger.Logger; + +public class SpiderDebug { + + private static final String TAG = SpiderDebug.class.getSimpleName(); + + public static void log(Throwable th) { + if (th != null) th.printStackTrace(); + } + + public static void log(String msg) { + if (!TextUtils.isEmpty(msg)) Logger.t(TAG).d(msg); + } +} diff --git a/catvod/src/main/java/com/github/catvod/crawler/SpiderNull.java b/catvod/src/main/java/com/github/catvod/crawler/SpiderNull.java new file mode 100644 index 00000000..c9bd8c59 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/crawler/SpiderNull.java @@ -0,0 +1,4 @@ +package com.github.catvod.crawler; + +public class SpiderNull extends Spider { +} diff --git a/catvod/src/main/java/com/github/catvod/net/OkCookieJar.java b/catvod/src/main/java/com/github/catvod/net/OkCookieJar.java new file mode 100644 index 00000000..7e28dabf --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/OkCookieJar.java @@ -0,0 +1,87 @@ +package com.github.catvod.net; + +import android.text.TextUtils; +import android.webkit.CookieManager; +import android.webkit.WebView; + +import androidx.annotation.NonNull; + +import com.google.common.net.HttpHeaders; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import okhttp3.Cookie; +import okhttp3.CookieJar; +import okhttp3.HttpUrl; +import okhttp3.Request; + +public class OkCookieJar implements CookieJar { + + private CookieManager manager; + + private static class Loader { + static volatile OkCookieJar INSTANCE = new OkCookieJar(); + } + + public static OkCookieJar get() { + return Loader.INSTANCE; + } + + private OkCookieJar() { + try { + manager = CookieManager.getInstance(); + } catch (Throwable ignored) { + } + } + + public static void setAcceptThirdPartyCookies(WebView view) { + try { + get().manager.setAcceptThirdPartyCookies(view, true); + } catch (Throwable ignored) { + } + } + + public static void sync(HttpUrl url, Request request) { + if (!"127.0.0.1".equals(url.host())) sync(url.toString(), request.header(HttpHeaders.COOKIE)); + } + + public static void sync(String url, String cookie) { + try { + if (TextUtils.isEmpty(cookie)) return; + for (String split : cookie.split(";")) get().manager.setCookie(url, split); + get().manager.flush(); + } catch (Throwable ignored) { + } + } + + private void add(List items, Cookie cookie) { + if (cookie != null) items.add(cookie); + } + + @NonNull + @Override + public synchronized List loadForRequest(@NonNull HttpUrl url) { + try { + List items = new ArrayList<>(); + String cookie = manager.getCookie(url.toString()); + if (TextUtils.isEmpty(cookie)) return Collections.emptyList(); + if ("127.0.0.1".equals(url.host())) return Collections.emptyList(); + for (String split : cookie.split(";")) add(items, Cookie.parse(url, split)); + return items; + } catch (Throwable e) { + return Collections.emptyList(); + } + } + + @Override + public synchronized void saveFromResponse(@NonNull HttpUrl url, @NonNull List cookies) { + try { + if ("127.0.0.1".equals(url.host())) return; + for (Cookie cookie : cookies) manager.setCookie(url.toString(), cookie.toString()); + manager.flush(); + } catch (Throwable ignored) { + } + } +} \ No newline at end of file diff --git a/catvod/src/main/java/com/github/catvod/net/OkDns.java b/catvod/src/main/java/com/github/catvod/net/OkDns.java new file mode 100644 index 00000000..37ddad6f --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/OkDns.java @@ -0,0 +1,49 @@ +package com.github.catvod.net; + +import androidx.annotation.NonNull; + +import com.github.catvod.utils.Util; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import okhttp3.Dns; +import okhttp3.dnsoverhttps.DnsOverHttps; + +public class OkDns implements Dns { + + private final ConcurrentHashMap map; + private DnsOverHttps doh; + + public OkDns() { + this.map = new ConcurrentHashMap<>(); + } + + public void setDoh(DnsOverHttps doh) { + this.doh = doh; + } + + public void clear() { + map.clear(); + } + + public synchronized void addAll(List hosts) { + for (String host : hosts) { + if (!host.contains("=")) continue; + String[] splits = host.split("=", 2); + String oldHost = splits[0]; + String newHost = splits[1]; + map.put(oldHost, newHost); + } + } + + @NonNull + @Override + public List lookup(@NonNull String hostname) throws UnknownHostException { + for (Map.Entry entry : map.entrySet()) if (Util.containOrMatch(hostname, entry.getKey())) hostname = entry.getValue(); + return (doh != null ? doh : Dns.SYSTEM).lookup(hostname); + } +} diff --git a/catvod/src/main/java/com/github/catvod/net/OkHttp.java b/catvod/src/main/java/com/github/catvod/net/OkHttp.java new file mode 100644 index 00000000..15cbbe81 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/OkHttp.java @@ -0,0 +1,242 @@ +package com.github.catvod.net; + +import android.annotation.SuppressLint; +import android.text.TextUtils; + +import androidx.collection.ArrayMap; + +import com.github.catvod.bean.Doh; +import com.github.catvod.net.interceptor.AuthInterceptor; +import com.github.catvod.net.interceptor.RequestInterceptor; +import com.github.catvod.net.interceptor.ResponseInterceptor; + +import java.net.ProxySelector; +import java.security.SecureRandom; +import java.security.cert.X509Certificate; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; +import okhttp3.dnsoverhttps.DnsOverHttps; +import okhttp3.logging.HttpLoggingInterceptor; + +public class OkHttp { + + private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(30); + private static final ProxySelector defaultSelector; + + private ResponseInterceptor responseInterceptor; + private RequestInterceptor requestInterceptor; + private AuthInterceptor authInterceptor; + private OkProxySelector selector; + private OkHttpClient client; + private OkDns dns; + + private boolean proxy; + + static { + defaultSelector = ProxySelector.getDefault(); + } + + private static class Loader { + static volatile OkHttp INSTANCE = new OkHttp(); + } + + public static OkHttp get() { + return Loader.INSTANCE; + } + + public void clear() { + cancelAll(); + dns().clear(); + selector().clear(); + authInterceptor().clear(); + requestInterceptor().clear(); + responseInterceptor().clear(); + } + + public void setDoh(Doh doh) { + dns().setDoh(doh.getUrl().isEmpty() ? null : new DnsOverHttps.Builder().client(new OkHttpClient()).url(HttpUrl.get(doh.getUrl())).bootstrapDnsHosts(doh.getHosts()).build()); + client = null; + } + + public void setProxy(String proxy) { + ProxySelector.setDefault(TextUtils.isEmpty(proxy) ? defaultSelector : selector()); + if (!TextUtils.isEmpty(proxy)) selector().setProxy(proxy); + this.proxy = !TextUtils.isEmpty(proxy); + client = null; + } + + public static OkDns dns() { + if (get().dns != null) return get().dns; + return get().dns = new OkDns(); + } + + public static ResponseInterceptor responseInterceptor() { + if (get().responseInterceptor != null) return get().responseInterceptor; + return get().responseInterceptor = new ResponseInterceptor(); + } + + public static RequestInterceptor requestInterceptor() { + if (get().requestInterceptor != null) return get().requestInterceptor; + return get().requestInterceptor = new RequestInterceptor(); + } + + public static AuthInterceptor authInterceptor() { + if (get().authInterceptor != null) return get().authInterceptor; + return get().authInterceptor = new AuthInterceptor(); + } + + public static OkProxySelector selector() { + if (get().selector != null) return get().selector; + return get().selector = new OkProxySelector(); + } + + public static OkHttpClient client() { + if (get().client != null) return get().client; + return get().client = getBuilder().build(); + } + + public static OkHttpClient client(long timeout) { + return client().newBuilder().connectTimeout(timeout, TimeUnit.MILLISECONDS).readTimeout(timeout, TimeUnit.MILLISECONDS).writeTimeout(timeout, TimeUnit.MILLISECONDS).build(); + } + + public static OkHttpClient noRedirect(long timeout) { + return client().newBuilder().connectTimeout(timeout, TimeUnit.MILLISECONDS).readTimeout(timeout, TimeUnit.MILLISECONDS).writeTimeout(timeout, TimeUnit.MILLISECONDS).followRedirects(false).followSslRedirects(false).build(); + } + + public static OkHttpClient client(boolean redirect, long timeout) { + return redirect ? client(timeout) : noRedirect(timeout); + } + + public static String string(String url) { + if (!url.startsWith("http")) return ""; + try (Response res = newCall(url).execute()) { + return res.body().string(); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String string(String url, Map headers) { + if (!url.startsWith("http")) return ""; + try (Response res = newCall(url, Headers.of(headers)).execute()) { + return res.body().string(); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static byte[] bytes(String url) { + if (!url.startsWith("http")) return new byte[0]; + try (Response res = newCall(url).execute()) { + return res.body().bytes(); + } catch (Exception e) { + e.printStackTrace(); + return new byte[0]; + } + } + + public static Call newCall(String url) { + return client().newCall(new Request.Builder().url(url).build()); + } + + public static Call newCall(String url, String tag) { + return client().newCall(new Request.Builder().url(url).tag(tag).build()); + } + + public static Call newCall(OkHttpClient client, String url) { + return client.newCall(new Request.Builder().url(url).build()); + } + + public static Call newCall(OkHttpClient client, String url, String tag) { + return client.newCall(new Request.Builder().url(url).tag(tag).build()); + } + + public static Call newCall(String url, Headers headers) { + return client().newCall(new Request.Builder().url(url).headers(headers).build()); + } + + public static Call newCall(String url, Headers headers, ArrayMap params) { + return client().newCall(new Request.Builder().url(buildUrl(url, params)).headers(headers).build()); + } + + public static Call newCall(String url, Headers headers, RequestBody body) { + return client().newCall(new Request.Builder().url(url).headers(headers).post(body).build()); + } + + public static Call newCall(OkHttpClient client, String url, RequestBody body) { + return client.newCall(new Request.Builder().url(url).post(body).build()); + } + + public static void cancel(String tag) { + for (Call call : client().dispatcher().queuedCalls()) if (tag.equals(call.request().tag())) call.cancel(); + for (Call call : client().dispatcher().runningCalls()) if (tag.equals(call.request().tag())) call.cancel(); + } + + public static void cancelAll() { + client().dispatcher().cancelAll(); + } + + public static FormBody toBody(ArrayMap params) { + FormBody.Builder body = new FormBody.Builder(); + for (Map.Entry entry : params.entrySet()) body.add(entry.getKey(), entry.getValue()); + return body.build(); + } + + private static HttpUrl buildUrl(String url, ArrayMap params) { + HttpUrl.Builder builder = Objects.requireNonNull(HttpUrl.parse(url)).newBuilder(); + for (Map.Entry entry : params.entrySet()) builder.addQueryParameter(entry.getKey(), entry.getValue()); + return builder.build(); + } + + private static OkHttpClient.Builder getBuilder() { + OkHttpClient.Builder builder = new OkHttpClient.Builder().cookieJar(OkCookieJar.get()).addInterceptor(requestInterceptor()).addInterceptor(authInterceptor()).addNetworkInterceptor(responseInterceptor()).connectTimeout(TIMEOUT, TimeUnit.MILLISECONDS).readTimeout(TIMEOUT, TimeUnit.MILLISECONDS).writeTimeout(TIMEOUT, TimeUnit.MILLISECONDS).dns(dns()).hostnameVerifier((hostname, session) -> true).sslSocketFactory(getSSLContext().getSocketFactory(), trustAllCertificates()); + HttpLoggingInterceptor logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY); + builder.proxySelector(get().proxy ? selector() : defaultSelector); + //builder.addNetworkInterceptor(logging); + return builder; + } + + private static SSLContext getSSLContext() { + try { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, new TrustManager[]{trustAllCertificates()}, new SecureRandom()); + return context; + } catch (Throwable e) { + return null; + } + } + + @SuppressLint({"TrustAllX509TrustManager", "CustomX509TrustManager"}) + private static X509TrustManager trustAllCertificates() { + return new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }; + } +} diff --git a/catvod/src/main/java/com/github/catvod/net/OkProxySelector.java b/catvod/src/main/java/com/github/catvod/net/OkProxySelector.java new file mode 100644 index 00000000..f20ed57b --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/OkProxySelector.java @@ -0,0 +1,68 @@ +package com.github.catvod.net; + +import android.net.Uri; + +import com.github.catvod.utils.Util; + +import java.io.IOException; +import java.net.Authenticator; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.SocketAddress; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +public class OkProxySelector extends ProxySelector { + + private final List hosts; + private Proxy proxy; + + public OkProxySelector() { + this.hosts = new ArrayList<>(); + } + + public synchronized void addAll(List hosts) { + this.hosts.addAll(hosts); + } + + public void clear() { + this.hosts.clear(); + } + + public void setProxy(String proxy) { + this.proxy = getProxy(proxy); + } + + @Override + public List select(URI uri) { + if (proxy == null || hosts.isEmpty() || uri.getHost() == null || "127.0.0.1".equals(uri.getHost())) return List.of(Proxy.NO_PROXY); + for (String host : hosts) if (Util.containOrMatch(uri.getHost(), host)) return List.of(proxy); + return List.of(Proxy.NO_PROXY); + } + + @Override + public void connectFailed(URI uri, SocketAddress socketAddress, IOException e) { + } + + private Proxy getProxy(String proxy) { + Uri uri = Uri.parse(proxy); + String userInfo = uri.getUserInfo(); + if (userInfo != null && userInfo.contains(":")) setAuthenticator(userInfo); + if (uri.getScheme() == null || uri.getHost() == null || uri.getPort() <= 0) return Proxy.NO_PROXY; + if (uri.getScheme().startsWith("http")) return new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort())); + if (uri.getScheme().startsWith("socks")) return new Proxy(Proxy.Type.SOCKS, InetSocketAddress.createUnresolved(uri.getHost(), uri.getPort())); + return Proxy.NO_PROXY; + } + + private void setAuthenticator(String userInfo) { + Authenticator.setDefault(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication(userInfo.split(":")[0], userInfo.split(":")[1].toCharArray()); + } + }); + } +} diff --git a/catvod/src/main/java/com/github/catvod/net/interceptor/AuthInterceptor.java b/catvod/src/main/java/com/github/catvod/net/interceptor/AuthInterceptor.java new file mode 100644 index 00000000..bfdbd6cc --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/interceptor/AuthInterceptor.java @@ -0,0 +1,54 @@ +package com.github.catvod.net.interceptor; + +import androidx.annotation.NonNull; + +import com.github.catvod.utils.Util; +import com.google.common.net.HttpHeaders; + +import java.io.IOException; +import java.net.URI; +import java.util.concurrent.ConcurrentHashMap; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class AuthInterceptor implements Interceptor { + + private final ConcurrentHashMap userMap; + + public AuthInterceptor() { + userMap = new ConcurrentHashMap<>(); + } + + public void clear() { + userMap.clear(); + } + + @NonNull + @Override + public Response intercept(Chain chain) throws IOException { + Request request = check(chain.request()); + Response response = chain.proceed(request); + if (response.code() != 401) return response; + String host = request.url().host(); + String user = request.url().uri().getUserInfo(); + String header = response.header(HttpHeaders.WWW_AUTHENTICATE); + if (user == null && userMap.containsKey(host)) user = userMap.get(host); + if (user == null) return response; + else response.close(); + String auth = digest(header) ? Util.digest(user, header, request) : Util.basic(user); + return chain.proceed(request.newBuilder().header(HttpHeaders.AUTHORIZATION, auth).build()); + } + + private boolean digest(String header) { + return header != null && header.startsWith("Digest"); + } + + private Request check(Request request) { + URI uri = request.url().uri(); + if (uri.getUserInfo() == null) return request; + userMap.put(request.url().host(), uri.getUserInfo()); + return request.newBuilder().header(HttpHeaders.AUTHORIZATION, Util.basic(uri.getUserInfo())).build(); + } +} diff --git a/catvod/src/main/java/com/github/catvod/net/interceptor/RequestInterceptor.java b/catvod/src/main/java/com/github/catvod/net/interceptor/RequestInterceptor.java new file mode 100644 index 00000000..0f0c815c --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/interceptor/RequestInterceptor.java @@ -0,0 +1,43 @@ +package com.github.catvod.net.interceptor; + +import androidx.annotation.NonNull; + +import com.github.catvod.net.OkCookieJar; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; + +import okhttp3.HttpUrl; +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +public class RequestInterceptor implements Interceptor { + + private final ConcurrentHashMap authMap; + + public RequestInterceptor() { + authMap = new ConcurrentHashMap<>(); + } + + public void clear() { + authMap.clear(); + } + + @NonNull + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + Request request = chain.request(); + Request.Builder builder = request.newBuilder(); + HttpUrl url = request.url(); + checkAuth(url, builder); + OkCookieJar.sync(url, request); + return chain.proceed(builder.build()); + } + + private void checkAuth(HttpUrl url, Request.Builder builder) { + String auth = url.queryParameter("auth"); + if (auth != null) authMap.put(url.host(), auth); + if (authMap.containsKey(url.host()) && auth == null) builder.url(url.newBuilder().addQueryParameter("auth", authMap.get(url.host())).build()); + } +} diff --git a/catvod/src/main/java/com/github/catvod/net/interceptor/ResponseInterceptor.java b/catvod/src/main/java/com/github/catvod/net/interceptor/ResponseInterceptor.java new file mode 100644 index 00000000..e3394518 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/net/interceptor/ResponseInterceptor.java @@ -0,0 +1,92 @@ +package com.github.catvod.net.interceptor; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.github.catvod.utils.Json; +import com.google.common.net.HttpHeaders; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.BufferedSource; +import okio.Okio; + +public class ResponseInterceptor implements Interceptor { + + private final ConcurrentHashMap redirectMap; + private final ConcurrentHashMap headerMap; + + public ResponseInterceptor() { + headerMap = new ConcurrentHashMap<>(); + redirectMap = new ConcurrentHashMap<>(); + } + + public synchronized void setHeaders(List items) { + for (JsonElement item : items) { + JsonObject object = Json.safeObject(item); + headerMap.put(object.get("host").getAsString(), object.get("header").getAsJsonObject()); + } + } + + public void clear() { + headerMap.clear(); + redirectMap.clear(); + } + + @NonNull + @Override + public Response intercept(@NonNull Chain chain) throws IOException { + Request request = check(chain.request()); + Response response = chain.proceed(request); + if ("deflate".equals(response.header(HttpHeaders.CONTENT_ENCODING))) return deflate(response); + if (response.code() == 406 && redirectMap.containsKey(request.url().toString())) return redirect(request, response); + if (response.code() == 302 && response.header(HttpHeaders.LOCATION) != null) redirectMap.put(response.header(HttpHeaders.LOCATION), request.url().toString()); + return response; + } + + private Request check(Request request) { + String host = request.url().host(); + Request.Builder builder = request.newBuilder(); + if (!headerMap.containsKey(host)) return request; + for (Map.Entry entry : headerMap.get(host).entrySet()) builder.header(entry.getKey(), entry.getValue().getAsString()); + return builder.build(); + } + + private Response redirect(Request request, Response response) { + return new Response.Builder().request(request).protocol(response.protocol()).code(302).message("Found").header(HttpHeaders.LOCATION, redirectMap.get(request.url().toString())).build(); + } + + private Response deflate(Response response) { + InflaterInputStream is = new InflaterInputStream(response.body().byteStream(), new Inflater(true)); + return response.newBuilder().headers(response.headers()).body(new ResponseBody() { + @Nullable + @Override + public MediaType contentType() { + return response.body().contentType(); + } + + @Override + public long contentLength() { + return response.body().contentLength(); + } + + @NonNull + @Override + public BufferedSource source() { + return Okio.buffer(Okio.source(is)); + } + }).build(); + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Asset.java b/catvod/src/main/java/com/github/catvod/utils/Asset.java new file mode 100644 index 00000000..4db4e16e --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Asset.java @@ -0,0 +1,24 @@ +package com.github.catvod.utils; + +import com.github.catvod.Init; + +import java.io.InputStream; + +public class Asset { + + public static InputStream open(String fileName) { + try { + return Init.context().getAssets().open(fileName.replace("assets://", "")); + } catch (Exception e) { + return null; + } + } + + public static String read(String fileName) { + try { + return Path.read(open(fileName)); + } catch (Exception e) { + return ""; + } + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Github.java b/catvod/src/main/java/com/github/catvod/utils/Github.java new file mode 100644 index 00000000..18f46fe0 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Github.java @@ -0,0 +1,18 @@ +package com.github.catvod.utils; + +public class Github { + + public static final String URL = "https://github.com/Tosencen/XMBOX"; + + private static String getUrl(String path, String name) { + return URL + "/" + path + "/" + name; + } + + public static String getJson(boolean dev, String name) { + return getUrl("apk/" + (dev ? "dev" : "release"), name + ".json"); + } + + public static String getApk(boolean dev, String name) { + return getUrl("apk/" + (dev ? "dev" : "release"), name + ".apk"); + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Json.java b/catvod/src/main/java/com/github/catvod/utils/Json.java new file mode 100644 index 00000000..d2728fc4 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Json.java @@ -0,0 +1,96 @@ +package com.github.catvod.utils; + +import android.text.TextUtils; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class Json { + + public static JsonElement parse(String json) { + try { + return JsonParser.parseString(json); + } catch (Throwable e) { + return new JsonParser().parse(json); + } + } + + public static boolean isObj(String text) { + try { + if (TextUtils.isEmpty(text)) return false; + new JSONObject(text); + return true; + } catch (Exception e) { + return false; + } + } + + public static boolean isArray(String text) { + try { + if (TextUtils.isEmpty(text)) return false; + new JSONArray(text); + return true; + } catch (Exception e) { + return false; + } + } + + public static String safeString(JsonObject obj, String key) { + try { + return obj.getAsJsonPrimitive(key).getAsString().trim(); + } catch (Exception e) { + return ""; + } + } + + public static List safeListString(JsonObject obj, String key) { + List result = new ArrayList<>(); + if (!obj.has(key)) return result; + if (obj.get(key).isJsonObject()) result.add(safeString(obj, key)); + else for (JsonElement opt : obj.getAsJsonArray(key)) result.add(opt.getAsString()); + return result; + } + + public static List safeListElement(JsonObject obj, String key) { + List result = new ArrayList<>(); + if (!obj.has(key)) return result; + if (obj.get(key).isJsonObject()) result.add(obj.get(key).getAsJsonObject()); + for (JsonElement opt : obj.getAsJsonArray(key)) result.add(opt.getAsJsonObject()); + return result; + } + + public static JsonObject safeObject(JsonElement element) { + try { + if (element.isJsonPrimitive()) element = parse(element.getAsJsonPrimitive().getAsString()); + return element.getAsJsonObject(); + } catch (Exception e) { + return new JsonObject(); + } + } + + public static Map toMap(String json) { + return TextUtils.isEmpty(json) ? null : toMap(parse(json)); + } + + public static Map toMap(JsonElement element) { + Map map = new HashMap<>(); + JsonObject object = safeObject(element); + for (Map.Entry entry : object.entrySet()) map.put(entry.getKey(), safeString(object, entry.getKey())); + return map; + } + + public static JsonObject toObject(Map map) { + JsonObject object = new JsonObject(); + for (String key : map.keySet()) object.addProperty(key, map.get(key)); + return object; + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Path.java b/catvod/src/main/java/com/github/catvod/utils/Path.java new file mode 100644 index 00000000..e644ac02 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Path.java @@ -0,0 +1,227 @@ +package com.github.catvod.utils; + +import android.os.Environment; +import android.util.Log; + +import com.github.catvod.Init; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Path { + + private static final String TAG = Path.class.getSimpleName(); + + private static File mkdir(File file) { + if (!file.exists()) file.mkdirs(); + return file; + } + + public static boolean exists(String path) { + return new File(path.replace("file://", "")).exists(); + } + + public static File root() { + return Environment.getExternalStorageDirectory(); + } + + public static File cache() { + return Init.context().getCacheDir(); + } + + public static File files() { + return Init.context().getFilesDir(); + } + + public static String rootPath() { + return root().getAbsolutePath(); + } + + public static File tv() { + return mkdir(new File(root() + File.separator + "TV")); + } + + public static File so() { + return mkdir(new File(files() + File.separator + "so")); + } + + public static File js() { + return mkdir(new File(cache() + File.separator + "js")); + } + + public static File py() { + return mkdir(new File(cache() + File.separator + "py")); + } + + public static File jar() { + return mkdir(new File(cache() + File.separator + "jar")); + } + + public static File exo() { + return mkdir(new File(cache() + File.separator + "exo")); + } + + public static File epg() { + return mkdir(new File(cache() + File.separator + "epg")); + } + + public static File jpa() { + return mkdir(new File(cache() + File.separator + "jpa")); + } + + public static File thunder() { + return mkdir(new File(cache() + File.separator + "thunder")); + } + + public static File root(String name) { + return new File(root(), name); + } + + public static File root(String child, String name) { + return new File(mkdir(new File(root(), child)), name); + } + + public static File cache(String name) { + return new File(cache(), name); + } + + public static File files(String name) { + return new File(files(), name); + } + + public static File epg(String name) { + return new File(epg(), name); + } + + public static File js(String name) { + return new File(js(), name); + } + + public static File py(String name) { + return new File(py(), name); + } + + public static File jar(String name) { + return new File(jar(), Util.md5(name).concat(".jar")); + } + + public static File thunder(String name) { + return mkdir(new File(thunder(), name)); + } + + public static File local(String path) { + path = path.replace("file:/", ""); + File file = new File(root(), path); + return file.exists() ? file : new File(path); + } + + public static String read(File file) { + try { + return read(new FileInputStream(file)); + } catch (Exception e) { + return ""; + } + } + + public static String read(InputStream is) { + try { + byte[] data = new byte[is.available()]; + is.read(data); + is.close(); + return new String(data, StandardCharsets.UTF_8); + } catch (IOException e) { + e.printStackTrace(); + return ""; + } + } + + public static byte[] readToByte(File file) { + try { + FileInputStream is = new FileInputStream(file); + byte[] data = new byte[is.available()]; + is.read(data); + is.close(); + return data; + } catch (IOException e) { + e.printStackTrace(); + return new byte[0]; + } + } + + public static File write(File file, byte[] data) { + try { + FileOutputStream fos = new FileOutputStream(create(file)); + fos.write(data); + fos.flush(); + fos.close(); + return file; + } catch (Exception e) { + e.printStackTrace(); + return file; + } + } + + public static void move(File in, File out) { + copy(in, out); + clear(in); + } + + public static void copy(File in, File out) { + try { + copy(new FileInputStream(in), out); + } catch (Exception ignored) { + } + } + + public static void copy(InputStream in, File out) { + try { + int read; + byte[] buffer = new byte[8192]; + FileOutputStream fos = new FileOutputStream(create(out)); + while ((read = in.read(buffer)) != -1) fos.write(buffer, 0, read); + fos.close(); + in.close(); + } catch (Exception ignored) { + } + } + + public static void sort(File[] files) { + Arrays.sort(files, (o1, o2) -> { + if (o1.isDirectory() && o2.isFile()) return -1; + if (o1.isFile() && o2.isDirectory()) return 1; + return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); + }); + } + + public static List list(File dir) { + File[] files = dir.listFiles(); + if (files != null) sort(files); + return files == null ? new ArrayList<>() : Arrays.asList(files); + } + + public static void clear(File dir) { + if (dir == null) return; + if (dir.isDirectory()) for (File file : list(dir)) clear(file); + if (dir.delete()) Log.d(TAG, "Deleted:" + dir.getAbsolutePath()); + } + + public static File create(File file) { + try { + if (file.getParentFile() != null) mkdir(file.getParentFile()); + if (!file.canWrite()) file.setWritable(true); + if (!file.exists()) file.createNewFile(); + Shell.exec("chmod 777 " + file); + return file; + } catch (Exception e) { + e.printStackTrace(); + return file; + } + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Prefers.java b/catvod/src/main/java/com/github/catvod/utils/Prefers.java new file mode 100644 index 00000000..91d0bdcc --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Prefers.java @@ -0,0 +1,86 @@ +package com.github.catvod.utils; + +import android.content.SharedPreferences; + +import androidx.preference.PreferenceManager; + +import com.github.catvod.Init; +import com.google.gson.internal.LazilyParsedNumber; + +public class Prefers { + + public static SharedPreferences getPrefers() { + return PreferenceManager.getDefaultSharedPreferences(Init.context()); + } + + public static String getString(String key) { + return getString(key, ""); + } + + public static String getString(String key, String defaultValue) { + try { + return getPrefers().getString(key, defaultValue); + } catch (Exception e) { + return defaultValue; + } + } + + public static int getInt(String key) { + return getInt(key, 0); + } + + public static int getInt(String key, int defaultValue) { + try { + return getPrefers().getInt(key, defaultValue); + } catch (Exception e) { + return defaultValue; + } + } + + public static float getFloat(String key) { + return getFloat(key, 0f); + } + + public static float getFloat(String key, float defaultValue) { + try { + return getPrefers().getFloat(key, defaultValue); + } catch (Exception e) { + return defaultValue; + } + } + + public static boolean getBoolean(String key) { + return getBoolean(key, false); + } + + public static boolean getBoolean(String key, boolean defaultValue) { + try { + return getPrefers().getBoolean(key, defaultValue); + } catch (Exception e) { + return defaultValue; + } + } + + public static void put(String key, Object obj) { + if (obj == null) return; + if (obj instanceof String) { + getPrefers().edit().putString(key, (String) obj).apply(); + } else if (obj instanceof Boolean) { + getPrefers().edit().putBoolean(key, (Boolean) obj).apply(); + } else if (obj instanceof Float) { + getPrefers().edit().putFloat(key, (Float) obj).apply(); + } else if (obj instanceof Integer) { + getPrefers().edit().putInt(key, (Integer) obj).apply(); + } else if (obj instanceof Long) { + getPrefers().edit().putLong(key, (Long) obj).apply(); + } else if (obj instanceof LazilyParsedNumber) { + LazilyParsedNumber number = (LazilyParsedNumber) obj; + if (number.toString().contains(".")) put(key, number.floatValue()); + else put(key, number.intValue()); + } + } + + public static void remove(String key) { + getPrefers().edit().remove(key).apply(); + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Shell.java b/catvod/src/main/java/com/github/catvod/utils/Shell.java new file mode 100644 index 00000000..ee06ae98 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Shell.java @@ -0,0 +1,26 @@ +package com.github.catvod.utils; + +import com.orhanobut.logger.Logger; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +public class Shell { + + private static final String TAG = Shell.class.getSimpleName(); + + public static String exec(String command) { + try { + StringBuilder sb = new StringBuilder(); + Process p = Runtime.getRuntime().exec(command); + BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream())); + String line; + while ((line = br.readLine()) != null) sb.append(line).append("\n"); + Logger.t(TAG).d("Shell command '%s' with exit code '%s'", command, p.waitFor()); + return Util.substring(sb.toString()); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } +} \ No newline at end of file diff --git a/catvod/src/main/java/com/github/catvod/utils/Trans.java b/catvod/src/main/java/com/github/catvod/utils/Trans.java new file mode 100644 index 00000000..67de0633 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Trans.java @@ -0,0 +1,68 @@ +package com.github.catvod.utils; + +import android.text.TextUtils; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +public class Trans { + + private final Map s2t; + private final Map t2s; + private final boolean trans; + + private static class Loader { + static volatile Trans INSTANCE = new Trans(); + } + + private static Trans get() { + return Loader.INSTANCE; + } + + private Trans() { + s2t = new HashMap<>(); + t2s = new HashMap<>(); + trans = "TW".equals(Locale.getDefault().getCountry()); + if (trans) init(); + } + + private void init() { + char[] UTF8T = "萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳歷厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備復夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽黿鼂鼉鞀鼴齇齊齏齒齔齕齗齟齡齙齠齜齦齬齪齲齷龍龔龕龜誌谘範鬆嚐嘗鬨準鐘閒乾儘臟拚恆".toCharArray(); + char[] UTF8S = "万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊强状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾鼋鼌鼍鼗鼹齄齐齑齿龀龁龂龃龄龅龆龇龈龉龊龋龌龙龚龛龟志咨范松尝尝闹准钟闲干尽脏拼恒".toCharArray(); + for (int i = 0, n = UTF8T.length; i < n; ++i) { + s2t.put(UTF8S[i], UTF8T[i]); + t2s.put(UTF8T[i], UTF8S[i]); + } + } + + private String get(String text, Map map) { + if (TextUtils.isEmpty(text)) return text; + char[] chars = text.toCharArray(); + for (int i = 0; i < chars.length; ++i) { + Character found = map.get(chars[i]); + if (found != null) chars[i] = found; + } + return String.valueOf(chars); + } + + public static boolean pass() { + return !get().trans; + } + + public static String s2t(String text) { + return text == null ? "" : s2t(pass(), text); + } + + public static String t2s(String text) { + return text == null ? "" : t2s(pass(), text); + } + + public static String s2t(boolean pass, String text) { + return pass ? text : get().get(text, get().s2t); + } + + public static String t2s(boolean pass, String text) { + return pass ? text : get().get(text, get().t2s); + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/UriUtil.java b/catvod/src/main/java/com/github/catvod/utils/UriUtil.java new file mode 100644 index 00000000..4140e0ab --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/UriUtil.java @@ -0,0 +1,231 @@ +package com.github.catvod.utils; + +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +/** + * Utility methods for manipulating URIs. + */ +public final class UriUtil { + + /** + * The length of arrays returned by {@link #getUriIndices(String)}. + */ + private static final int INDEX_COUNT = 4; + + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the ':' after the scheme. Equals -1 + * if the URI is a relative reference (no scheme). The hier-part starts at (schemeColon + 1), + * including when the URI has no scheme. + */ + private static final int SCHEME_COLON = 0; + + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the path part. Equals (schemeColon + + * 1) if no authority part, (schemeColon + 3) if the authority part consists of just "//", and + * (query) if no path part. The characters starting at this index can be "//" only if the + * authority part is non-empty (in this case the double-slash means the first segment is empty). + */ + private static final int PATH = 1; + + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the query part, including the '?' + * before the query. Equals fragment if no query part, and (fragment - 1) if the query part is a + * single '?' with no data. + */ + private static final int QUERY = 2; + + /** + * An index into an array returned by {@link #getUriIndices(String)}. + * + *

The value at this position in the array is the index of the fragment part, including the '#' + * before the fragment. Equal to the length of the URI if no fragment part, and (length - 1) if + * the fragment part is a single '#' with no data. + */ + private static final int FRAGMENT = 3; + + /** + * Performs relative resolution of a {@code referenceUri} with respect to a {@code baseUri}. + * + *

The resolution is performed as specified by RFC-3986. + * + * @param baseUri The base URI. + * @param referenceUri The reference URI to resolve. + */ + public static String resolve(@Nullable String baseUri, @Nullable String referenceUri) { + StringBuilder uri = new StringBuilder(); + + // Map null onto empty string, to make the following logic simpler. + baseUri = baseUri == null ? "" : baseUri; + referenceUri = referenceUri == null ? "" : referenceUri; + + int[] refIndices = getUriIndices(referenceUri); + if (refIndices[SCHEME_COLON] != -1) { + // The reference is absolute. The target Uri is the reference. + uri.append(referenceUri); + removeDotSegments(uri, refIndices[PATH], refIndices[QUERY]); + return uri.toString(); + } + + int[] baseIndices = getUriIndices(baseUri); + if (refIndices[FRAGMENT] == 0) { + // The reference is empty or contains just the fragment part, then the target Uri is the + // concatenation of the base Uri without its fragment, and the reference. + return uri.append(baseUri, 0, baseIndices[FRAGMENT]).append(referenceUri).toString(); + } + + if (refIndices[QUERY] == 0) { + // The reference starts with the query part. The target is the base up to (but excluding) the + // query, plus the reference. + return uri.append(baseUri, 0, baseIndices[QUERY]).append(referenceUri).toString(); + } + + if (refIndices[PATH] != 0) { + // The reference has authority. The target is the base scheme plus the reference. + int baseLimit = baseIndices[SCHEME_COLON] + 1; + uri.append(baseUri, 0, baseLimit).append(referenceUri); + return removeDotSegments(uri, baseLimit + refIndices[PATH], baseLimit + refIndices[QUERY]); + } + + if (referenceUri.charAt(refIndices[PATH]) == '/') { + // The reference path is rooted. The target is the base scheme and authority (if any), plus + // the reference. + uri.append(baseUri, 0, baseIndices[PATH]).append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY]); + } + + // The target Uri is the concatenation of the base Uri up to (but excluding) the last segment, + // and the reference. This can be split into 2 cases: + if (baseIndices[SCHEME_COLON] + 2 < baseIndices[PATH] && baseIndices[PATH] == baseIndices[QUERY]) { + // Case 1: The base hier-part is just the authority, with an empty path. An additional '/' is + // needed after the authority, before appending the reference. + uri.append(baseUri, 0, baseIndices[PATH]).append('/').append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseIndices[PATH] + refIndices[QUERY] + 1); + } else { + // Case 2: Otherwise, find the last '/' in the base hier-part and append the reference after + // it. If base hier-part has no '/', it could only mean that it is completely empty or + // contains only one segment, in which case the whole hier-part is excluded and the reference + // is appended right after the base scheme colon without an added '/'. + int lastSlashIndex = baseUri.lastIndexOf('/', baseIndices[QUERY] - 1); + int baseLimit = lastSlashIndex == -1 ? baseIndices[PATH] : lastSlashIndex + 1; + uri.append(baseUri, 0, baseLimit).append(referenceUri); + return removeDotSegments(uri, baseIndices[PATH], baseLimit + refIndices[QUERY]); + } + } + + /** + * Removes dot segments from the path of a URI. + * + * @param uri A {@link StringBuilder} containing the URI. + * @param offset The index of the start of the path in {@code uri}. + * @param limit The limit (exclusive) of the path in {@code uri}. + */ + private static String removeDotSegments(StringBuilder uri, int offset, int limit) { + if (offset >= limit) { + // Nothing to do. + return uri.toString(); + } + if (uri.charAt(offset) == '/') { + // If the path starts with a /, always retain it. + offset++; + } + // The first character of the current path segment. + int segmentStart = offset; + int i = offset; + while (i <= limit) { + int nextSegmentStart; + if (i == limit) { + nextSegmentStart = i; + } else if (uri.charAt(i) == '/') { + nextSegmentStart = i + 1; + } else { + i++; + continue; + } + // We've encountered the end of a segment or the end of the path. If the final segment was + // "." or "..", remove the appropriate segments of the path. + if (i == segmentStart + 1 && uri.charAt(segmentStart) == '.') { + // Given "abc/def/./ghi", remove "./" to get "abc/def/ghi". + uri.delete(segmentStart, nextSegmentStart); + limit -= nextSegmentStart - segmentStart; + i = segmentStart; + } else if (i == segmentStart + 2 && uri.charAt(segmentStart) == '.' && uri.charAt(segmentStart + 1) == '.') { + // Given "abc/def/../ghi", remove "def/../" to get "abc/ghi". + int prevSegmentStart = uri.lastIndexOf("/", segmentStart - 2) + 1; + int removeFrom = Math.max(prevSegmentStart, offset); + uri.delete(removeFrom, nextSegmentStart); + limit -= nextSegmentStart - removeFrom; + segmentStart = prevSegmentStart; + i = prevSegmentStart; + } else { + i++; + segmentStart = i; + } + } + return uri.toString(); + } + + /** + * Calculates indices of the constituent components of a URI. + * + * @param uriString The URI as a string. + * @return The corresponding indices. + */ + private static int[] getUriIndices(String uriString) { + int[] indices = new int[INDEX_COUNT]; + if (TextUtils.isEmpty(uriString)) { + indices[SCHEME_COLON] = -1; + return indices; + } + + // Determine outer structure from right to left. + // Uri = scheme ":" hier-part [ "?" query ] [ "#" fragment ] + int length = uriString.length(); + int fragmentIndex = uriString.indexOf('#'); + if (fragmentIndex == -1) { + fragmentIndex = length; + } + int queryIndex = uriString.indexOf('?'); + if (queryIndex == -1 || queryIndex > fragmentIndex) { + // '#' before '?': '?' is within the fragment. + queryIndex = fragmentIndex; + } + // Slashes are allowed only in hier-part so any colon after the first slash is part of the + // hier-part, not the scheme colon separator. + int schemeIndexLimit = uriString.indexOf('/'); + if (schemeIndexLimit == -1 || schemeIndexLimit > queryIndex) { + schemeIndexLimit = queryIndex; + } + int schemeIndex = uriString.indexOf(':'); + if (schemeIndex > schemeIndexLimit) { + // '/' before ':' + schemeIndex = -1; + } + + // Determine hier-part structure: hier-part = "//" authority path / path + // This block can also cope with schemeIndex == -1. + boolean hasAuthority = schemeIndex + 2 < queryIndex && uriString.charAt(schemeIndex + 1) == '/' && uriString.charAt(schemeIndex + 2) == '/'; + int pathIndex; + if (hasAuthority) { + pathIndex = uriString.indexOf('/', schemeIndex + 3); // find first '/' after "://" + if (pathIndex == -1 || pathIndex > queryIndex) { + pathIndex = queryIndex; + } + } else { + pathIndex = schemeIndex + 1; + } + + indices[SCHEME_COLON] = schemeIndex; + indices[PATH] = pathIndex; + indices[QUERY] = queryIndex; + indices[FRAGMENT] = fragmentIndex; + return indices; + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/Util.java b/catvod/src/main/java/com/github/catvod/utils/Util.java new file mode 100644 index 00000000..f8f7b3e9 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/Util.java @@ -0,0 +1,190 @@ +package com.github.catvod.utils; + +import android.content.Context; +import android.net.wifi.WifiManager; +import android.text.TextUtils; +import android.util.Base64; + +import com.github.catvod.Init; + +import java.io.File; +import java.io.FileInputStream; +import java.math.BigInteger; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import okhttp3.OkHttp; +import okhttp3.Request; + +public class Util { + + public static final String OKHTTP = "okhttp/" + OkHttp.VERSION; + public static final String CHROME = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"; + public static final int URL_SAFE = Base64.DEFAULT | Base64.URL_SAFE | Base64.NO_WRAP; + + public static String base64(String s) { + return base64(s.getBytes()); + } + + public static String base64(byte[] bytes) { + return base64(bytes, Base64.DEFAULT | Base64.NO_WRAP); + } + + public static String base64(String s, int flags) { + return base64(s.getBytes(), flags); + } + + public static String base64(byte[] bytes, int flags) { + return Base64.encodeToString(bytes, flags); + } + + public static byte[] decode(String s) { + return decode(s, Base64.DEFAULT | Base64.NO_WRAP); + } + + public static byte[] decode(String s, int flags) { + return Base64.decode(s, flags); + } + + public static String basic(String userInfo) { + if (!userInfo.contains(":")) userInfo += ":"; + return "Basic " + base64(userInfo, Base64.NO_WRAP); + } + + public static byte[] hex2byte(String s) { + byte[] bytes = new byte[s.length() / 2]; + for (int i = 0; i < bytes.length; i++) bytes[i] = Integer.valueOf(s.substring(i * 2, i * 2 + 2), 16).byteValue(); + return bytes; + } + + public static boolean equals(String name, String md5) { + return md5(Path.jar(name)).equalsIgnoreCase(md5); + } + + public static String md5(String src) { + try { + if (TextUtils.isEmpty(src)) return ""; + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] bytes = digest.digest(src.getBytes()); + BigInteger no = new BigInteger(1, bytes); + StringBuilder sb = new StringBuilder(no.toString(16)); + while (sb.length() < 32) sb.insert(0, "0"); + return sb.toString().toLowerCase(); + } catch (NoSuchAlgorithmException e) { + return ""; + } + } + + public static String md5(File file) { + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + FileInputStream fis = new FileInputStream(file); + byte[] bytes = new byte[4096]; + int count; + while ((count = fis.read(bytes)) != -1) digest.update(bytes, 0, count); + fis.close(); + StringBuilder sb = new StringBuilder(); + for (byte b : digest.digest()) sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); + return sb.toString(); + } catch (Exception e) { + return ""; + } + } + + public static boolean containOrMatch(String text, String regex) { + try { + return text.contains(regex) || text.matches(regex); + } catch (Exception e) { + return false; + } + } + + public static String substring(String text) { + return substring(text, 1); + } + + public static String substring(String text, int num) { + if (text != null && text.length() > num) return text.substring(0, text.length() - num); + return text; + } + + public static String getIp() { + try { + String ip = getHostAddress("wlan"); + if (!ip.isEmpty()) return ip; + ip = getHostAddress("eth"); + if (!ip.isEmpty()) return ip; + ip = getWifiAddress(); + if (!ip.isEmpty()) return ip; + return getHostAddress(""); + } catch (Exception e) { + return ""; + } + } + + private static String getWifiAddress() { + WifiManager manager = (WifiManager) Init.context().getApplicationContext().getSystemService(Context.WIFI_SERVICE); + int ip = manager.getConnectionInfo().getIpAddress(); + return ip == 0 ? "" : String.format(Locale.getDefault(), "%d.%d.%d.%d", ip & 0xFF, (ip >> 8) & 0xFF, (ip >> 16) & 0xFF, (ip >> 24) & 0xFF); + } + + private static String getHostAddress(String keyword) throws SocketException { + for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) { + NetworkInterface nif = en.nextElement(); + if (!keyword.isEmpty() && !nif.getName().startsWith(keyword)) continue; + for (Enumeration addresses = nif.getInetAddresses(); addresses.hasMoreElements(); ) { + InetAddress addr = addresses.nextElement(); + if (!addr.isLoopbackAddress() && addr instanceof Inet4Address) { + return addr.getHostAddress(); + } + } + } + return ""; + } + + public static String digest(String userInfo, String header, Request request) { + Map params = parse(header.substring(7)); + String[] parts = userInfo.split(":", 2); + String nc = "00000001"; + String username = parts[0]; + String password = parts.length > 1 ? parts[1] : ""; + String qop = params.get("qop"); + String realm = params.get("realm"); + String nonce = params.get("nonce"); + String opaque = params.get("opaque"); + String uri = request.url().encodedPath(); + String hash1 = Util.md5(username + ":" + realm + ":" + password); + String hash2 = Util.md5(request.method() + ":" + uri); + String cnonce = UUID.randomUUID().toString().replace("-", ""); + String response = Util.md5(hash1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + hash2); + StringBuilder sb = new StringBuilder("Digest "); + sb.append("username=\"").append(username).append("\", "); + sb.append("realm=\"").append(realm).append("\", "); + sb.append("nonce=\"").append(nonce).append("\", "); + sb.append("uri=\"").append(uri).append("\", "); + sb.append("cnonce=\"").append(cnonce).append("\", "); + sb.append("nc=").append(nc).append(", "); + sb.append("qop=\"").append(qop).append("\", "); + sb.append("response=\"").append(response).append("\""); + if (opaque != null) sb.append(", opaque=\"").append(opaque).append("\""); + return sb.toString(); + } + + private static Map parse(String header) { + Map params = new HashMap<>(); + Matcher matcher = Pattern.compile("(\\w+)=\"([^\"]*)\"").matcher(header); + while (matcher.find()) params.put(matcher.group(1), matcher.group(2)); + return params; + } +} diff --git a/catvod/src/main/java/com/github/catvod/utils/ZhuToPin.java b/catvod/src/main/java/com/github/catvod/utils/ZhuToPin.java new file mode 100644 index 00000000..12a683c0 --- /dev/null +++ b/catvod/src/main/java/com/github/catvod/utils/ZhuToPin.java @@ -0,0 +1,456 @@ +package com.github.catvod.utils; + +import java.util.HashMap; +import java.util.Map; + +public class ZhuToPin { + + private static final Map map = new HashMap<>(); + + static { + map.put("ㄅㄧㄝ", "bie"); + map.put("ㄅㄧㄠ", "biao"); + map.put("ㄅㄧㄢ", "bian"); + map.put("ㄅㄧㄣ", "bin"); + map.put("ㄅㄧㄥ", "bing"); + map.put("ㄅㄨ", "bu"); + map.put("ㄅㄚ", "ba"); + map.put("ㄅㄛ", "bo"); + map.put("ㄅㄞ", "bai"); + map.put("ㄅㄟ", "bei"); + map.put("ㄅㄠ", "bao"); + map.put("ㄅㄢ", "ban"); + map.put("ㄅㄣ", "ben"); + map.put("ㄅㄤ", "bang"); + map.put("ㄅㄥ", "beng"); + map.put("ㄅㄧ", "bi"); + map.put("ㄆㄧㄝ", "pie"); + map.put("ㄆㄧㄠ", "piao"); + map.put("ㄆㄧㄢ", "pian"); + map.put("ㄆㄧㄣ", "pin"); + map.put("ㄆㄧㄥ", "ping"); + map.put("ㄆㄨ", "pu"); + map.put("ㄆㄚ", "pa"); + map.put("ㄆㄛ", "po"); + map.put("ㄆㄞ", "pai"); + map.put("ㄆㄟ", "pei"); + map.put("ㄆㄠ", "pao"); + map.put("ㄆㄡ", "pou"); + map.put("ㄆㄢ", "pan"); + map.put("ㄆㄣ", "pen"); + map.put("ㄆㄤ", "pang"); + map.put("ㄆㄥ", "peng"); + map.put("ㄆㄧ", "pi"); + map.put("ㄇㄧㄝ", "mie"); + map.put("ㄇㄧㄠ", "miao"); + map.put("ㄇㄧㄡ", "miu"); + map.put("ㄇㄧㄢ", "mian"); + map.put("ㄇㄧㄣ", "min"); + map.put("ㄇㄨ", "mu"); + map.put("ㄇㄚ", "ma"); + map.put("ㄇㄛ", "mo"); + map.put("ㄇㄜ", "me"); + map.put("ㄇㄞ", "mai"); + map.put("ㄇㄟ", "mei"); + map.put("ㄇㄠ", "mao"); + map.put("ㄇㄡ", "mou"); + map.put("ㄇㄢ", "man"); + map.put("ㄇㄣ", "men"); + map.put("ㄇㄤ", "mang"); + map.put("ㄇㄥ", "meng"); + map.put("ㄇㄧ", "mi"); + map.put("ㄈㄚ", "fa"); + map.put("ㄈㄛ", "fo"); + map.put("ㄈㄜ", "fe"); + map.put("ㄈㄟ", "fei"); + map.put("ㄈㄡ", "fou"); + map.put("ㄈㄢ", "fan"); + map.put("ㄈㄣ", "fen"); + map.put("ㄈㄤ", "fang"); + map.put("ㄈㄥ", "feng"); + map.put("ㄈㄨ", "fu"); + map.put("ㄉㄧㄝ", "die"); + map.put("ㄉㄧㄠ", "diao"); + map.put("ㄉㄧㄡ", "diu"); + map.put("ㄉㄧㄢ", "dian"); + map.put("ㄉㄧㄥ", "ding"); + map.put("ㄉㄨㄛ", "duo"); + map.put("ㄉㄨㄟ", "dui"); + map.put("ㄉㄨㄢ", "duan"); + map.put("ㄉㄨㄣ", "dun"); + map.put("ㄉㄚ", "da"); + map.put("ㄉㄜ", "de"); + map.put("ㄉㄞ", "dai"); + map.put("ㄉㄟ", "dei"); + map.put("ㄉㄠ", "dao"); + map.put("ㄉㄡ", "dou"); + map.put("ㄉㄢ", "dan"); + map.put("ㄉㄣ", "den"); + map.put("ㄉㄤ", "dang"); + map.put("ㄉㄥ", "deng"); + map.put("ㄉㄧ", "di"); + map.put("ㄉㄨ", "du"); + map.put("ㄊㄧㄝ", "tie"); + map.put("ㄊㄧㄠ", "tiao"); + map.put("ㄊㄧㄢ", "tian"); + map.put("ㄊㄧㄥ", "ting"); + map.put("ㄊㄨㄛ", "tuo"); + map.put("ㄊㄨㄟ", "tui"); + map.put("ㄊㄨㄢ", "tuan"); + map.put("ㄊㄨㄣ", "tun"); + map.put("ㄊㄚ", "ta"); + map.put("ㄊㄜ", "te"); + map.put("ㄊㄞ", "tai"); + map.put("ㄊㄠ", "tao"); + map.put("ㄊㄡ", "tou"); + map.put("ㄊㄢ", "tan"); + map.put("ㄊㄤ", "tang"); + map.put("ㄊㄥ", "teng"); + map.put("ㄊㄧ", "ti"); + map.put("ㄊㄨ", "tu"); + map.put("ㄋㄧㄝ", "nie"); + map.put("ㄋㄧㄠ", "niao"); + map.put("ㄋㄧㄡ", "niu"); + map.put("ㄋㄧㄢ", "nian"); + map.put("ㄋㄧㄣ", "nin"); + map.put("ㄋㄧㄤ", "niang"); + map.put("ㄋㄧㄥ", "ning"); + map.put("ㄋㄨㄛ", "nuo"); + map.put("ㄋㄨㄢ", "nuan"); + map.put("ㄋㄨㄣ", "nun"); + map.put("ㄋㄨㄥ", "nung"); + map.put("ㄋㄚ", "na"); + map.put("ㄋㄜ", "ne"); + map.put("ㄋㄞ", "nai"); + map.put("ㄋㄟ", "nei"); + map.put("ㄋㄠ", "nao"); + map.put("ㄋㄡ", "nou"); + map.put("ㄋㄢ", "nan"); + map.put("ㄋㄣ", "nen"); + map.put("ㄋㄤ", "nang"); + map.put("ㄋㄥ", "neng"); + map.put("ㄋㄧ", "ni"); + map.put("ㄋㄨ", "nu"); + map.put("ㄌㄧㄚ", "lia"); + map.put("ㄌㄧㄝ", "lie"); + map.put("ㄌㄧㄠ", "liao"); + map.put("ㄌㄧㄡ", "liu"); + map.put("ㄌㄧㄢ", "lian"); + map.put("ㄌㄧㄣ", "lin"); + map.put("ㄌㄧㄤ", "liang"); + map.put("ㄌㄧㄥ", "ling"); + map.put("ㄌㄨㄛ", "luo"); + map.put("ㄌㄨㄢ", "luan"); + map.put("ㄌㄨㄣ", "lun"); + map.put("ㄌㄨㄥ", "lung"); + map.put("ㄌㄩ", "lv"); + map.put("ㄌㄩㄝ", "lve"); + map.put("ㄌㄚ", "la"); + map.put("ㄌㄛ", "lo"); + map.put("ㄌㄜ", "le"); + map.put("ㄌㄞ", "lai"); + map.put("ㄌㄟ", "lei"); + map.put("ㄌㄠ", "lao"); + map.put("ㄌㄡ", "lou"); + map.put("ㄌㄢ", "lan"); + map.put("ㄌㄣ", "len"); + map.put("ㄌㄤ", "lang"); + map.put("ㄌㄥ", "leng"); + map.put("ㄌㄧ", "li"); + map.put("ㄌㄨ", "lu"); + map.put("ㄍㄨㄚ", "gua"); + map.put("ㄍㄨㄛ", "guo"); + map.put("ㄍㄨㄞ", "guai"); + map.put("ㄍㄨㄟ", "gui"); + map.put("ㄍㄨㄢ", "guan"); + map.put("ㄍㄨㄣ", "gun"); + map.put("ㄍㄨㄤ", "guang"); + map.put("ㄍㄨㄥ", "gong"); + map.put("ㄍㄚ", "ga"); + map.put("ㄍㄜ", "ge"); + map.put("ㄍㄞ", "gai"); + map.put("ㄍㄟ", "gei"); + map.put("ㄍㄠ", "gao"); + map.put("ㄍㄡ", "gou"); + map.put("ㄍㄢ", "gan"); + map.put("ㄍㄣ", "gen"); + map.put("ㄍㄤ", "gang"); + map.put("ㄍㄥ", "geng"); + map.put("ㄍㄨ", "gu"); + map.put("ㄎㄨㄚ", "kua"); + map.put("ㄎㄨㄛ", "kuo"); + map.put("ㄎㄨㄞ", "kuai"); + map.put("ㄎㄨㄟ", "kui"); + map.put("ㄎㄨㄢ", "kuan"); + map.put("ㄎㄨㄣ", "kun"); + map.put("ㄎㄨㄤ", "kuang"); + map.put("ㄎㄨㄥ", "kong"); + map.put("ㄎㄚ", "ka"); + map.put("ㄎㄜ", "ke"); + map.put("ㄎㄞ", "kai"); + map.put("ㄎㄠ", "kao"); + map.put("ㄎㄡ", "kou"); + map.put("ㄎㄢ", "kan"); + map.put("ㄎㄣ", "ken"); + map.put("ㄎㄤ", "kang"); + map.put("ㄎㄥ", "keng"); + map.put("ㄎㄨ", "ku"); + map.put("ㄏㄨㄚ", "hua"); + map.put("ㄏㄨㄛ", "huo"); + map.put("ㄏㄨㄞ", "huai"); + map.put("ㄏㄨㄟ", "huai"); + map.put("ㄏㄨㄢ", "huan"); + map.put("ㄏㄨㄣ", "hun"); + map.put("ㄏㄨㄤ", "huang"); + map.put("ㄏㄨㄥ", "hong"); + map.put("ㄏㄚ", "ha"); + map.put("ㄏㄜ", "he"); + map.put("ㄏㄞ", "hai"); + map.put("ㄏㄟ", "hei"); + map.put("ㄏㄠ", "hao"); + map.put("ㄏㄡ", "hou"); + map.put("ㄏㄢ", "han"); + map.put("ㄏㄣ", "hen"); + map.put("ㄏㄤ", "hang"); + map.put("ㄏㄥ", "heng"); + map.put("ㄏㄨ", "hu"); + map.put("ㄐㄧㄚ", "jia"); + map.put("ㄐㄧㄝ", "jie"); + map.put("ㄐㄧㄠ", "jiao"); + map.put("ㄐㄧㄡ", "jiu"); + map.put("ㄐㄧㄢ", "jian"); + map.put("ㄐㄧㄣ", "jin"); + map.put("ㄐㄧㄤ", "jiang"); + map.put("ㄐㄧㄥ", "jing"); + map.put("ㄐㄩㄝ", "jue"); + map.put("ㄐㄩㄢ", "juan"); + map.put("ㄐㄩㄣ", "jun"); + map.put("ㄐㄩㄥ", "jiong"); + map.put("ㄐㄧ", "ji"); + map.put("ㄐㄩ", "ju"); + map.put("ㄑㄧㄚ", "qia"); + map.put("ㄑㄧㄝ", "qie"); + map.put("ㄑㄧㄠ", "qiao"); + map.put("ㄑㄧㄡ", "qiu"); + map.put("ㄑㄧㄢ", "qian"); + map.put("ㄑㄧㄣ", "qin"); + map.put("ㄑㄧㄤ", "qiang"); + map.put("ㄑㄧㄥ", "qing"); + map.put("ㄑㄩㄝ", "que"); + map.put("ㄑㄩㄢ", "quan"); + map.put("ㄑㄩㄣ", "qun"); + map.put("ㄑㄩㄥ", "qun"); + map.put("ㄑㄧ", "qi"); + map.put("ㄑㄩ", "qu"); + map.put("ㄒㄧㄚ", "xia"); + map.put("ㄒㄧㄝ", "xie"); + map.put("ㄒㄧㄠ", "xiao"); + map.put("ㄒㄧㄡ", "xiu"); + map.put("ㄒㄧㄢ", "xian"); + map.put("ㄒㄧㄣ", "xin"); + map.put("ㄒㄧㄤ", "xiang"); + map.put("ㄒㄧㄥ", "xing"); + map.put("ㄒㄩㄝ", "xue"); + map.put("ㄒㄩㄢ", "xuan"); + map.put("ㄒㄩㄣ", "xun"); + map.put("ㄒㄩㄥ", "xiong"); + map.put("ㄒㄧ", "xi"); + map.put("ㄒㄩ", "xu"); + map.put("ㄓㄨㄚ", "zhua"); + map.put("ㄓㄨㄛ", "zhuo"); + map.put("ㄓㄨㄞ", "zhuai"); + map.put("ㄓㄨㄟ", "zhuo"); + map.put("ㄓㄨㄢ", "zhuan"); + map.put("ㄓㄨㄣ", "zhuan"); + map.put("ㄓㄨㄤ", "zhuan"); + map.put("ㄓㄨㄥ", "zhong"); + map.put("ㄓㄚ", "zha"); + map.put("ㄓㄜ", "zhe"); + map.put("ㄓㄞ", "zhai"); + map.put("ㄓㄟ", "zhei"); + map.put("ㄓㄠ", "zhao"); + map.put("ㄓㄡ", "zhou"); + map.put("ㄓㄢ", "zhan"); + map.put("ㄓㄣ", "zhen"); + map.put("ㄓㄤ", "zhang"); + map.put("ㄓㄥ", "zheng"); + map.put("ㄓㄨ", "zhu"); + map.put("ㄔㄨㄚ", "chua"); + map.put("ㄔㄨㄛ", "chuo"); + map.put("ㄔㄨㄞ", "chuai"); + map.put("ㄔㄨㄟ", "chui"); + map.put("ㄔㄨㄢ", "chuan"); + map.put("ㄔㄨㄣ", "chun"); + map.put("ㄔㄨㄤ", "chuang"); + map.put("ㄔㄨㄥ", "chong"); + map.put("ㄔㄚ", "cha"); + map.put("ㄔㄜ", "che"); + map.put("ㄔㄞ", "chai"); + map.put("ㄔㄠ", "chao"); + map.put("ㄔㄡ", "chou"); + map.put("ㄔㄢ", "chan"); + map.put("ㄔㄣ", "chen"); + map.put("ㄔㄤ", "chang"); + map.put("ㄔㄥ", "cheng"); + map.put("ㄔㄨ", "chu"); + map.put("ㄕㄨㄚ", "shua"); + map.put("ㄕㄨㄛ", "shuo"); + map.put("ㄕㄨㄞ", "shuai"); + map.put("ㄕㄨㄟ", "shui"); + map.put("ㄕㄨㄢ", "shuan"); + map.put("ㄕㄨㄣ", "shun"); + map.put("ㄕㄨㄤ", "shuang"); + map.put("ㄕㄚ", "sha"); + map.put("ㄕㄜ", "she"); + map.put("ㄕㄞ", "shai"); + map.put("ㄕㄟ", "shei"); + map.put("ㄕㄠ", "shao"); + map.put("ㄕㄡ", "shou"); + map.put("ㄕㄢ", "shan"); + map.put("ㄕㄣ", "shen"); + map.put("ㄕㄤ", "shang"); + map.put("ㄕㄥ", "sheng"); + map.put("ㄕㄨ", "shu"); + map.put("ㄖㄨㄛ", "ruo"); + map.put("ㄖㄨㄟ", "rui"); + map.put("ㄖㄨㄢ", "ruan"); + map.put("ㄖㄨㄣ", "run"); + map.put("ㄖㄨㄥ", "rung"); + map.put("ㄖㄜ", "re"); + map.put("ㄖㄠ", "rao"); + map.put("ㄖㄡ", "rou"); + map.put("ㄖㄢ", "ran"); + map.put("ㄖㄣ", "ren"); + map.put("ㄖㄤ", "rang"); + map.put("ㄖㄥ", "reng"); + map.put("ㄖㄨ", "ru"); + map.put("ㄗㄨㄛ", "zuo"); + map.put("ㄗㄨㄟ", "zui"); + map.put("ㄗㄨㄢ", "zuan"); + map.put("ㄗㄨㄣ", "zun"); + map.put("ㄗㄨㄥ", "zong"); + map.put("ㄗㄚ", "za"); + map.put("ㄗㄜ", "ze"); + map.put("ㄗㄞ", "zai"); + map.put("ㄗㄟ", "zei"); + map.put("ㄗㄠ", "zao"); + map.put("ㄗㄡ", "zou"); + map.put("ㄗㄢ", "zan"); + map.put("ㄗㄣ", "zen"); + map.put("ㄗㄤ", "zang"); + map.put("ㄗㄥ", "zeng"); + map.put("ㄗㄨ", "zu"); + map.put("ㄘㄨㄛ", "cuo"); + map.put("ㄘㄨㄟ", "cui"); + map.put("ㄘㄨㄢ", "cuan"); + map.put("ㄘㄨㄣ", "cun"); + map.put("ㄘㄨㄥ", "cong"); + map.put("ㄘㄚ", "ca"); + map.put("ㄘㄜ", "ce"); + map.put("ㄘㄞ", "cai"); + map.put("ㄘㄠ", "cao"); + map.put("ㄘㄡ", "cou"); + map.put("ㄘㄢ", "can"); + map.put("ㄘㄣ", "cen"); + map.put("ㄘㄤ", "cang"); + map.put("ㄘㄥ", "ceng"); + map.put("ㄘㄨ", "cu"); + map.put("ㄙㄨㄛ", "suo"); + map.put("ㄙㄨㄟ", "sui"); + map.put("ㄙㄨㄢ", "suan"); + map.put("ㄙㄨㄣ", "sun"); + map.put("ㄙㄨㄥ", "song"); + map.put("ㄙㄚ", "sa"); + map.put("ㄙㄜ", "se"); + map.put("ㄙㄞ", "sai"); + map.put("ㄙㄟ", "sei"); + map.put("ㄙㄠ", "sao"); + map.put("ㄙㄡ", "sou"); + map.put("ㄙㄢ", "san"); + map.put("ㄙㄣ", "sen"); + map.put("ㄙㄤ", "sang"); + map.put("ㄙㄥ", "seng"); + map.put("ㄙㄨ", "su"); + map.put("ㄧㄚ", "ya"); + map.put("ㄧㄛ", "yo"); + map.put("ㄧㄝ", "ye"); + map.put("ㄧㄞ", "yai"); + map.put("ㄧㄠ", "yao"); + map.put("ㄧㄡ", "you"); + map.put("ㄧㄢ", "yan"); + map.put("ㄧㄣ", "yin"); + map.put("ㄧㄤ", "yang"); + map.put("ㄧㄥ", "ying"); + map.put("ㄨㄚ", "wa"); + map.put("ㄨㄛ", "wo"); + map.put("ㄨㄞ", "wai"); + map.put("ㄨㄟ", "wei"); + map.put("ㄨㄢ", "wan"); + map.put("ㄨㄣ", "wen"); + map.put("ㄨㄤ", "wang"); + map.put("ㄨㄥ", "weng"); + map.put("ㄩㄝ", "yue"); + map.put("ㄩㄢ", "yuan"); + map.put("ㄩㄣ", "yun"); + map.put("ㄩㄥ", "yong"); + map.put("ㄅ", "b"); + map.put("ㄆ", "p"); + map.put("ㄇ", "m"); + map.put("ㄈ", "f"); + map.put("ㄉ", "d"); + map.put("ㄊ", "t"); + map.put("ㄋ", "n"); + map.put("ㄌ", "l"); + map.put("ㄍ", "g"); + map.put("ㄎ", "k"); + map.put("ㄏ", "h"); + map.put("ㄐ", "j"); + map.put("ㄑ", "q"); + map.put("ㄒ", "x"); + map.put("ㄓ", "zh"); + map.put("ㄔ", "ch"); + map.put("ㄕ", "sh"); + map.put("ㄖ", "r"); + map.put("ㄗ", "z"); + map.put("ㄘ", "c"); + map.put("ㄙ", "s"); + map.put("ㄧ", "yi"); + map.put("ㄨ", "wu"); + map.put("ㄩ", "yu"); + map.put("ㄚ", "a"); + map.put("ㄛ", "o"); + map.put("ㄜ", "e"); + map.put("ㄝ", "eh"); + map.put("ㄞ", "ai"); + map.put("ㄟ", "ei"); + map.put("ㄠ", "ao"); + map.put("ㄡ", "ou"); + map.put("ㄢ", "an"); + map.put("ㄣ", "en"); + map.put("ㄤ", "ang"); + map.put("ㄥ", "eng"); + map.put("ㄦ", "er"); + } + + public static String get(String text) { + StringBuilder sb = new StringBuilder(); + String[] splits = text.split("↩"); + if (splits.length > 1) findSplit(sb, splits); + else findSingle(sb, text); + return sb.toString(); + } + + private static void findSplit(StringBuilder sb, String[] splits) { + for (String split : splits) { + String pinyin = map.get(split); + sb.append(pinyin != null ? pinyin : split); + } + } + + private static void findSingle(StringBuilder sb, String text) { + for (char chars : text.toCharArray()) { + String pinyin = map.get(String.valueOf(chars)); + sb.append(pinyin != null ? pinyin : chars); + } + } +} diff --git a/catvod/src/main/res/values-zh-rCN/strings.xml b/catvod/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 00000000..4494779c --- /dev/null +++ b/catvod/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,17 @@ + + + + 系统 + 腾讯 + 阿里 + 360 + + + + + https://doh.pub/dns-query + https://dns.alidns.com/dns-query + https://doh.360.cn/dns-query + + + \ No newline at end of file diff --git a/catvod/src/main/res/values-zh-rTW/strings.xml b/catvod/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 00000000..d9005be2 --- /dev/null +++ b/catvod/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,17 @@ + + + + 系統 + 騰訊 + 阿里 + 360 + + + + + https://doh.pub/dns-query + https://dns.alidns.com/dns-query + https://doh.360.cn/dns-query + + + \ No newline at end of file diff --git a/catvod/src/main/res/values/strings.xml b/catvod/src/main/res/values/strings.xml new file mode 100644 index 00000000..d1261986 --- /dev/null +++ b/catvod/src/main/res/values/strings.xml @@ -0,0 +1,17 @@ + + + + System + Tencent + Alibaba + 360 + + + + + https://doh.pub/dns-query + https://dns.alidns.com/dns-query + https://doh.360.cn/dns-query + + + \ No newline at end of file diff --git a/forcetech/.gitignore b/forcetech/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/forcetech/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/forcetech/build.gradle b/forcetech/build.gradle new file mode 100644 index 00000000..91b77e7a --- /dev/null +++ b/forcetech/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.forcetech' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + ndk { abiFilters "armeabi-v7a" } + } +} + +dependencies { + implementation project(':catvod') +} \ No newline at end of file diff --git a/forcetech/src/main/AndroidManifest.xml b/forcetech/src/main/AndroidManifest.xml new file mode 100644 index 00000000..fa662814 --- /dev/null +++ b/forcetech/src/main/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/forcetech/src/main/assets/libmitv.so b/forcetech/src/main/assets/libmitv.so new file mode 100644 index 00000000..971b8d57 Binary files /dev/null and b/forcetech/src/main/assets/libmitv.so differ diff --git a/forcetech/src/main/java/com/anymediacloud/iptv/standard/ForceTV.java b/forcetech/src/main/java/com/anymediacloud/iptv/standard/ForceTV.java new file mode 100644 index 00000000..18a9fe41 --- /dev/null +++ b/forcetech/src/main/java/com/anymediacloud/iptv/standard/ForceTV.java @@ -0,0 +1,15 @@ +package com.anymediacloud.iptv.standard; + +public class ForceTV { + + public void start(int port) { + try { + start(port, 20 * 1024 * 1024); + } catch (Throwable ignored) { + } + } + + public native int start(int port, int size); + + public native int stop(); +} diff --git a/forcetech/src/main/java/com/forcetech/Util.java b/forcetech/src/main/java/com/forcetech/Util.java new file mode 100644 index 00000000..e888e793 --- /dev/null +++ b/forcetech/src/main/java/com/forcetech/Util.java @@ -0,0 +1,59 @@ +package com.forcetech; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +import com.forcetech.service.P2PService; +import com.forcetech.service.P3PService; +import com.gsoft.mitv.MainActivity; + +public class Util { + + public static int MTV = 9002; + public static int P2P = 9906; + public static int P3P = 9907; + + public static String scheme(String url) { + String scheme = Uri.parse(url).getScheme(); + if ("P2p".equals(scheme)) scheme = "mitv"; + return scheme.toLowerCase(); + } + + public static String trans(ComponentName o) { + String name = o.getClassName(); + name = name.substring(name.lastIndexOf(".") + 1); + name = name.replace("Service", ""); + name = name.replace("MainActivity", "mitv"); + return name.toLowerCase(); + } + + public static Intent intent(Context context, String scheme) { + Intent intent = new Intent(context, clz(scheme)); + intent.putExtra("scheme", scheme); + return intent; + } + + private static Class clz(String scheme) { + switch (scheme) { + case "p2p": + return P2PService.class; + case "p3p": + return P3PService.class; + default: + return MainActivity.class; + } + } + + public static int port(String scheme) { + switch (scheme) { + case "p2p": + return P2P; + case "p3p": + return P3P; + default: + return MTV; + } + } +} diff --git a/forcetech/src/main/java/com/forcetech/android/ForceTV.java b/forcetech/src/main/java/com/forcetech/android/ForceTV.java new file mode 100644 index 00000000..9b996e23 --- /dev/null +++ b/forcetech/src/main/java/com/forcetech/android/ForceTV.java @@ -0,0 +1,16 @@ +package com.forcetech.android; + +public class ForceTV { + + public void start(String lib, int port) { + try { + System.loadLibrary(lib); + start(port, 20 * 1024 * 1024); + } catch (Throwable ignored) { + } + } + + public native int start(int port, int size); + + public native int stop(); +} diff --git a/forcetech/src/main/java/com/forcetech/service/P2PService.java b/forcetech/src/main/java/com/forcetech/service/P2PService.java new file mode 100644 index 00000000..944915ad --- /dev/null +++ b/forcetech/src/main/java/com/forcetech/service/P2PService.java @@ -0,0 +1,11 @@ +package com.forcetech.service; + +import com.forcetech.Util; + +public class P2PService extends PxPService { + + @Override + public int getPort() { + return Util.P2P; + } +} diff --git a/forcetech/src/main/java/com/forcetech/service/P3PService.java b/forcetech/src/main/java/com/forcetech/service/P3PService.java new file mode 100644 index 00000000..69efb7b3 --- /dev/null +++ b/forcetech/src/main/java/com/forcetech/service/P3PService.java @@ -0,0 +1,11 @@ +package com.forcetech.service; + +import com.forcetech.Util; + +public class P3PService extends PxPService { + + @Override + public int getPort() { + return Util.P3P; + } +} diff --git a/forcetech/src/main/java/com/forcetech/service/PxPService.java b/forcetech/src/main/java/com/forcetech/service/PxPService.java new file mode 100644 index 00000000..6754e57e --- /dev/null +++ b/forcetech/src/main/java/com/forcetech/service/PxPService.java @@ -0,0 +1,35 @@ +package com.forcetech.service; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import com.forcetech.android.ForceTV; +import com.gsoft.mitv.LocalBinder; + +public abstract class PxPService extends Service { + + private ForceTV forceTV; + private IBinder binder; + + public abstract int getPort(); + + @Override + public void onCreate() { + super.onCreate(); + binder = new LocalBinder(); + } + + @Override + public IBinder onBind(Intent intent) { + forceTV = new ForceTV(); + forceTV.start(intent.getStringExtra("scheme"), getPort()); + return binder; + } + + @Override + public boolean onUnbind(Intent intent) { + if (forceTV != null) forceTV.stop(); + return false; + } +} \ No newline at end of file diff --git a/forcetech/src/main/java/com/gsoft/mitv/LocalBinder.java b/forcetech/src/main/java/com/gsoft/mitv/LocalBinder.java new file mode 100644 index 00000000..e193e4ec --- /dev/null +++ b/forcetech/src/main/java/com/gsoft/mitv/LocalBinder.java @@ -0,0 +1,6 @@ +package com.gsoft.mitv; + +import android.os.Binder; + +public class LocalBinder extends Binder { +} diff --git a/forcetech/src/main/java/com/gsoft/mitv/MainActivity.java b/forcetech/src/main/java/com/gsoft/mitv/MainActivity.java new file mode 100644 index 00000000..27022110 --- /dev/null +++ b/forcetech/src/main/java/com/gsoft/mitv/MainActivity.java @@ -0,0 +1,58 @@ +package com.gsoft.mitv; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +import com.anymediacloud.iptv.standard.ForceTV; +import com.forcetech.Util; +import com.github.catvod.utils.Asset; +import com.github.catvod.utils.Path; + +import java.io.File; + +public class MainActivity extends Service { + + private ForceTV forceTV; + private IBinder binder; + + public MainActivity() { + try { + checkLibrary(); + System.loadLibrary("mitv"); + } catch (Throwable t) { + t.printStackTrace(); + } + } + + private void checkLibrary() { + String name = "libmitv.so"; + File file = Path.cache(name); + if (!file.exists()) Path.copy(Asset.open(name), file); + } + + @Override + public void onCreate() { + super.onCreate(); + try { + binder = new LocalBinder(); + loadLibrary(1); + } catch (Throwable ignored) { + } + } + + @Override + public IBinder onBind(Intent intent) { + forceTV = new ForceTV(); + forceTV.start(Util.MTV); + return binder; + } + + @Override + public boolean onUnbind(Intent intent) { + if (forceTV != null) forceTV.stop(); + return false; + } + + private native void loadLibrary(int type); +} diff --git a/forcetech/src/main/jniLibs/armeabi-v7a/libmitv.so b/forcetech/src/main/jniLibs/armeabi-v7a/libmitv.so new file mode 100644 index 00000000..dc7c5a7f Binary files /dev/null and b/forcetech/src/main/jniLibs/armeabi-v7a/libmitv.so differ diff --git a/forcetech/src/main/jniLibs/armeabi-v7a/libp2p.so b/forcetech/src/main/jniLibs/armeabi-v7a/libp2p.so new file mode 100644 index 00000000..7758b7cb Binary files /dev/null and b/forcetech/src/main/jniLibs/armeabi-v7a/libp2p.so differ diff --git a/forcetech/src/main/jniLibs/armeabi-v7a/libp3p.so b/forcetech/src/main/jniLibs/armeabi-v7a/libp3p.so new file mode 100644 index 00000000..e7283345 Binary files /dev/null and b/forcetech/src/main/jniLibs/armeabi-v7a/libp3p.so differ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..128225a2 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,24 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +org.gradle.parallel=false +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +android.enableJetifier=true +# Enables namespacing of each library's R class so that its R class includes only the +# resources declared in the library itself and none from the library's dependencies, +# thereby reducing the size of the R class for that library +android.useFullClasspathForDexingTransform=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..e708b1c0 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..16adbb70 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Mar 29 12:54:35 CST 2023 +distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionPath=wrapper/dists +zipStorePath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew new file mode 100755 index 00000000..4f906e0c --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/hook/.gitignore b/hook/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/hook/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/hook/build.gradle b/hook/build.gradle new file mode 100644 index 00000000..b00fce4c --- /dev/null +++ b/hook/build.gradle @@ -0,0 +1,14 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.fongmi.hook' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + } +} \ No newline at end of file diff --git a/hook/src/main/AndroidManifest.xml b/hook/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/hook/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/hook/src/main/java/com/fongmi/hook/Hook.java b/hook/src/main/java/com/fongmi/hook/Hook.java new file mode 100644 index 00000000..2df9ace2 --- /dev/null +++ b/hook/src/main/java/com/fongmi/hook/Hook.java @@ -0,0 +1,504 @@ +package com.fongmi.hook; + +import android.annotation.TargetApi; +import android.content.ComponentName; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ChangedPackages; +import android.content.pm.FeatureInfo; +import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageInstaller; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.pm.SharedLibraryInfo; +import android.content.pm.Signature; +import android.content.pm.VersionedPackage; +import android.content.res.Resources; +import android.content.res.XmlResourceParser; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.os.UserHandle; + +import java.util.List; + +public class Hook extends PackageManager { + + private final String sign; + private final String pkgn; + + public Hook(String sign, String pkgn) { + this.sign = sign; + this.pkgn = pkgn; + } + + public String getPackageName() { + return pkgn; + } + + @Override + public PackageInfo getPackageInfo(String packageName, int flags) { + PackageInfo info = new PackageInfo(); + info.signatures = new Signature[]{new Signature(sign)}; + return info; + } + + @Override + @TargetApi(26) + public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags) { + return getPackageInfo(versionedPackage.getPackageName(), flags); + } + + @Override + public String[] currentToCanonicalPackageNames(String[] packageNames) { + return new String[0]; + } + + @Override + public String[] canonicalToCurrentPackageNames(String[] packageNames) { + return new String[0]; + } + + @Override + public Intent getLaunchIntentForPackage(String packageName) { + return null; + } + + @Override + public Intent getLeanbackLaunchIntentForPackage(String packageName) { + return null; + } + + @Override + public int[] getPackageGids(String packageName) { + return new int[0]; + } + + @Override + public int[] getPackageGids(String packageName, int flags) { + return new int[0]; + } + + @Override + public int getPackageUid(String packageName, int flags) { + return 0; + } + + @Override + public PermissionInfo getPermissionInfo(String permName, int flags) { + return null; + } + + @Override + public List queryPermissionsByGroup(String permissionGroup, int flags) { + return null; + } + + @Override + public PermissionGroupInfo getPermissionGroupInfo(String groupName, int flags) { + return null; + } + + @Override + public List getAllPermissionGroups(int flags) { + return null; + } + + @Override + public ApplicationInfo getApplicationInfo(String packageName, int flags) { + return null; + } + + @Override + public ActivityInfo getActivityInfo(ComponentName component, int flags) { + return null; + } + + @Override + public ActivityInfo getReceiverInfo(ComponentName component, int flags) { + return null; + } + + @Override + public ServiceInfo getServiceInfo(ComponentName component, int flags) { + return null; + } + + @Override + public ProviderInfo getProviderInfo(ComponentName component, int flags) { + return null; + } + + @Override + public List getInstalledPackages(int flags) { + return null; + } + + @Override + public List getPackagesHoldingPermissions(String[] permissions, int flags) { + return null; + } + + @Override + public int checkPermission(String permName, String packageName) { + return android.content.pm.PackageManager.PERMISSION_GRANTED; + } + + @Override + public boolean isPermissionRevokedByPolicy(String permName, String packageName) { + return false; + } + + @Override + public boolean addPermission(PermissionInfo info) { + return false; + } + + @Override + public boolean addPermissionAsync(PermissionInfo info) { + return false; + } + + @Override + public void removePermission(String permName) { + } + + @Override + public int checkSignatures(String packageName1, String packageName2) { + return android.content.pm.PackageManager.SIGNATURE_MATCH; + } + + @Override + public int checkSignatures(int uid1, int uid2) { + return android.content.pm.PackageManager.SIGNATURE_MATCH; + } + + @Override + public String[] getPackagesForUid(int uid) { + return new String[0]; + } + + @Override + public String getNameForUid(int uid) { + return null; + } + + @Override + public List getInstalledApplications(int flags) { + return null; + } + + @Override + public boolean isInstantApp() { + return false; + } + + @Override + public boolean isInstantApp(String packageName) { + return false; + } + + @Override + public int getInstantAppCookieMaxBytes() { + return 0; + } + + @Override + public byte[] getInstantAppCookie() { + return new byte[0]; + } + + @Override + public void clearInstantAppCookie() { + } + + @Override + public void updateInstantAppCookie(byte[] cookie) { + } + + @Override + public String[] getSystemSharedLibraryNames() { + return new String[0]; + } + + @Override + public List getSharedLibraries(int flags) { + return null; + } + + @Override + public ChangedPackages getChangedPackages(int sequenceNumber) { + return null; + } + + @Override + public FeatureInfo[] getSystemAvailableFeatures() { + return new FeatureInfo[0]; + } + + @Override + public boolean hasSystemFeature(String featureName) { + return false; + } + + @Override + public boolean hasSystemFeature(String featureName, int version) { + return false; + } + + @Override + public ResolveInfo resolveActivity(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentActivities(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentActivityOptions(ComponentName caller, Intent[] specifics, Intent intent, int flags) { + return null; + } + + @Override + public List queryBroadcastReceivers(Intent intent, int flags) { + return null; + } + + @Override + public ResolveInfo resolveService(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentServices(Intent intent, int flags) { + return null; + } + + @Override + public List queryIntentContentProviders(Intent intent, int flags) { + return null; + } + + @Override + public ProviderInfo resolveContentProvider(String authority, int flags) { + return null; + } + + @Override + public List queryContentProviders(String processName, int uid, int flags) { + return null; + } + + @Override + public InstrumentationInfo getInstrumentationInfo(ComponentName className, int flags) { + return null; + } + + @Override + public List queryInstrumentation(String targetPackage, int flags) { + return null; + } + + @Override + public Drawable getDrawable(String packageName, int resid, ApplicationInfo appInfo) { + return null; + } + + @Override + public Drawable getActivityIcon(ComponentName activityName) { + return null; + } + + @Override + public Drawable getActivityIcon(Intent intent) { + return null; + } + + @Override + public Drawable getActivityBanner(ComponentName activityName) { + return null; + } + + @Override + public Drawable getActivityBanner(Intent intent) { + return null; + } + + @Override + public Drawable getDefaultActivityIcon() { + return null; + } + + @Override + public Drawable getApplicationIcon(ApplicationInfo info) { + return null; + } + + @Override + public Drawable getApplicationIcon(String packageName) { + return null; + } + + @Override + public Drawable getApplicationBanner(ApplicationInfo info) { + return null; + } + + @Override + public Drawable getApplicationBanner(String packageName) { + return null; + } + + @Override + public Drawable getActivityLogo(ComponentName activityName) { + return null; + } + + @Override + public Drawable getActivityLogo(Intent intent) { + return null; + } + + @Override + public Drawable getApplicationLogo(ApplicationInfo info) { + return null; + } + + @Override + public Drawable getApplicationLogo(String packageName) { + return null; + } + + @Override + public Drawable getUserBadgedIcon(Drawable drawable, UserHandle user) { + return null; + } + + @Override + public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user, Rect badgeLocation, int badgeDensity) { + return null; + } + + @Override + public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) { + return null; + } + + @Override + public CharSequence getText(String packageName, int resid, ApplicationInfo appInfo) { + return null; + } + + @Override + public XmlResourceParser getXml(String packageName, int resid, ApplicationInfo appInfo) { + return null; + } + + @Override + public CharSequence getApplicationLabel(ApplicationInfo info) { + return null; + } + + @Override + public Resources getResourcesForActivity(ComponentName activityName) { + return null; + } + + @Override + public Resources getResourcesForApplication(ApplicationInfo app) { + return null; + } + + @Override + public Resources getResourcesForApplication(String packageName) { + return null; + } + + @Override + public void verifyPendingInstall(int id, int verificationCode) { + } + + @Override + public void extendVerificationTimeout(int id, int verificationCodeAtTimeout, long millisecondsToDelay) { + } + + @Override + public void setInstallerPackageName(String targetPackage, String installerPackageName) { + } + + @Override + public String getInstallerPackageName(String packageName) { + return null; + } + + @Override + public void addPackageToPreferred(String packageName) { + } + + @Override + public void removePackageFromPreferred(String packageName) { + } + + @Override + public List getPreferredPackages(int flags) { + return null; + } + + @Override + public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + } + + @Override + public void clearPackagePreferredActivities(String packageName) { + } + + @Override + public int getPreferredActivities(List outFilters, List outActivities, String packageName) { + return 0; + } + + @Override + public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) { + } + + @Override + public int getComponentEnabledSetting(ComponentName componentName) { + return android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } + + @Override + public void setApplicationEnabledSetting(String packageName, int newState, int flags) { + } + + @Override + public int getApplicationEnabledSetting(String packageName) { + return android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + } + + @Override + public boolean isSafeMode() { + return false; + } + + @Override + public void setApplicationCategoryHint(String packageName, int categoryHint) { + } + + @Override + public PackageInstaller getPackageInstaller() { + return null; + } + + @Override + public boolean canRequestPackageInstalls() { + return false; + } +} diff --git a/jianpian/.gitignore b/jianpian/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/jianpian/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/jianpian/build.gradle b/jianpian/build.gradle new file mode 100644 index 00000000..4de57783 --- /dev/null +++ b/jianpian/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.p2p' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + } +} + +dependencies { + implementation project(':catvod') +} \ No newline at end of file diff --git a/jianpian/src/main/AndroidManifest.xml b/jianpian/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/jianpian/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/jianpian/src/main/java/com/p2p/P2PClass.java b/jianpian/src/main/java/com/p2p/P2PClass.java new file mode 100644 index 00000000..1a397838 --- /dev/null +++ b/jianpian/src/main/java/com/p2p/P2PClass.java @@ -0,0 +1,31 @@ +package com.p2p; + +import com.github.catvod.utils.Path; + +public class P2PClass { + + public int port; + + public P2PClass() { + System.loadLibrary("jpa"); + this.port = P2Pdoxstarthttpd("TEST3E63BAAECDAA79BEAA91853490A69F08".getBytes(), Path.jpa().getAbsolutePath().getBytes()); + } + + public int P2Pdoxstarthttpd(byte[] bArr, byte[] bArr2) { + return doxstarthttpd(bArr, bArr2); + } + + public void P2Pdoxstart(byte[] bArr) { + doxstart(bArr); + } + + public void P2Pdoxpause(byte[] bArr) { + doxpause(bArr); + } + + private native int doxstarthttpd(byte[] bArr, byte[] bArr2); + + private native int doxstart(byte[] bArr); + + private native int doxpause(byte[] bArr); +} diff --git a/jianpian/src/main/jniLibs/arm64-v8a/libjpa.so b/jianpian/src/main/jniLibs/arm64-v8a/libjpa.so new file mode 100644 index 00000000..2d75eed4 Binary files /dev/null and b/jianpian/src/main/jniLibs/arm64-v8a/libjpa.so differ diff --git a/jianpian/src/main/jniLibs/armeabi-v7a/libjpa.so b/jianpian/src/main/jniLibs/armeabi-v7a/libjpa.so new file mode 100644 index 00000000..17024664 Binary files /dev/null and b/jianpian/src/main/jniLibs/armeabi-v7a/libjpa.so differ diff --git a/other/image/icon.png b/other/image/icon.png new file mode 100644 index 00000000..ebdba05f Binary files /dev/null and b/other/image/icon.png differ diff --git a/other/image/logo-1.png b/other/image/logo-1.png new file mode 100644 index 00000000..8537b34c Binary files /dev/null and b/other/image/logo-1.png differ diff --git a/other/image/logo-2.png b/other/image/logo-2.png new file mode 100644 index 00000000..3a1cbd45 Binary files /dev/null and b/other/image/logo-2.png differ diff --git a/other/sample/live/offline.json b/other/sample/live/offline.json new file mode 100644 index 00000000..ae3391e7 --- /dev/null +++ b/other/sample/live/offline.json @@ -0,0 +1,75 @@ +{ + "lives": [ + { + "name": "M3U", + "url": "file://Download/live.m3u" + }, + { + "name": "TXT", + "url": "file://Download/live.txt", + "epg": "https://epg.112114.xyz/?ch={name}&date={date}", + "logo": "https://epg.112114.xyz/logo/{name}.png" + }, + { + "name": "UA", + "url": "file://Download/live.txt", + "epg": "https://epg.112114.xyz/?ch={name}&date={date}", + "logo": "https://epg.112114.xyz/logo/{name}.png", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "referer": "https://github.com/" + }, + { + "name": "Custom", + "boot": false, + "pass": true, + "url": "file://Download/live.txt", + "epg": "https://epg.112114.xyz/?ch={name}&date={date}&serverTimeZone=Asia/Shanghai", + "logo": "https://epg.112114.xyz/logo/{name}.png", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "Referer": "https://github.com/" + }, + "catchup": { + "days": "7", + "type": "append", + "regex": "/PLTV/", + "replace": "/PLTV/,/TVOD/", + "source": "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}" + } + }, + { + "name": "JSON", + "type": 1, + "url": "file://Download/live.json" + }, + { + "name": "Spider-JS", + "type": 3, + "api": "./live.js", + "ext": "" + }, + { + "name": "Spider-Python", + "type": 3, + "api": "./live.py", + "ext": "" + } + ], + "headers": [ + { + "host": "gslbserv.itv.cmvideo.cn", + "header": { + "User-Agent": "okhttp/3.12.13" + } + } + ], + "proxy": [ + "raw.githubusercontent.com" + ], + "hosts": [ + "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" + ], + "ads": [ + "static-mozai.4gtv.tv" + ] +} \ No newline at end of file diff --git a/other/sample/live/online.json b/other/sample/live/online.json new file mode 100644 index 00000000..3825d5fb --- /dev/null +++ b/other/sample/live/online.json @@ -0,0 +1,75 @@ +{ + "lives": [ + { + "name": "M3U", + "url": "https://github.com/live.m3u" + }, + { + "name": "TXT", + "url": "https://github.com/live.txt", + "epg": "https://epg.112114.xyz/?ch={name}&date={date}", + "logo": "https://epg.112114.xyz/logo/{name}.png" + }, + { + "name": "UA", + "url": "https://github.com/live.txt", + "epg": "https://epg.112114.xyz/?ch={name}&date={date}", + "logo": "https://epg.112114.xyz/logo/{name}.png", + "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "referer": "https://github.com/" + }, + { + "name": "Custom", + "boot": false, + "pass": true, + "url": "https://github.com/live.txt", + "epg": "https://epg.112114.xyz/?ch={name}&date={date}&serverTimeZone=Asia/Shanghai", + "logo": "https://epg.112114.xyz/logo/{name}.png", + "header": { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36", + "Referer": "https://github.com/" + }, + "catchup": { + "days": "7", + "type": "append", + "regex": "/PLTV/", + "replace": "/PLTV/,/TVOD/", + "source": "?playseek=${(b)yyyyMMddHHmmss}-${(e)yyyyMMddHHmmss}" + } + }, + { + "name": "JSON", + "type": 1, + "url": "https://github.com/live.json" + }, + { + "name": "Spider-JS", + "type": 3, + "api": "https://github.com/live.js", + "ext": "" + }, + { + "name": "Spider-Python", + "type": 3, + "api": "https://github.com/live.py", + "ext": "" + } + ], + "headers": [ + { + "host": "gslbserv.itv.cmvideo.cn", + "header": { + "User-Agent": "okhttp/3.12.13" + } + } + ], + "proxy": [ + "raw.githubusercontent.com" + ], + "hosts": [ + "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" + ], + "ads": [ + "static-mozai.4gtv.tv" + ] +} \ No newline at end of file diff --git a/other/sample/vod/offline.json b/other/sample/vod/offline.json new file mode 100644 index 00000000..b9f0fe79 --- /dev/null +++ b/other/sample/vod/offline.json @@ -0,0 +1,70 @@ +{ + "spider": "file://Download/custom_spider.jar", + "sites": [ + { + "key": "one", + "name": "One", + "type": 3, + "api": "csp_Csp", + "searchable": 1, + "changeable": 1, + "ext": "file://Download/one.json" + }, + { + "key": "two", + "name": "Two", + "type": 3, + "api": "csp_Csp", + "searchable": 1, + "changeable": 1, + "ext": "file://Download/two.json" + }, + { + "key": "extend", + "name": "Extend", + "type": 3, + "api": "csp_Csp", + "searchable": 1, + "changeable": 1, + "ext": "file://Download/extend.json", + "jar": "file://Download/extend.jar" + } + ], + "parses": [ + { + "name": "官方", + "type": 1, + "url": "https://google.com/api/?url=" + } + ], + "doh": [ + { + "name": "Google", + "url": "https://dns.google/dns-query", + "ips": [ + "8.8.4.4", + "8.8.8.8" + ] + } + ], + "headers": [ + { + "host": "gslbserv.itv.cmvideo.cn", + "header": { + "User-Agent": "okhttp/3.12.13" + } + } + ], + "proxy": [ + "raw.githubusercontent.com" + ], + "hosts": [ + "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" + ], + "flags": [ + "qq" + ], + "ads": [ + "static-mozai.4gtv.tv" + ] +} \ No newline at end of file diff --git a/other/sample/vod/online.json b/other/sample/vod/online.json new file mode 100644 index 00000000..26abb632 --- /dev/null +++ b/other/sample/vod/online.json @@ -0,0 +1,70 @@ +{ + "spider": "https://github.com/custom_spider.jar", + "sites": [ + { + "key": "one", + "name": "One", + "type": 3, + "api": "csp_Csp", + "searchable": 1, + "changeable": 1, + "ext": "https://github.com/one.json" + }, + { + "key": "two", + "name": "Two", + "type": 3, + "api": "csp_Csp", + "searchable": 1, + "changeable": 1, + "ext": "https://github.com/two.json" + }, + { + "key": "extend", + "name": "Extend", + "type": 3, + "api": "csp_Csp", + "searchable": 1, + "changeable": 1, + "ext": "https://github.com/extend.json", + "jar": "https://github.com/extend.jar" + } + ], + "parses": [ + { + "name": "官方", + "type": 1, + "url": "https://google.com/api/?url=" + } + ], + "doh": [ + { + "name": "Google", + "url": "https://dns.google/dns-query", + "ips": [ + "8.8.4.4", + "8.8.8.8" + ] + } + ], + "headers": [ + { + "host": "gslbserv.itv.cmvideo.cn", + "header": { + "User-Agent": "okhttp/3.12.13" + } + } + ], + "proxy": [ + "raw.githubusercontent.com" + ], + "hosts": [ + "cache.ott.ystenlive.itv.cmvideo.cn=base-v4-free-mghy.e.cdn.chinamobile.com" + ], + "flags": [ + "qq" + ], + "ads": [ + "static-mozai.4gtv.tv" + ] +} \ No newline at end of file diff --git a/other/tools/bfg.jar b/other/tools/bfg.jar new file mode 100644 index 00000000..688fe713 Binary files /dev/null and b/other/tools/bfg.jar differ diff --git a/other/tools/cleaner.bat b/other/tools/cleaner.bat new file mode 100644 index 00000000..2f5fdece --- /dev/null +++ b/other/tools/cleaner.bat @@ -0,0 +1,7 @@ +git clone --mirror https://github.com/FongMi/Release.git +java -jar bfg.jar --delete-files *.apk Release.git +java -jar bfg.jar --delete-files *.json Release.git +cd Release.git +git reflog expire --expire=now --all && git gc --prune=now --aggressive +git push +git gc \ No newline at end of file diff --git a/quickjs/.gitignore b/quickjs/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/quickjs/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/quickjs/build.gradle b/quickjs/build.gradle new file mode 100644 index 00000000..8dd0cd7e --- /dev/null +++ b/quickjs/build.gradle @@ -0,0 +1,30 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.fongmi.android.tv.quickjs' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + } + + lint { + disable 'UnsafeOptInUsageError' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } +} + +dependencies { + implementation project(':catvod') + implementation 'wang.harlon.quickjs:wrapper-java:3.2.0' + implementation 'wang.harlon.quickjs:wrapper-android:3.2.0' + implementation 'net.sourceforge.streamsupport:android-retrofuture:1.7.4' +} \ No newline at end of file diff --git a/quickjs/src/main/AndroidManifest.xml b/quickjs/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/quickjs/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/quickjs/src/main/assets/js/lib/cat.js b/quickjs/src/main/assets/js/lib/cat.js new file mode 100644 index 00000000..63f5cf74 --- /dev/null +++ b/quickjs/src/main/assets/js/lib/cat.js @@ -0,0 +1 @@ +var e,pe,t=function(){return(t=Object.assign||function(e){for(var t,n=1,r=arguments.length;n{var t;return{name:e,value:this.attribs[e],namespace:null==(t=this["x-attribsNamespace"])?void 0:t[e],prefix:null==(t=this["x-attribsPrefix"])?void 0:t[e]}})}}function S(t){return t.type===e.Tag||t.type===e.Script||t.type===e.Style}function C(t){return t.type===e.CDATA}function N(t){return t.type===e.Text}function b(t){return t.type===e.Comment}function I(t){return t.type===e.Directive}function O(t){return t.type===e.Root}function k(e){return Object.prototype.hasOwnProperty.call(e,"children")}function L(e,t=!1){let n;if(N(e))n=new _(e.data);else if(b(e))n=new E(e.data);else if(S(e)){const r=t?D(e.children):[],i=new y(e.name,{...e.attribs},r);r.forEach(e=>e.parent=i),null!=e.namespace&&(i.namespace=e.namespace),e["x-attribsNamespace"]&&(i["x-attribsNamespace"]={...e["x-attribsNamespace"]}),e["x-attribsPrefix"]&&(i["x-attribsPrefix"]={...e["x-attribsPrefix"]}),n=i}else if(C(e)){const r=t?D(e.children):[],i=new g(r);r.forEach(e=>e.parent=i),n=i}else if(O(e)){const r=t?D(e.children):[],i=new v(r);r.forEach(e=>e.parent=i),e["x-mode"]&&(i["x-mode"]=e["x-mode"]),n=i}else{if(!I(e))throw new Error("Not implemented yet: "+e.type);{const t=new T(e.name,e.data);null!=e["x-name"]&&(t["x-name"]=e["x-name"],t["x-publicId"]=e["x-publicId"],t["x-systemId"]=e["x-systemId"]),n=t}}return n.startIndex=e.startIndex,n.endIndex=e.endIndex,null!=e.sourceCodeLocation&&(n.sourceCodeLocation=e.sourceCodeLocation),n}function D(e){var t=e.map(e=>L(e,!0));for(let e=1;e$\x80-\uFFFF]/g,M=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]),P=null!=String.prototype.codePointAt?(e,t)=>e.codePointAt(t):(e,t)=>55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t);function B(e){let t,n="",r=0;for(;null!==(t=x.exec(e));){var i=t.index,s=e.charCodeAt(i),a=M.get(s);r=void 0!==a?(n+=e.substring(r,i)+a,i+1):(n+=`${e.substring(r,i)}&#x${P(e,i).toString(16)};`,x.lastIndex+=Number(55296==(64512&s)))}return n+e.substr(r)}function F(e,t){return function(n){let r,i=0,s="";for(;r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}const U=F(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),H=F(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]])),G=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map(e=>[e.toLowerCase(),e])),j=new Map(["definitionURL","attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map(e=>[e.toLowerCase(),e])),q=new Set(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]);function Y(e){return e.replace(/"/g,""")}const K=new Set(["area","base","basefont","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr"]);function W(e,t={}){var n="length"in e?e:[e];let r="";for(let e=0;e`;case c:return``;case f:return``;case u:case l:case h:return function(e,t){!(t="foreign"===t.xmlMode&&(e.name=null!=(n=G.get(e.name))?n:e.name,e.parent)&&$.has(e.parent.name)?{...t,xmlMode:!1}:t).xmlMode&&Q.has(e.name)&&(t={...t,xmlMode:"foreign"});let r="<"+e.name;var n=function(e,t){var n;if(e){const r=!1===(null!=(n=t.encodeEntities)?n:t.decodeEntities)?Y:t.xmlMode||"utf8"!==t.encodeEntities?B:U;return Object.keys(e).map(n=>{var s,i=null!=(i=e[n])?i:"";return"foreign"===t.xmlMode&&(n=null!=(s=j.get(n))?s:n),t.emptyAttrs||t.xmlMode||""!==i?`${n}="${r(i)}"`:n}).join(" ")}}(e.attribs,t);return n&&(r+=" "+n),0===e.children.length&&(t.xmlMode?!1!==t.selfClosingTags:t.selfClosingTags&&K.has(e.name))?(t.xmlMode||(r+=" "),r+="/>"):(r+=">",0`)),r}(e,t);case a:return function(e,t){var n,r=e.data||"";return!1===(null!=(n=t.encodeEntities)?n:t.decodeEntities)||!t.xmlMode&&e.parent&&q.has(e.parent.name)?r:(t.xmlMode||"utf8"!==t.encodeEntities?B:H)(r)}(e,t)}}const $=new Set(["mi","mo","mn","ms","mtext","annotation-xml","foreignObject","desc","title"]),Q=new Set(["svg","math"]);function z(e,t){return W(e,t)}function X(e){return Array.isArray(e)?e.map(X).join(""):k(e)&&!b(e)?X(e.children):N(e)?e.data:""}function Z(t){return Array.isArray(t)?t.map(Z).join(""):k(t)&&(t.type===e.Tag||C(t))?Z(t.children):N(t)?t.data:""}function J(e){return k(e)?e.children:[]}function ee(e){return e.parent||null}function te(e){var t=ee(e);if(null!=t)return J(t);var n=[e];let{prev:r,next:i}=e;for(;null!=r;)n.unshift(r),{prev:r}=r;for(;null!=i;)n.push(i),{next:i}=i;return n}function ne(e){let t=e.next;for(;null!==t&&!S(t);)({next:t}=t);return t}function re(e){let t=e.prev;for(;null!==t&&!S(t);)({prev:t}=t);return t}function ie(e){var t,n;e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e.parent&&0<=(n=(t=e.parent.children).lastIndexOf(e))&&t.splice(n,1),e.next=null,e.prev=null,e.parent=null}function se(e,t,n=!0,r=1/0){return ae(e,Array.isArray(t)?t:[t],n,r)}function ae(e,t,n,r){for(var i=[],s=[t],a=[0];;)if(a[0]>=s[0].length){if(1===a.length)return i;s.shift(),a.shift()}else{const t=s[0][a[0]++];if(e(t)&&(i.push(t),--r<=0))return i;n&&k(t)&&0"function"==typeof e?t=>S(t)&&e(t.name):"*"===e?S:t=>S(t)&&t.name===e,tag_type:e=>"function"==typeof e?t=>e(t.type):t=>t.type===e,tag_contains:e=>"function"==typeof e?t=>N(t)&&e(t.data):t=>N(t)&&t.data===e};function ue(e,t){return"function"==typeof t?n=>S(n)&&t(n.attribs[e]):n=>S(n)&&n.attribs[e]===t}function le(e,t){return n=>e(n)||t(n)}function he(e){var t=Object.keys(e).map(t=>{var n=e[t];return Object.prototype.hasOwnProperty.call(ce,t)?ce[t](n):ue(t,n)});return 0===t.length?null:t.reduce(le)}function fe(e,t,n=!0,r=1/0){return se(ce.tag_name(e),t,n,r)}function de(e,t){var n=[],r=[];if(e===t)return 0;let i=k(e)?e:e.parent;for(;i;)n.unshift(i),i=i.parent;for(i=k(t)?t:t.parent;i;)r.unshift(i),i=i.parent;var o,c,u,l,s=Math.min(n.length,r.length);let a=0;for(;ac.indexOf(l)?o===t?pe.FOLLOWING|pe.CONTAINED_BY:pe.FOLLOWING:o===e?pe.PRECEDING|pe.CONTAINS:pe.PRECEDING)}function me(e){return(e=e.filter((e,t,n)=>!n.includes(e,t+1))).sort((e,t)=>(e=de(e,t))&pe.PRECEDING?-1:e&pe.FOLLOWING?1:0),e}!function(e){e[e.DISCONNECTED=1]="DISCONNECTED",e[e.PRECEDING=2]="PRECEDING",e[e.FOLLOWING=4]="FOLLOWING",e[e.CONTAINS=8]="CONTAINS",e[e.CONTAINED_BY=16]="CONTAINED_BY"}(pe=pe||{});const _e=["url","type","lang"],Ee=["fileSize","bitrate","framerate","samplingrate","channels","duration","height","width"];function Te(e){return fe("media:content",e).map(e=>{var t=e.attribs,n={medium:t.medium,isDefault:!!t.isDefault};for(const e of _e)t[e]&&(n[e]=t[e]);for(const e of Ee)t[e]&&(n[e]=parseInt(t[e],10));return t.expression&&(n.expression=t.expression),n})}function Ae(e,t){return fe(e,t,!0,1)[0]}function ge(e,t,n=!1){return X(fe(e,t,n,1)).trim()}function ve(e,t,n,r,i=!1){(n=ge(n,r,i))&&(e[t]=n)}function ye(e){return"rss"===e||"feed"===e||"rdf:RDF"===e}var Se=Object.freeze({__proto__:null,isTag:S,isCDATA:C,isText:N,isComment:b,isDocument:O,hasChildren:k,getOuterHTML:z,getInnerHTML:function(e,t){return k(e)?e.children.map(e=>z(e,t)).join(""):""},getText:function e(t){return Array.isArray(t)?t.map(e).join(""):S(t)?"br"===t.name?"\n":e(t.children):C(t)?e(t.children):N(t)?t.data:""},textContent:X,innerText:Z,getChildren:J,getParent:ee,getSiblings:te,getAttributeValue:function(e,t){return null==(e=e.attribs)?void 0:e[t]},hasAttrib:function(e,t){return null!=e.attribs&&Object.prototype.hasOwnProperty.call(e.attribs,t)&&null!=e.attribs[t]},getName:function(e){return e.name},nextElementSibling:ne,prevElementSibling:re,removeElement:ie,replaceElement:function(e,t){const n=t.prev=e.prev;n&&(n.next=t);var r=t.next=e.next;if(r&&(r.prev=t),r=t.parent=e.parent){const n=r.children;n[n.lastIndexOf(e)]=t,e.parent=null}},appendChild:function(e,t){ie(t),t.next=null,1<(t.parent=e).children.push(t)?((e=e.children[e.children.length-2]).next=t).prev=e:t.prev=null},append:function(e,t){ie(t);var n=e.parent,r=e.next;if(t.next=r,((t.prev=e).next=t).parent=n,r){if(r.prev=t,n){const e=n.children;e.splice(e.lastIndexOf(r),0,t)}}else n&&n.children.push(t)},prependChild:function(e,t){ie(t),t.parent=e,t.prev=null,1!==e.children.unshift(t)?((e=e.children[1]).prev=t).next=e:t.next=null},prepend:function(e,t){ie(t);var r,n=e.parent;n&&(r=n.children).splice(r.indexOf(e),0,t),e.prev&&(e.prev.next=t),t.parent=n,t.prev=e.prev,(t.next=e).prev=t},filter:se,find:ae,findOneChild:function(e,t){return t.find(e)},findOne:oe,existsOne:function e(t,n){return n.some(n=>S(n)&&(t(n)||e(t,n.children)))},findAll:function(e,t){for(var n=[],r=[t],i=[0];;)if(i[0]>=r[0].length){if(1===r.length)return n;r.shift(),i.shift()}else{const t=r[0][i[0]++];S(t)&&(e(t)&&n.push(t),0{var r={media:Te(e=e.children)},t=(ve(r,"id","id",e),ve(r,"title","title",e),null==(t=Ae("link",e))?void 0:t.attribs.href);return t&&(r.link=t),(t=ge("summary",e)||ge("content",e))&&(r.description=t),(t=ge("updated",e))&&(r.pubDate=new Date(t)),r})},t=(ve(r,"id","id",e),ve(r,"title","title",e),null==(t=Ae("link",e))?void 0:t.attribs.href);return t&&(r.link=t),ve(r,"description","subtitle",e),(t=ge("updated",e))&&(r.updated=new Date(t)),ve(r,"author","email",e,!0),r}:function(e){var t=null!=(t=null==(t=Ae("channel",e.children))?void 0:t.children)?t:[],e={type:e.name.substr(0,3),id:"",items:fe("item",e.children).map(e=>{var n={media:Te(e=e.children)};return ve(n,"id","guid",e),ve(n,"title","title",e),ve(n,"link","link",e),ve(n,"description","description",e),(e=ge("pubDate",e)||ge("dc:date",e))&&(n.pubDate=new Date(e)),n})},s=(ve(e,"title","title",t),ve(e,"link","link",t),ve(e,"description","description",t),ge("lastBuildDate",t));return s&&(e.updated=new Date(s)),ve(e,"author","managingEditor",t,!0),e})(e):null}}),Ce=function(){return(Ce=Object.assign||function(e){for(var t,n=1,r=arguments.length;ne.length-3)&&((n=e.charCodeAt(t+1))>=xe.LowerA&&n<=xe.LowerZ||n>=xe.UpperA&&n<=xe.UpperZ||n===xe.Exclamation)&&e.includes(">",t+2)}!function(e){e[e.LowerA=97]="LowerA",e[e.LowerZ=122]="LowerZ",e[e.UpperA=65]="UpperA",e[e.UpperZ=90]="UpperZ",e[e.Exclamation=33]="Exclamation"}(xe=xe||{});var He=Object.prototype.hasOwnProperty,Ge=/\s+/,je="data-",qe={null:null,true:!0,false:!1},Ye=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,Ke=/^{[^]*}$|^\[[^]*]$/;function We(e,t,n){if(e&&S(e))return null==e.attribs&&(e.attribs={}),t?He.call(e.attribs,t)?!n&&Ye.test(t)?t:e.attribs[t]:"option"===e.name&&"value"===t?Oe(e.children):"input"!==e.name||"radio"!==e.attribs.type&&"checkbox"!==e.attribs.type||"value"!==t?void 0:"on":e.attribs}function Ve(e,t,n){null===n?Ze(e,t):e.attribs[t]="".concat(n)}function $e(e,t,n){return t in e?e[t]:!n&&Ye.test(t)?void 0!==We(e,t,!1):We(e,t,n)}function Qe(e,t,n,r){t in e?e[t]=n:Ve(e,t,!r&&Ye.test(t)?n?"":null:"".concat(n))}function ze(e,t,n){null==e.data&&(e.data={}),"object"==typeof t?Object.assign(e.data,t):"string"==typeof t&&void 0!==n&&(e.data[t]=n)}function Xe(e,t){for(var n,i,r=null==t?(n=Object.keys(e.attribs).filter(function(e){return e.startsWith(je)})).map(function(e){return e.slice(je.length).replace(/[_.-](\w|$)/g,function(e,t){return t.toUpperCase()})}):(n=[je+t.replace(/[A-Z]/g,"-$&").toLowerCase()],[t]),a=0;a").parent().html();case"innerHTML":return this.html();default:return $e(i,e,this.options.xmlMode)}}if("object"==typeof e||void 0!==t){if("function"!=typeof t)return Be(this,function(n){S(n)&&("object"==typeof e?Object.keys(e).forEach(function(t){var i=e[t];Qe(n,t,i,r.options.xmlMode)}):Qe(n,e,t,r.options.xmlMode))});if("object"==typeof e)throw new Error("Bad combination of arguments.");return Be(this,function(n,i){S(n)&&Qe(n,e,t.call(n,i,$e(n,e,r.options.xmlMode)),r.options.xmlMode)})}},data:function(e,t){var r=this[0];if(r&&S(r))return null==r.data&&(r.data={}),e?"object"==typeof e||void 0!==t?(Be(this,function(n){S(n)&&("object"==typeof e?ze(n,e):ze(n,e,t))}),this):He.call(r.data,e)?r.data[e]:Xe(r,e):Xe(r)},val:function(e){var t=0===arguments.length,n=this[0];if(!n||!S(n))return t?void 0:this;switch(n.name){case"textarea":return this.text(e);case"select":var r=this.find("option:selected");if(t)return this.attr("multiple")?r.toArray().map(function(e){return Oe(e.children)}):r.attr("value");if(null!=this.attr("multiple")||"object"!=typeof e){this.find("option").removeAttr("selected");for(var i="object"!=typeof e?[e]:e,s=0;s>10|55296,1023&r|56320)}function lt(e){return e.replace(it,ut)}function ht(e){return 39===e||34===e}function ft(e){return 32===e||9===e||10===e||12===e||13===e}function pt(e){var t=[],n=dt(t,""+e,0);if(n>=1)):e.type===et.Pseudo&&(e.data?"has"===e.name||"contains"===e.name?r=0:Array.isArray(e.data)?(r=Math.min(...e.data.map(e=>Math.min(...e.map(vt)))))<0&&(r=0):r=2:r=3),r}const yt=/[-[\]{}()*+?.,\\^$|#\s]/g;function St(e){return e.replace(yt,"\\$&")}const Ct=new Set(["accept","accept-charset","align","alink","axis","bgcolor","charset","checked","clear","codetype","color","compact","declare","defer","dir","direction","disabled","enctype","face","frame","hreflang","http-equiv","lang","language","link","media","method","multiple","nohref","noresize","noshade","nowrap","readonly","rel","rev","rules","scope","scrolling","selected","shape","target","text","type","valign","valuetype","vlink"]);function Nt(e,t){return"boolean"==typeof e.ignoreCase?e.ignoreCase:"quirks"===e.ignoreCase?!!t.quirksMode:!t.xmlMode&&Ct.has(e.name)}const bt={equals(e,t,n){const r=n.adapter,i=t.name;let s=t.value;return Nt(t,n)?(s=s.toLowerCase(),t=>{var n=r.getAttributeValue(t,i);return null!=n&&n.length===s.length&&n.toLowerCase()===s&&e(t)}):t=>r.getAttributeValue(t,i)===s&&e(t)},hyphen(e,t,n){const r=n.adapter,i=t.name;let s=t.value;const a=s.length;return Nt(t,n)?(s=s.toLowerCase(),function(t){var n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a).toLowerCase()===s&&e(t)}):function(t){var n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a)===s&&e(t)}},element(e,t,n){const r=n.adapter,{name:i,value:s}=t;if(/\s/.test(s))return mt.falseFunc;const a=new RegExp(`(?:^|\\s)${St(s)}(?:$|\\s)`,Nt(t,n)?"i":"");return function(t){var n=r.getAttributeValue(t,i);return null!=n&&n.length>=s.length&&a.test(n)&&e(t)}},exists:(e,{name:t},{adapter:n})=>r=>n.hasAttrib(r,t)&&e(r),start(e,t,n){const r=n.adapter,i=t.name;let s=t.value;const a=s.length;return 0===a?mt.falseFunc:Nt(t,n)?(s=s.toLowerCase(),t=>{var n=r.getAttributeValue(t,i);return null!=n&&n.length>=a&&n.substr(0,a).toLowerCase()===s&&e(t)}):t=>{var n;return!(null==(n=r.getAttributeValue(t,i))||!n.startsWith(s))&&e(t)}},end(e,t,n){const r=n.adapter,i=t.name;let s=t.value;const a=-s.length;return 0==a?mt.falseFunc:Nt(t,n)?(s=s.toLowerCase(),t=>{var n;return(null==(n=r.getAttributeValue(t,i))?void 0:n.substr(a).toLowerCase())===s&&e(t)}):t=>{var n;return!(null==(n=r.getAttributeValue(t,i))||!n.endsWith(s))&&e(t)}},any(e,t,n){const r=n.adapter,{name:i,value:s}=t;if(""===s)return mt.falseFunc;if(Nt(t,n)){const t=new RegExp(St(s),"i");return function(n){var a=r.getAttributeValue(n,i);return null!=a&&a.length>=s.length&&t.test(a)&&e(n)}}return t=>{var n;return!(null==(n=r.getAttributeValue(t,i))||!n.includes(s))&&e(t)}},not(e,t,n){const r=n.adapter,i=t.name;let s=t.value;return""===s?t=>!!r.getAttributeValue(t,i)&&e(t):Nt(t,n)?(s=s.toLowerCase(),t=>{var n=r.getAttributeValue(t,i);return(null==n||n.length!==s.length||n.toLowerCase()!==s)&&e(t)}):t=>r.getAttributeValue(t,i)!==s&&e(t)}},It=new Set([9,10,12,13,32]),Ot="0".charCodeAt(0),kt="9".charCodeAt(0);function Lt(e){return function(e){const t=e[0],n=e[1]-1;if(n<0&&t<=0)return mt.falseFunc;if(-1===t)return e=>e<=n;if(0===t)return e=>e===n;if(1===t)return n<0?mt.trueFunc:e=>e>=n;const r=Math.abs(t),i=(n%r+r)%r;return 1e>=n&&e%r==i:e=>e<=n&&e%r==i}(function(e){if("even"===(e=e.trim().toLowerCase()))return[2,0];if("odd"===e)return[2,1];let t=0,n=0,r=s(),i=a();if(t=Ot&&e.charCodeAt(t)<=kt;)r=10*r+(e.charCodeAt(t)-Ot),t++;return t===n?null:r}function o(){for(;t{var r=t.getParent(n);return null!=r&&t.isTag(r)&&e(n)}}const Rt={contains:(e,t,{adapter:n})=>function(r){return e(r)&&n.getText(r).includes(t)},icontains(e,t,{adapter:n}){const r=t.toLowerCase();return function(t){return e(t)&&n.getText(t).toLowerCase().includes(r)}},"nth-child"(e,t,{adapter:n,equals:r}){const i=Lt(t);return i===mt.falseFunc?mt.falseFunc:i===mt.trueFunc?Dt(e,n):function(t){var s=n.getSiblings(t);let a=0;for(let e=0;et=>{var r=n.getParent(t);return(null==r||!n.isTag(r))&&e(t)},scope(e,t,n,r){const i=n.equals;return r&&0!==r.length?1===r.length?t=>i(r[0],t)&&e(t):t=>r.includes(t)&&e(t):Rt.root(e,t,n)},hover:wt("isHovered"),visited:wt("isVisited"),active:wt("isActive")};function wt(e){return function(t,n,{adapter:r}){const i=r[e];return"function"!=typeof i?mt.falseFunc:function(e){return i(e)&&t(e)}}}const xt={empty:(e,{adapter:t})=>!t.getChildren(e).some(e=>t.isTag(e)||""!==t.getText(e)),"first-child"(e,{adapter:t,equals:n}){var r;return t.prevElementSibling?null==t.prevElementSibling(e):null!=(r=t.getSiblings(e).find(e=>t.isTag(e)))&&n(e,r)},"last-child"(e,{adapter:t,equals:n}){var r=t.getSiblings(e);for(let i=r.length-1;0<=i;i--){if(n(e,r[i]))return!0;if(t.isTag(r[i]))break}return!1},"first-of-type"(e,{adapter:t,equals:n}){var r=t.getSiblings(e),i=t.getName(e);for(let s=0;sn(e,i)||!t.isTag(i)||t.getName(i)!==r)},"only-child":(e,{adapter:t,equals:n})=>t.getSiblings(e).every(r=>n(e,r)||!t.isTag(r))};function Mt(e,t,n,r){if(null===n){if(e.length>r)throw new Error(`Pseudo-class :${t} requires an argument`)}else if(e.length===r)throw new Error(`Pseudo-class :${t} doesn't have any arguments`)}const Pt={"any-link":":is(a, area, link)[href]",link:":any-link:not(:visited)",disabled:":is(\n :is(button, input, select, textarea, optgroup, option)[disabled],\n optgroup[disabled] > option,\n fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)\n )",enabled:":not(:disabled)",checked:":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",required:":is(input, select, textarea)[required]",optional:":is(input, select, textarea):not([required])",selected:"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",checkbox:"[type=checkbox]",file:"[type=file]",password:"[type=password]",radio:"[type=radio]",reset:"[type=reset]",image:"[type=image]",submit:"[type=submit]",parent:":not(:empty)",header:":is(h1, h2, h3, h4, h5, h6)",button:":is(button, input[type=button])",input:":is(input, textarea, select, button)",text:"input:is(:not([type!='']), [type=text])"},Bt={};function Ft(e,t){var n=t.getSiblings(e);return n.length<=1||(e=n.indexOf(e))<0||e===n.length-1?[]:n.slice(e+1).filter(t.isTag)}function Ut(e){return{xmlMode:!!e.xmlMode,lowerCaseAttributeNames:!!e.lowerCaseAttributeNames,lowerCaseTags:!!e.lowerCaseTags,quirksMode:!!e.quirksMode,cacheResults:!!e.cacheResults,pseudos:e.pseudos,adapter:e.adapter,equals:e.equals}}const Ht=(e,t,n,r,i)=>{const s=i(t,Ut(n),r);return s===mt.trueFunc?e:s===mt.falseFunc?mt.falseFunc:t=>s(t)&&e(t)},Gt={is:Ht,matches:Ht,where:Ht,not(e,t,n,r,i){const s=i(t,Ut(n),r);return s===mt.falseFunc?e:s===mt.trueFunc?mt.falseFunc:t=>!s(t)&&e(t)},has(e,t,n,r,i){const s=n.adapter,a=Ut(n),o=(a.relativeSelector=!0,t.some(e=>e.some(Tt))?[Bt]:void 0),c=i(t,a,o);if(c===mt.falseFunc)return mt.falseFunc;const u=function(e,t){return e===mt.falseFunc?mt.falseFunc:n=>t.isTag(n)&&e(n)}(c,s);if(o&&c!==mt.trueFunc){const{shouldTestNextSiblings:t=!1}=c;return n=>{if(!e(n))return!1;o[0]=n;var r=s.getChildren(n),n=t?[...r,...Ft(n,s)]:r;return s.existsOne(u,n)}}return t=>e(t)&&s.existsOne(u,s.getChildren(t))}};function jt(e,t){return(e=t.getParent(e))&&t.isTag(e)?e:null}function qt(e,t,n,r,i){const{adapter:s,equals:a}=n;switch(t.type){case et.PseudoElement:throw new Error("Pseudo-elements are not supported by css-select");case et.ColumnCombinator:throw new Error("Column combinators are not yet supported by css-select");case et.Attribute:if(null!=t.namespace)throw new Error("Namespaced attributes are not yet supported by css-select");return n.xmlMode&&!n.lowerCaseAttributeNames||(t.name=t.name.toLowerCase()),bt[t.action](e,t,n);case et.Pseudo:return function(e,t,n,r,i){const{name:a,data:o}=t;if(Array.isArray(o)){if(a in Gt)return Gt[a](e,o,n,r,i);throw new Error(`Unknown pseudo-class :${a}(${o})`)}const c=null==(t=n.pseudos)?void 0:t[a],u="string"==typeof c?c:Pt[a];if("string"==typeof u){if(null!=o)throw new Error(`Pseudo ${a} doesn't have any arguments`);const t=pt(u);return Gt.is(e,t,n,r,i)}if("function"==typeof c)return Mt(c,a,o,1),t=>c(t,o)&&e(t);if(a in Rt)return Rt[a](e,o,n,r);if(a in xt){const t=xt[a];return Mt(t,a,o,2),r=>t(r,n,o)&&e(r)}throw new Error("Unknown pseudo-class :"+a)}(e,t,n,r,i);case et.Tag:{if(null!=t.namespace)throw new Error("Namespaced tag names are not yet supported by css-select");let r=t.name;return n.xmlMode&&!n.lowerCaseTags||(r=r.toLowerCase()),function(t){return s.getName(t)===r&&e(t)}}case et.Descendant:{if(!1===n.cacheResults||"undefined"==typeof WeakSet)return function(t){let n=t;for(;n=jt(n,s);)if(e(n))return!0;return!1};const t=new WeakSet;return function(n){let r=n;for(;r=jt(r,s);)if(!t.has(r)){if(s.isTag(r)&&e(r))return!0;t.add(r)}return!1}}case"_flexibleDescendant":return function(t){let n=t;do{if(e(n))return!0}while(n=jt(n,s));return!1};case et.Parent:return function(t){return s.getChildren(t).some(t=>s.isTag(t)&&e(t))};case et.Child:return function(t){return null!=(t=s.getParent(t))&&s.isTag(t)&&e(t)};case et.Sibling:return function(t){var n=s.getSiblings(t);for(let r=0;re.some(Yt)))}const Kt={type:et.Descendant},Wt={type:"_flexibleDescendant"},Vt={type:et.Pseudo,name:"scope",data:null};function $t(e,t,n){e.forEach(gt),n=null!=(r=t.context)?r:n;const i=Array.isArray(n),s=n&&(Array.isArray(n)?n:[n]);if(!1!==t.relativeSelector)!function(e,{adapter:t},n){var r=!(null==n||!n.every(e=>{var n=t.isTag(e)&&t.getParent(e);return e===Bt||n&&t.isTag(n)}));for(const t of e){if(!(00{if(2<=e.length){const[t,n]=e;t.type===et.Pseudo&&"scope"===t.name&&(i&&n.type===et.Descendant?e[1]=Wt:n.type!==et.Adjacent&&n.type!==et.Sibling||(a=!0))}return function(e,t,n){return e.reduce((e,r)=>e===mt.falseFunc?mt.falseFunc:qt(e,r,t,n,$t),null!=(e=t.rootFunc)?e:mt.trueFunc)}(e,t,s)}).reduce(Qt,mt.falseFunc);return r.shouldTestNextSiblings=a,r}function Qt(e,t){return t===mt.falseFunc||e===mt.trueFunc?e:e===mt.falseFunc||t===mt.trueFunc?t:function(n){return e(n)||t(n)}}const zt=(e,t)=>e===t,Xt={adapter:Se,equals:zt},Zt=(Jt=$t,function(e,t,n){return t=function(e){var r;return null==(e=null!=e?e:Xt).adapter&&(e.adapter=Se),null==e.equals&&(e.equals=null!=(r=null==(r=e.adapter)?void 0:r.equals)?r:zt),e}(t),Jt(e,t,n)});function en(e,t,n=!1){return n&&(e=function(e,t){const n=Array.isArray(e)?e.slice(0):[e],r=n.length;for(let e=0;ee.some(nn)))}function rn(e){var t=[],n=[];for(const r of e)(r.some(nn)?t:n).push(r);return[n,t]}const sn={type:et.Universal,namespace:null},an={type:et.Pseudo,name:"scope",data:null};function on(e,t,n={}){return cn([e],t,n)}function cn(e,t,n={}){var i;return"function"==typeof t?e.some(t):([t,i]=rn(pt(t)),00S(e)&&!s.has(e)):t;if(0===a.length)break;var o=hn(r,t,n);if(o.length)if(s)o.forEach(e=>s.add(e));else{if(e===i.length-1)return o;s=new Set(o)}}return void 0!==s?s.size===t.length?t:t.filter(e=>s.has(e)):[]}function hn(e,t,n){var r,s;return e.some(ot)?(r=null!=(r=n.root)?r:function(e){for(;e.parent;)e=e.parent;return e}(t[0]),s={...n,context:t,relativeSelector:!1},e.push(an),fn(r,e,s,!0,t.length)):fn(t,e,n,!1,t.length)}function fn(e,t,n,r,i){var s=t.findIndex(nn),a=t.slice(0,s),o=t[s],c=t.length-1===s?i:1/0;if(0===(c=function(e,t,n){var r=null!=t?parseInt(t,10):NaN;switch(e){case"first":return 1;case"nth":case"eq":return isFinite(r)?0<=r?r+1:1/0:0;case"lt":return isFinite(r)?0<=r?Math.min(r,n):1/0:0;case"gt":return isFinite(r)?1/0:0;case"odd":return 2*n;case"even":return 2*n-1;case"last":case"not":return 1/0}}(o.name,o.data,c)))return[];r=(0!==a.length||Array.isArray(e)?0===a.length?(Array.isArray(e)?e:[e]).filter(S):r||a.some(ot)?pn(e,[a],n,c):mn(e,[a],n):J(e).filter(S)).slice(0,c);let h=function(e,t,n,r){var i="string"==typeof n?parseInt(n,10):NaN;switch(e){case"first":case"lt":return t;case"last":return 0t%2==0);case"odd":return t.filter((e,t)=>t%2==1);case"not":{const e=new Set(ln(n,t,r));return t.filter(t=>!e.has(t))}}}(o.name,r,o.data,n);if(0===h.length||t.length===s+1)return h;if(e=(a=t.slice(s+1)).some(ot)){if(ot(a[0])){const e=a[0].type;e!==et.Sibling&&e!==et.Adjacent||(h=en(h,Se,!0)),a.unshift(sn)}n={...n,relativeSelector:!1,rootFunc:e=>h.includes(e)}}else n.rootFunc&&n.rootFunc!==_t&&(n={...n,rootFunc:_t});return a.some(nn)?fn(h,a,n,!1,i):e?pn(h,[a],n,i):mn(h,[a],n)}function pn(e,t,n,r){return dn(e,Zt(t,n,e),r)}function dn(e,t,n=1/0){return ae(e=>S(e)&&t(e),en(e,Se,t.shouldTestNextSiblings),!0,n)}function mn(e,t,n){return 0===(e=(Array.isArray(e)?e:[e]).filter(S)).length||(t=Zt(t,n))===_t?e:e.filter(t)}var _n=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;ifn(t,e,n,!0,1/0)),e.length&&s.push(pn(t,e,n,1/0)),0===s.length?[]:1===s.length?s[0]:me(s.reduce((e,t)=>[...e,...t])))}(e,r,t)))):this._make([])},parent:Sn,parents:Cn,parentsUntil:Nn,closest:function(e){var t,r,i,n=[];return e&&(r={xmlMode:this.options.xmlMode,root:null==(t=this._root)?void 0:t[0]},i="string"==typeof e?function(t){return on(t,e,r)}:xn(e),Be(this,function(e){for(;e&&S(e);){if(i(e,0)){n.includes(e)||n.push(e);break}e=e.parent}})),this._make(n)},next:bn,nextAll:In,nextUntil:On,prev:kn,prevAll:Ln,prevUntil:Dn,siblings:Rn,children:wn,contents:function(){var e=this.toArray().reduce(function(e,t){return k(t)?e.concat(t.children):e},[]);return this._make(e)},each:function(e){for(var t=0,n=this.length;t=e.length?null:e[t+n],l=0;lthis.bufferWaterline}dropParsedChunk(){this.willDropParsedChunk()&&(this.html=this.html.substring(this.pos),this.lineStartPos-=this.pos,this.droppedBufferSize+=this.pos,this.pos=0,this.lastGapPos=-2,this.gapStack.length=0)}write(e,t){0this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,!1;if(t)return this.html.startsWith(e,this.pos);for(let t=0;t=this.html.length?(this.endOfChunkHit=!this.lastChunkWritten,ar.EOF):(e=this.html.charCodeAt(e))===ar.CARRIAGE_RETURN?ar.LINE_FEED:e}advance(){if(this.pos++,this.isEol&&(this.isEol=!1,this.line++,this.lineStartPos=this.pos),this.pos>=this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,ar.EOF;let e=this.html.charCodeAt(this.pos);return e===ar.CARRIAGE_RETURN?(this.isEol=!0,this.skipNextNewLine=!0,ar.LINE_FEED):e===ar.LINE_FEED&&(this.isEol=!0,this.skipNextNewLine)?(this.line--,this.skipNextNewLine=!1,this._addGap(),this.advance()):(this.skipNextNewLine=!1,pr(e)&&(e=this._processSurrogate(e)),null===this.handler.onParseError||31䀾mmaĀ;d׷׸䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇܎ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ޷޼߂ߐĀiyޱ޵rc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄΀HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶߻dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣ঳সে্਷ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗ࡜ࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४Ānrࢃ࢏gleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpw৔ਖਛgȀLRlr৞৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼੝੠੷੼અઋ઎p;椅y;䐜Ādl੥੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑ඗ඞcy;䐊cute;䅃ƀaey઴હાron;䅇dil;䅅;䐝ƀgswે૰଎ativeƀMTV૓૟૨ediumSpace;怋hiĀcn૦૘ë૙eryThiî૙tedĀGL૸ଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷ଺reak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪୼஡௫ఄ౞಄ದ೘ൡඅ櫬Āou୛୤ngruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊ஛ement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater΀;EFGLSTஶஷ஽௉௓௘௥扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲௽ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ೒拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨೹setĀ;E೰ೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂ෉෕ෛ෠෧෼ขภยา฿ไlig;䅒cute耻Ó䃓Āiy෎ීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲ෶cr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬื฼de耻Õ䃕es;樷ml耻Ö䃖erĀBP๋๠Āar๐๓r;怾acĀek๚๜;揞et;掴arenthesis;揜Ҁacfhilors๿ງຊຏຒດຝະ໼rtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ໠໤檻cedesȀ;EST່້໏໚扺qual;檯lantEqual;扼ilde;找me;怳Ādp໩໮uct;戏ortionĀ;aȥ໹l;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬؀BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁࿫࿳ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL࿜࿝࿡憒ar;懥eftArrow;懄eiling;按oǵ࿹\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»࿝pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄ቉ቕ቞ቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHc቎ቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗Āeiቻ኉Dzኀ\0ኇefore;戴a;䎘Ācn኎ኘkSpace;쀀  Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0᏿ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0጖y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻፿on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtè૙a;䎖r;愨pf;愤cr;쀀𝒵௡ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚΀;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒΀;Eaeiop዁ᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;e዁ᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;e዁ᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰ᜼ᝃᝈ᝸᝽០៦ᠹᡐᜍ᤽᥈ᥰot;櫭Ācrᛶ᜞kȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e᜚᜛戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;t፜᜷brk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓ᝛ᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯ᝱ᝳ;䎲;愶een;扬r;쀀𝔟g΀costuvwឍឝឳេ៕៛៞ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀako៭ᠦᠵĀcn៲ᠣkƀlst៺֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘᠝斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈؀DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬ᣿ᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教΀;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ᣷᣹᣻᣽;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ᤟;敛;敘;攘;攔΀;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģ᥂bar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;e᜚᜜lƀ;bhᥨᥩᥫ䁜;槅sub;柈Ŭᥴ᥾lĀ;e᥹᥺怢t»᥺pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭍᭒\0᯽\0ᰌƀcpr᦭ᦲ᧝ute;䄇̀;abcdsᦿᧀᧄ᧊᧕᧙戩nd;橄rcup;橉Āau᧏᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r΀;Ecefms᩟᩠ᩢᩫ᪤᪪᪮旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖᪚᪟»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇᫔᫺\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ᫙\0\0᫢aĀ;t᫞᫟䀬;䁀ƀ;fl᫨᫩᫫戁îᅠeĀmx᫱᫶ent»᫩eóɍǧ᫾\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯΀delprvw᭠᭬᭷ᮂᮬᯔ᯹arrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;p᭿ᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰸᰻᰿ᱝᱩᱵᲊᲞᲬᲷ᳻᳿ᴍᵻᶑᶫᶻ᷆᷍rò΁ar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂͸᳖᳜᳠mƀ;oș᳊᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄ὎὚ĀDoḆᴴoôᲉĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»Ṻƀaeiἒ἖Ἒls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙ῜ῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao‌⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧‪‬\0‮耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₟₥₰₴⃰⃵⃺⃿℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕ₝ute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽⃉ƀ;qsؾٌ⃄lanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqr׮ⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0↎proø₞r;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢␧␭␱␵␻ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀஀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼rò৆òΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonó྘quigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d྘➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ᠛旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐௏쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop඄⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roø඄urĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓΀;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨í஘istĀ;s஠டr;쀀𝔫ȀEest௅⩦⩹⩼ƀ;qs஼⩭௡ƀ;qs஼௅⩴lanô௢ií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚΀AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs఻⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs఻⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast୻⭕⭚⭟lleì୻l;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖ΀chimpqu⮽⯍⯙⬄୸⯤⯯Ȁ;cerല⯆ഷ⯉uå൅;쀀𝓃ortɭ⬅\0\0⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭å೸åഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñ೗Ā;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰⳴ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\0\0\0\0\0\0\0\0\0\0\0\0\0ⴭ\0ⴸⵈⵠⵥ⵲ⶄᬇ\0\0ⶍⶫ\0ⷈⷎ\0ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;c᪞ⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācr⵩⵭ir;榿;쀀𝔬ͯ⵹\0\0⵼\0ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕ⶘ⶥⶨrò᪀Āir⶝ⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀𝕠ƀaelⷔ⷗ǒr;榷rp;榹΀;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ⹞\0⹽\0⺀⺝\0⺢⺹\0\0⻋ຜ\0⼓\0\0⼫⾼\0⿈rȀ;astЃ⹧⹲຅脀¶;l⹭⹮䂶leìЃɩ⹸\0\0⹻m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀𝔭ƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳⻴ᤈ⻹⻽⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀𝕡nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp໬⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t໻⾴ï໻rel;抰Āci⿀⿅r;쀀𝓅;䏈ncsp;怈̀fiopsu⿚⋢⿟⿥⿫⿱r;쀀𝔮pf;쀀𝕢rime;恗cr;쀀𝓆ƀaeo⿸〉〓tĀei⿾々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔઀ABHabcdefhilmnoprstux぀けさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤΀cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstw࿜ガクシスゼゾダッデナp;極Ā;f࿠ゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes㄂㄄;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ì࿲âヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀𝔯ĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘㇤㇮rrowĀ;t࿜ㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowó࿪arpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓rò࿪aòՑ;怏oustĀ;a㈞㈟掱che»㈟mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀𝕣us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀𝓇Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\0㍺㎤\0\0㏬㏰\0㐨㑈㑚㒭㒱㓊㓱\0㘖\0\0㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\0㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦΀Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼਴t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀𝔰Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\0\0㎜iäᑤaraì⹯耻­䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;q኱ኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀𝕤aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀𝓈tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫ਩war;椪lig耻ß䃟௡㙑㙝㙠ዎ㙳㙹\0㙾㛂\0\0\0\0\0㛛㜃\0㜉㝬\0\0\0㞇ɲ㙖\0\0㙛get;挖;䏄rë๟ƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀𝔱Ȁeiko㚆㚝㚵㚼Dz㚋\0㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproø዁im»ኬsðኞĀas㚺㚮ð዁rn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀𝕥rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈ΀adempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀𝓉;䑆cy;䑛rok;䅧Āio㞋㞎xô᝷headĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\0㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀𝔲rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\0\0㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀𝕦̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\0\0㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀𝓊ƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜΀eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀𝔳tré㦮suĀbp㧯㧱»ജ»൙pf;쀀𝕧roð໻tré㦴Ācu㨆㨋r;쀀𝓋Ābp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚΀cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀𝔴pf;쀀𝕨Ā;eᑹ㩦atèᑹcr;쀀𝓌ૣណ㪇\0㪋\0㪐㪛\0\0㪝㪨㪫㪯\0\0㫃㫎\0㫘ៜ៟tré៑r;쀀𝔵ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀𝕩imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀𝓍Āpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀𝔶cy;䑗pf;쀀𝕪cr;쀀𝓎Ācm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀𝔷cy;䐶grarr;懝pf;쀀𝕫cr;쀀𝓏Ājn㮅㮇;怍j;怌'.split("").map(function(e){return e.charCodeAt(0)}))}),Nr=(yr(Cr),Sr(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array("Ȁaglq\tɭ\0\0p;䀦os;䀧t;䀾t;䀼uot;䀢".split("").map(function(e){return e.charCodeAt(0)}))})),br=(yr(Nr),Sr(function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.replaceCodePoint=t.fromCodePoint=void 0;var n,r=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);function i(e){var t;return 55296<=e&&e<=57343||1114111>>10&1023|55296),e=56320|1023&e),t+String.fromCharCode(e)},t.replaceCodePoint=i,t.default=function(e){return(0,t.fromCodePoint)(i(e))}})),Ir=(yr(br),br.replaceCodePoint,br.fromCodePoint,Cr),Or=Nr,kr=br,Lr=Sr(function(e,t){var n=gr&&gr.__createBinding||(Object.create?function(e,t,n,r){void 0===r&&(r=n);var i=Object.getOwnPropertyDescriptor(t,n);i&&!("get"in i?!t.__esModule:i.writable||i.configurable)||(i={enumerable:!0,get:function(){return t[n]}}),Object.defineProperty(e,r,i)}:function(e,t,n,r){e[r=void 0===r?n:r]=t[n]}),r=gr&&gr.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),i=gr&&gr.__importStar||function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i in e)"default"!==i&&Object.prototype.hasOwnProperty.call(e,i)&&n(t,e,i);return r(t,e),t},s=gr&&gr.__importDefault||function(e){return e&&e.__esModule?e:{default:e}},a=(Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXML=t.decodeHTMLStrict=t.decodeHTMLAttribute=t.decodeHTML=t.determineBranch=t.EntityDecoder=t.DecodingMode=t.BinTrieFlags=t.fromCodePoint=t.replaceCodePoint=t.decodeCodePoint=t.xmlDecodeTree=t.htmlDecodeTree=void 0,s(Ir)),s=(t.htmlDecodeTree=a.default,s(Or)),c=(t.xmlDecodeTree=s.default,i(kr));t.decodeCodePoint=c.default;var u,h,f,p,l=kr;function d(e){return e>=u.ZERO&&e<=u.NINE}Object.defineProperty(t,"replaceCodePoint",{enumerable:!0,get:function(){return l.replaceCodePoint}}),Object.defineProperty(t,"fromCodePoint",{enumerable:!0,get:function(){return l.fromCodePoint}}),function(e){e[e.NUM=35]="NUM",e[e.SEMI=59]="SEMI",e[e.EQUALS=61]="EQUALS",e[e.ZERO=48]="ZERO",e[e.NINE=57]="NINE",e[e.LOWER_A=97]="LOWER_A",e[e.LOWER_F=102]="LOWER_F",e[e.LOWER_X=120]="LOWER_X",e[e.LOWER_Z=122]="LOWER_Z",e[e.UPPER_A=65]="UPPER_A",e[e.UPPER_F=70]="UPPER_F",e[e.UPPER_Z=90]="UPPER_Z"}(u={}),function(e){e[e.VALUE_LENGTH=49152]="VALUE_LENGTH",e[e.BRANCH_LENGTH=16256]="BRANCH_LENGTH",e[e.JUMP_TABLE=127]="JUMP_TABLE"}(h=t.BinTrieFlags||(t.BinTrieFlags={})),function(e){e[e.EntityStart=0]="EntityStart",e[e.NumericStart=1]="NumericStart",e[e.NumericDecimal=2]="NumericDecimal",e[e.NumericHex=3]="NumericHex",e[e.NamedEntity=4]="NamedEntity"}(f={}),function(e){e[e.Legacy=0]="Legacy",e[e.Strict=1]="Strict",e[e.Attribute=2]="Attribute"}(p=t.DecodingMode||(t.DecodingMode={}));var _=function(){function e(e,t,n){this.decodeTree=e,this.emitCodePoint=t,this.errors=n,this.state=f.EntityStart,this.consumed=1,this.result=0,this.treeIndex=0,this.excess=1,this.decodeMode=p.Strict}return e.prototype.startEntity=function(e){this.decodeMode=e,this.state=f.EntityStart,this.result=0,this.treeIndex=0,this.excess=1,this.consumed=1},e.prototype.write=function(e,t){switch(this.state){case f.EntityStart:return e.charCodeAt(t)===u.NUM?(this.state=f.NumericStart,this.consumed+=1,this.stateNumericStart(e,t+1)):(this.state=f.NamedEntity,this.stateNamedEntity(e,t));case f.NumericStart:return this.stateNumericStart(e,t);case f.NumericDecimal:return this.stateNumericDecimal(e,t);case f.NumericHex:return this.stateNumericHex(e,t);case f.NamedEntity:return this.stateNamedEntity(e,t)}},e.prototype.stateNumericStart=function(e,t){return t>=e.length?-1:(32|e.charCodeAt(t))===u.LOWER_X?(this.state=f.NumericHex,this.consumed+=1,this.stateNumericHex(e,t+1)):(this.state=f.NumericDecimal,this.stateNumericDecimal(e,t))},e.prototype.addToNumericResult=function(e,t,n,r){t!==n&&(n-=t,this.result=this.result*Math.pow(r,n)+parseInt(e.substr(t,n),r),this.consumed+=n)},e.prototype.stateNumericHex=function(e,t){for(var r=t;t=u.UPPER_A&&i<=u.UPPER_F||u.LOWER_A<=i&&i<=u.LOWER_F))return this.addToNumericResult(e,r,t,16),this.emitNumericEntity(i,3);t+=1}return this.addToNumericResult(e,r,t,16),-1},e.prototype.stateNumericDecimal=function(e,t){for(var n=t;t>14;t>14)){if(s===u.SEMI)return this.emitNamedEntityData(this.treeIndex,i,this.consumed+this.excess);this.decodeMode!==p.Strict&&(this.result=this.treeIndex,this.consumed+=this.excess,this.excess=0)}}return-1},e.prototype.emitNotTerminatedNamedEntity=function(){var t=this.result,n=(this.decodeTree[t]&h.VALUE_LENGTH)>>14;return this.emitNamedEntityData(t,n,this.consumed),null!=(t=this.errors)&&t.missingSemicolonAfterCharacterReference(),this.consumed},e.prototype.emitNamedEntityData=function(e,t,n){var r=this.decodeTree;return this.emitCodePoint(1===t?r[e]&~h.VALUE_LENGTH:r[e+1],n),3===t&&this.emitCodePoint(r[e+2],n),n},e.prototype.end=function(){var e;switch(this.state){case f.NamedEntity:return 0===this.result||this.decodeMode===p.Attribute&&this.result!==this.treeIndex?0:this.emitNotTerminatedNamedEntity();case f.NumericDecimal:return this.emitNumericEntity(0,2);case f.NumericHex:return this.emitNumericEntity(0,3);case f.NumericStart:return null!=(e=this.errors)&&e.absenceOfDigitsInNumericCharacterReference(this.consumed),0;case f.EntityStart:return 0}},e}();function E(e){var t="",n=new _(e,function(e){return t+=(0,c.fromCodePoint)(e)});return function(e,r){for(var i=0,s=0;0<=(s=e.indexOf("&",s));){t+=e.slice(i,s),n.startEntity(r);var a=n.write(e,s+1);if(a<0){i=s+n.end();break}i=s+a,s=0===a?i+1:i}var o=t+e.slice(i);return t="",o}}function T(e,t,n,r){var i=(t&h.BRANCH_LENGTH)>>7,t=t&h.JUMP_TABLE;if(0==i)return 0!=t&&r===t?n:-1;if(t)return(t=r-t)<0||i<=t?-1:e[n+t]-1;for(var o=n,c=o+i-1;o<=c;){var u=o+c>>>1,l=e[u];if(l=ar.DIGIT_0&&e<=ar.DIGIT_9}function Zr(e){return e>=ar.LATIN_CAPITAL_A&&e<=ar.LATIN_CAPITAL_Z}function Jr(e){return function(e){return e>=ar.LATIN_SMALL_A&&e<=ar.LATIN_SMALL_Z}(e)||Zr(e)}function ei(e){return Jr(e)||Xr(e)}function ti(e){return e>=ar.LATIN_CAPITAL_A&&e<=ar.LATIN_CAPITAL_F}function ni(e){return e>=ar.LATIN_SMALL_A&&e<=ar.LATIN_SMALL_F}function ri(e){return e+32}function ii(e){return e===ar.SPACE||e===ar.LINE_FEED||e===ar.TABULATION||e===ar.FORM_FEED}function si(e){return ii(e)||e===ar.SOLIDUS||e===ar.GREATER_THAN_SIGN}class ai{constructor(e,t){this.options=e,this.handler=t,this.paused=!1,this.inLoop=!1,this.inForeignNode=!1,this.lastStartTagName="",this.active=!1,this.state=Qr.DATA,this.returnState=Qr.DATA,this.charRefCode=-1,this.consumedAfterSnapshot=-1,this.currentCharacterToken=null,this.currentToken=null,this.currentAttr={name:"",value:""},this.preprocessor=new Er(t),this.currentLocation=this.getCurrentLocation(-1)}_err(e){var t,n;null!=(n=(t=this.handler).onParseError)&&n.call(t,this.preprocessor.getError(e))}getCurrentLocation(e){return this.options.sourceCodeLocationInfo?{startLine:this.preprocessor.line,startCol:this.preprocessor.col-e,startOffset:this.preprocessor.offset-e,endLine:-1,endCol:-1,endOffset:-1}:null}_runParsingLoop(){if(!this.inLoop){for(this.inLoop=!0;this.active&&!this.paused;){this.consumedAfterSnapshot=0;var e=this._consume();this._ensureHibernation()||this._callState(e)}this.inLoop=!1}}pause(){this.paused=!0}resume(e){if(!this.paused)throw new Error("Parser was already resumed");this.paused=!1,this.inLoop||(this._runParsingLoop(),this.paused)||null==e||e()}write(e,t,n){this.active=!0,this.preprocessor.write(e,t),this._runParsingLoop(),this.paused||null==n||n()}insertHtmlAtCurrentPos(e){this.active=!0,this.preprocessor.insertHtmlAtCurrentPos(e),this._runParsingLoop()}_ensureHibernation(){return!(!this.preprocessor.endOfChunkHit||(this._unconsume(this.consumedAfterSnapshot),this.active=!1))}_consume(){return this.consumedAfterSnapshot++,this.preprocessor.advance()}_unconsume(e){this.consumedAfterSnapshot-=e,this.preprocessor.retreat(e)}_reconsumeInState(e,t){this.state=e,this._callState(t)}_advanceBy(e){this.consumedAfterSnapshot+=e;for(let t=0;t>14)-1;if(e!==ar.SEMICOLON&&this._isCharacterReferenceInAttribute()&&((o=this.preprocessor.peek(1))===ar.EQUALS_SIGN||ei(o))?(t=[ar.AMPERSAND],s+=a):(t=0==a?[Gr[s]&~Rr.VALUE_LENGTH]:1==a?[Gr[++s]]:[Gr[++s],Gr[++s]],n=0,r=e!==ar.SEMICOLON),0==a){this._consume();break}}}return this._unconsume(n),r&&!this.preprocessor.endOfChunkHit&&this._err(_r.missingSemicolonAfterCharacterReference),this._unconsume(1),t}_isCharacterReferenceInAttribute(){return this.returnState===Qr.ATTRIBUTE_VALUE_DOUBLE_QUOTED||this.returnState===Qr.ATTRIBUTE_VALUE_SINGLE_QUOTED||this.returnState===Qr.ATTRIBUTE_VALUE_UNQUOTED}_flushCodePointConsumedAsCharacterReference(e){this._isCharacterReferenceInAttribute()?this.currentAttr.value+=String.fromCodePoint(e):this._emitCodePoint(e)}_callState(e){switch(this.state){case Qr.DATA:this._stateData(e);break;case Qr.RCDATA:this._stateRcdata(e);break;case Qr.RAWTEXT:this._stateRawtext(e);break;case Qr.SCRIPT_DATA:this._stateScriptData(e);break;case Qr.PLAINTEXT:this._statePlaintext(e);break;case Qr.TAG_OPEN:this._stateTagOpen(e);break;case Qr.END_TAG_OPEN:this._stateEndTagOpen(e);break;case Qr.TAG_NAME:this._stateTagName(e);break;case Qr.RCDATA_LESS_THAN_SIGN:this._stateRcdataLessThanSign(e);break;case Qr.RCDATA_END_TAG_OPEN:this._stateRcdataEndTagOpen(e);break;case Qr.RCDATA_END_TAG_NAME:this._stateRcdataEndTagName(e);break;case Qr.RAWTEXT_LESS_THAN_SIGN:this._stateRawtextLessThanSign(e);break;case Qr.RAWTEXT_END_TAG_OPEN:this._stateRawtextEndTagOpen(e);break;case Qr.RAWTEXT_END_TAG_NAME:this._stateRawtextEndTagName(e);break;case Qr.SCRIPT_DATA_LESS_THAN_SIGN:this._stateScriptDataLessThanSign(e);break;case Qr.SCRIPT_DATA_END_TAG_OPEN:this._stateScriptDataEndTagOpen(e);break;case Qr.SCRIPT_DATA_END_TAG_NAME:this._stateScriptDataEndTagName(e);break;case Qr.SCRIPT_DATA_ESCAPE_START:this._stateScriptDataEscapeStart(e);break;case Qr.SCRIPT_DATA_ESCAPE_START_DASH:this._stateScriptDataEscapeStartDash(e);break;case Qr.SCRIPT_DATA_ESCAPED:this._stateScriptDataEscaped(e);break;case Qr.SCRIPT_DATA_ESCAPED_DASH:this._stateScriptDataEscapedDash(e);break;case Qr.SCRIPT_DATA_ESCAPED_DASH_DASH:this._stateScriptDataEscapedDashDash(e);break;case Qr.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataEscapedLessThanSign(e);break;case Qr.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:this._stateScriptDataEscapedEndTagOpen(e);break;case Qr.SCRIPT_DATA_ESCAPED_END_TAG_NAME:this._stateScriptDataEscapedEndTagName(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPE_START:this._stateScriptDataDoubleEscapeStart(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED:this._stateScriptDataDoubleEscaped(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED_DASH:this._stateScriptDataDoubleEscapedDash(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:this._stateScriptDataDoubleEscapedDashDash(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataDoubleEscapedLessThanSign(e);break;case Qr.SCRIPT_DATA_DOUBLE_ESCAPE_END:this._stateScriptDataDoubleEscapeEnd(e);break;case Qr.BEFORE_ATTRIBUTE_NAME:this._stateBeforeAttributeName(e);break;case Qr.ATTRIBUTE_NAME:this._stateAttributeName(e);break;case Qr.AFTER_ATTRIBUTE_NAME:this._stateAfterAttributeName(e);break;case Qr.BEFORE_ATTRIBUTE_VALUE:this._stateBeforeAttributeValue(e);break;case Qr.ATTRIBUTE_VALUE_DOUBLE_QUOTED:this._stateAttributeValueDoubleQuoted(e);break;case Qr.ATTRIBUTE_VALUE_SINGLE_QUOTED:this._stateAttributeValueSingleQuoted(e);break;case Qr.ATTRIBUTE_VALUE_UNQUOTED:this._stateAttributeValueUnquoted(e);break;case Qr.AFTER_ATTRIBUTE_VALUE_QUOTED:this._stateAfterAttributeValueQuoted(e);break;case Qr.SELF_CLOSING_START_TAG:this._stateSelfClosingStartTag(e);break;case Qr.BOGUS_COMMENT:this._stateBogusComment(e);break;case Qr.MARKUP_DECLARATION_OPEN:this._stateMarkupDeclarationOpen(e);break;case Qr.COMMENT_START:this._stateCommentStart(e);break;case Qr.COMMENT_START_DASH:this._stateCommentStartDash(e);break;case Qr.COMMENT:this._stateComment(e);break;case Qr.COMMENT_LESS_THAN_SIGN:this._stateCommentLessThanSign(e);break;case Qr.COMMENT_LESS_THAN_SIGN_BANG:this._stateCommentLessThanSignBang(e);break;case Qr.COMMENT_LESS_THAN_SIGN_BANG_DASH:this._stateCommentLessThanSignBangDash(e);break;case Qr.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH:this._stateCommentLessThanSignBangDashDash(e);break;case Qr.COMMENT_END_DASH:this._stateCommentEndDash(e);break;case Qr.COMMENT_END:this._stateCommentEnd(e);break;case Qr.COMMENT_END_BANG:this._stateCommentEndBang(e);break;case Qr.DOCTYPE:this._stateDoctype(e);break;case Qr.BEFORE_DOCTYPE_NAME:this._stateBeforeDoctypeName(e);break;case Qr.DOCTYPE_NAME:this._stateDoctypeName(e);break;case Qr.AFTER_DOCTYPE_NAME:this._stateAfterDoctypeName(e);break;case Qr.AFTER_DOCTYPE_PUBLIC_KEYWORD:this._stateAfterDoctypePublicKeyword(e);break;case Qr.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:this._stateBeforeDoctypePublicIdentifier(e);break;case Qr.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypePublicIdentifierDoubleQuoted(e);break;case Qr.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypePublicIdentifierSingleQuoted(e);break;case Qr.AFTER_DOCTYPE_PUBLIC_IDENTIFIER:this._stateAfterDoctypePublicIdentifier(e);break;case Qr.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:this._stateBetweenDoctypePublicAndSystemIdentifiers(e);break;case Qr.AFTER_DOCTYPE_SYSTEM_KEYWORD:this._stateAfterDoctypeSystemKeyword(e);break;case Qr.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:this._stateBeforeDoctypeSystemIdentifier(e);break;case Qr.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypeSystemIdentifierDoubleQuoted(e);break;case Qr.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypeSystemIdentifierSingleQuoted(e);break;case Qr.AFTER_DOCTYPE_SYSTEM_IDENTIFIER:this._stateAfterDoctypeSystemIdentifier(e);break;case Qr.BOGUS_DOCTYPE:this._stateBogusDoctype(e);break;case Qr.CDATA_SECTION:this._stateCdataSection(e);break;case Qr.CDATA_SECTION_BRACKET:this._stateCdataSectionBracket(e);break;case Qr.CDATA_SECTION_END:this._stateCdataSectionEnd(e);break;case Qr.CHARACTER_REFERENCE:this._stateCharacterReference(e);break;case Qr.NAMED_CHARACTER_REFERENCE:this._stateNamedCharacterReference(e);break;case Qr.AMBIGUOUS_AMPERSAND:this._stateAmbiguousAmpersand(e);break;case Qr.NUMERIC_CHARACTER_REFERENCE:this._stateNumericCharacterReference(e);break;case Qr.HEXADEMICAL_CHARACTER_REFERENCE_START:this._stateHexademicalCharacterReferenceStart(e);break;case Qr.HEXADEMICAL_CHARACTER_REFERENCE:this._stateHexademicalCharacterReference(e);break;case Qr.DECIMAL_CHARACTER_REFERENCE:this._stateDecimalCharacterReference(e);break;case Qr.NUMERIC_CHARACTER_REFERENCE_END:this._stateNumericCharacterReferenceEnd(e);break;default:throw new Error("Unknown state")}}_stateData(e){switch(e){case ar.LESS_THAN_SIGN:this.state=Qr.TAG_OPEN;break;case ar.AMPERSAND:this.returnState=Qr.DATA,this.state=Qr.CHARACTER_REFERENCE;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitCodePoint(e);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRcdata(e){switch(e){case ar.AMPERSAND:this.returnState=Qr.RCDATA,this.state=Qr.CHARACTER_REFERENCE;break;case ar.LESS_THAN_SIGN:this.state=Qr.RCDATA_LESS_THAN_SIGN;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRawtext(e){switch(e){case ar.LESS_THAN_SIGN:this.state=Qr.RAWTEXT_LESS_THAN_SIGN;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateScriptData(e){switch(e){case ar.LESS_THAN_SIGN:this.state=Qr.SCRIPT_DATA_LESS_THAN_SIGN;break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_statePlaintext(e){switch(e){case ar.NULL:this._err(_r.unexpectedNullCharacter),this._emitChars(sr);break;case ar.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateTagOpen(e){if(Jr(e))this._createStartTagToken(),this.state=Qr.TAG_NAME,this._stateTagName(e);else switch(e){case ar.EXCLAMATION_MARK:this.state=Qr.MARKUP_DECLARATION_OPEN;break;case ar.SOLIDUS:this.state=Qr.END_TAG_OPEN;break;case ar.QUESTION_MARK:this._err(_r.unexpectedQuestionMarkInsteadOfTagName),this._createCommentToken(1),this.state=Qr.BOGUS_COMMENT,this._stateBogusComment(e);break;case ar.EOF:this._err(_r.eofBeforeTagName),this._emitChars("<"),this._emitEOFToken();break;default:this._err(_r.invalidFirstCharacterOfTagName),this._emitChars("<"),this.state=Qr.DATA,this._stateData(e)}}_stateEndTagOpen(e){if(Jr(e))this._createEndTagToken(),this.state=Qr.TAG_NAME,this._stateTagName(e);else switch(e){case ar.GREATER_THAN_SIGN:this._err(_r.missingEndTagName),this.state=Qr.DATA;break;case ar.EOF:this._err(_r.eofBeforeTagName),this._emitChars("");break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this.state=Qr.SCRIPT_DATA_ESCAPED,this._emitChars(sr);break;case ar.EOF:this._err(_r.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Qr.SCRIPT_DATA_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataEscapedLessThanSign(e){e===ar.SOLIDUS?this.state=Qr.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:Jr(e)?(this._emitChars("<"),this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPE_START,this._stateScriptDataDoubleEscapeStart(e)):(this._emitChars("<"),this.state=Qr.SCRIPT_DATA_ESCAPED,this._stateScriptDataEscaped(e))}_stateScriptDataEscapedEndTagOpen(e){Jr(e)?(this.state=Qr.SCRIPT_DATA_ESCAPED_END_TAG_NAME,this._stateScriptDataEscapedEndTagName(e)):(this._emitChars("");break;case ar.NULL:this._err(_r.unexpectedNullCharacter),this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitChars(sr);break;case ar.EOF:this._err(_r.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataDoubleEscapedLessThanSign(e){e===ar.SOLIDUS?(this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPE_END,this._emitChars("/")):(this.state=Qr.SCRIPT_DATA_DOUBLE_ESCAPED,this._stateScriptDataDoubleEscaped(e))}_stateScriptDataDoubleEscapeEnd(e){if(this.preprocessor.startsWith(lr,!1)&&si(this.preprocessor.peek(lr.length))){this._emitCodePoint(e);for(let e=0;e=e;){var t=this.current;0[e.name,e.value]));let i=0;for(let e=0;er.get(e.name)===e.value)&&3<=(i+=1)&&this.entries.splice(t.idx,1)}}}}insertMarker(){this.entries.unshift(Ei)}pushElement(e,t){this._ensureNoahArkCondition(e),this.entries.unshift({type:_i.Element,element:e,token:t})}insertElementAfterBookmark(e,t){var n=this.entries.indexOf(this.bookmark);this.entries.splice(n,0,{type:_i.Element,element:e,token:t})}removeEntry(e){0<=(e=this.entries.indexOf(e))&&this.entries.splice(e,1)}clearToLastMarker(){var e=this.entries.indexOf(Ei);0<=e?this.entries.splice(0,e+1):this.entries.length=0}getElementEntryInScopeWithTagName(e){var t=this.entries.find(t=>t.type===_i.Marker||this.treeAdapter.getTagName(t.element)===e);return t&&t.type===_i.Element?t:null}getElementEntry(e){return this.entries.find(t=>t.type===_i.Element&&t.element===e)}}function Ai(e){return{nodeName:"#text",value:e,parentNode:null}}const gi={createDocument:()=>({nodeName:"#document",mode:Br.NO_QUIRKS,childNodes:[]}),createDocumentFragment:()=>({nodeName:"#document-fragment",childNodes:[]}),createElement:(e,t,n)=>({nodeName:e,tagName:e,attrs:n,namespaceURI:t,childNodes:[],parentNode:null}),createCommentNode:e=>({nodeName:"#comment",data:e,parentNode:null}),appendChild(e,t){e.childNodes.push(t),t.parentNode=e},insertBefore(e,t,n){n=e.childNodes.indexOf(n),e.childNodes.splice(n,0,t),t.parentNode=e},setTemplateContent(e,t){e.content=t},getTemplateContent:e=>e.content,setDocumentType(e,t,n,r){const i=e.childNodes.find(e=>"#documentType"===e.nodeName);if(i)i.name=t,i.publicId=n,i.systemId=r;else{const i={nodeName:"#documentType",name:t,publicId:n,systemId:r,parentNode:null};gi.appendChild(e,i)}},setDocumentMode(e,t){e.mode=t},getDocumentMode:e=>e.mode,detachNode(e){var t;e.parentNode&&(t=e.parentNode.childNodes.indexOf(e),e.parentNode.childNodes.splice(t,1),e.parentNode=null)},insertText(e,t){if(0e.name));for(let r=0;re.childNodes[0],getChildNodes:e=>e.childNodes,getParentNode:e=>e.parentNode,getAttrList:e=>e.attrs,getTagName:e=>e.tagName,getNamespaceURI:e=>e.namespaceURI,getTextNodeContent:e=>e.value,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName:e=>e.name,getDocumentTypeNodePublicId:e=>e.publicId,getDocumentTypeNodeSystemId:e=>e.systemId,isTextNode:e=>"#text"===e.nodeName,isCommentNode:e=>"#comment"===e.nodeName,isDocumentTypeNode:e=>"#documentType"===e.nodeName,isElementNode:e=>Object.prototype.hasOwnProperty.call(e,"tagName"),setNodeSourceCodeLocation(e,t){e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){e.sourceCodeLocation={...e.sourceCodeLocation,...t}}},vi="html",yi="about:legacy-compat",Si="http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd",Ci=["+//silmaril//dtd html pro v0r11 19970101//","-//as//dtd html 3.0 aswedit + extensions//","-//advasoft ltd//dtd html 3.0 aswedit + extensions//","-//ietf//dtd html 2.0 level 1//","-//ietf//dtd html 2.0 level 2//","-//ietf//dtd html 2.0 strict level 1//","-//ietf//dtd html 2.0 strict level 2//","-//ietf//dtd html 2.0 strict//","-//ietf//dtd html 2.0//","-//ietf//dtd html 2.1e//","-//ietf//dtd html 3.0//","-//ietf//dtd html 3.2 final//","-//ietf//dtd html 3.2//","-//ietf//dtd html 3//","-//ietf//dtd html level 0//","-//ietf//dtd html level 1//","-//ietf//dtd html level 2//","-//ietf//dtd html level 3//","-//ietf//dtd html strict level 0//","-//ietf//dtd html strict level 1//","-//ietf//dtd html strict level 2//","-//ietf//dtd html strict level 3//","-//ietf//dtd html strict//","-//ietf//dtd html//","-//metrius//dtd metrius presentational//","-//microsoft//dtd internet explorer 2.0 html strict//","-//microsoft//dtd internet explorer 2.0 html//","-//microsoft//dtd internet explorer 2.0 tables//","-//microsoft//dtd internet explorer 3.0 html strict//","-//microsoft//dtd internet explorer 3.0 html//","-//microsoft//dtd internet explorer 3.0 tables//","-//netscape comm. corp.//dtd html//","-//netscape comm. corp.//dtd strict html//","-//o'reilly and associates//dtd html 2.0//","-//o'reilly and associates//dtd html extended 1.0//","-//o'reilly and associates//dtd html extended relaxed 1.0//","-//sq//dtd html 2.0 hotmetal + extensions//","-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//","-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//","-//spyglass//dtd html 2.0 extended//","-//sun microsystems corp.//dtd hotjava html//","-//sun microsystems corp.//dtd hotjava strict html//","-//w3c//dtd html 3 1995-03-24//","-//w3c//dtd html 3.2 draft//","-//w3c//dtd html 3.2 final//","-//w3c//dtd html 3.2//","-//w3c//dtd html 3.2s draft//","-//w3c//dtd html 4.0 frameset//","-//w3c//dtd html 4.0 transitional//","-//w3c//dtd html experimental 19960712//","-//w3c//dtd html experimental 970421//","-//w3c//dtd w3 html//","-//w3o//dtd w3 html 3.0//","-//webtechs//dtd mozilla html 2.0//","-//webtechs//dtd mozilla html//"],Ni=[...Ci,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"],bi=new Set(["-//w3o//dtd w3 html strict 3.0//en//","-/w3c/dtd html 4.0 transitional/en","html"]),Ii=["-//w3c//dtd xhtml 1.0 frameset//","-//w3c//dtd xhtml 1.0 transitional//"],Oi=[...Ii,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"];function ki(e,t){return t.some(t=>e.startsWith(t))}const Li={TEXT_HTML:"text/html",APPLICATION_XML:"application/xhtml+xml"},Di="definitionurl",Ri="definitionURL",wi=new Map(["attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map(e=>[e.toLowerCase(),e])),xi=new Map([["xlink:actuate",{prefix:"xlink",name:"actuate",namespace:Mr.XLINK}],["xlink:arcrole",{prefix:"xlink",name:"arcrole",namespace:Mr.XLINK}],["xlink:href",{prefix:"xlink",name:"href",namespace:Mr.XLINK}],["xlink:role",{prefix:"xlink",name:"role",namespace:Mr.XLINK}],["xlink:show",{prefix:"xlink",name:"show",namespace:Mr.XLINK}],["xlink:title",{prefix:"xlink",name:"title",namespace:Mr.XLINK}],["xlink:type",{prefix:"xlink",name:"type",namespace:Mr.XLINK}],["xml:base",{prefix:"xml",name:"base",namespace:Mr.XML}],["xml:lang",{prefix:"xml",name:"lang",namespace:Mr.XML}],["xml:space",{prefix:"xml",name:"space",namespace:Mr.XML}],["xmlns",{prefix:"",name:"xmlns",namespace:Mr.XMLNS}],["xmlns:xlink",{prefix:"xmlns",name:"xlink",namespace:Mr.XMLNS}]]),Mi=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map(e=>[e.toLowerCase(),e])),Pi=new Set([Ur.B,Ur.BIG,Ur.BLOCKQUOTE,Ur.BODY,Ur.BR,Ur.CENTER,Ur.CODE,Ur.DD,Ur.DIV,Ur.DL,Ur.DT,Ur.EM,Ur.EMBED,Ur.H1,Ur.H2,Ur.H3,Ur.H4,Ur.H5,Ur.H6,Ur.HEAD,Ur.HR,Ur.I,Ur.IMG,Ur.LI,Ur.LISTING,Ur.MENU,Ur.META,Ur.NOBR,Ur.OL,Ur.P,Ur.PRE,Ur.RUBY,Ur.S,Ur.SMALL,Ur.SPAN,Ur.STRONG,Ur.STRIKE,Ur.SUB,Ur.SUP,Ur.TABLE,Ur.TT,Ur.U,Ur.UL,Ur.VAR]);function Bi(e){for(let t=0;tthis.treeAdapter.isDocumentTypeNode(e));t&&this.treeAdapter.setNodeSourceCodeLocation(t,e.location)}}_attachElementToTree(e,t){if(this.options.sourceCodeLocationInfo&&(t=t&&{...t,startTag:t},this.treeAdapter.setNodeSourceCodeLocation(e,t)),this._shouldFosterParentOnInsertion())this._fosterParentElement(e);else{const t=this.openElements.currentTmplContentOrNode;this.treeAdapter.appendChild(t,e)}}_appendElement(e,t){t=this.treeAdapter.createElement(e.tagName,t,e.attrs),this._attachElementToTree(t,e.location)}_insertElement(e,t){t=this.treeAdapter.createElement(e.tagName,t,e.attrs),this._attachElementToTree(t,e.location),this.openElements.push(t,e.tagID)}_insertFakeElement(e,t){e=this.treeAdapter.createElement(e,Mr.HTML,[]),this._attachElementToTree(e,null),this.openElements.push(e,t)}_insertTemplate(e){var t=this.treeAdapter.createElement(e.tagName,Mr.HTML,e.attrs),n=this.treeAdapter.createDocumentFragment();this.treeAdapter.setTemplateContent(t,n),this._attachElementToTree(t,e.location),this.openElements.push(t,e.tagID),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,null)}_insertFakeRootElement(){var e=this.treeAdapter.createElement(Fr.HTML,Mr.HTML,[]);this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(e,null),this.treeAdapter.appendChild(this.openElements.current,e),this.openElements.push(e,Ur.HTML)}_appendCommentNode(e,t){var n=this.treeAdapter.createCommentNode(e.data);this.treeAdapter.appendChild(t,n),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,e.location)}_insertCharacters(e){let t,n;if(this._shouldFosterParentOnInsertion()?({parent:t,beforeElement:n}=this._findFosterParentingLocation(),n?this.treeAdapter.insertTextBefore(t,e.chars,n):this.treeAdapter.insertText(t,e.chars)):(t=this.openElements.currentTmplContentOrNode,this.treeAdapter.insertText(t,e.chars)),e.location){var r=this.treeAdapter.getChildNodes(t),i=n?r.lastIndexOf(n):r.length,i=r[i-1];if(this.treeAdapter.getNodeSourceCodeLocation(i)){const{endLine:t,endCol:n,endOffset:r}=e.location;this.treeAdapter.updateNodeSourceCodeLocation(i,{endLine:t,endCol:n,endOffset:r})}else this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(i,e.location)}}_adoptNodes(e,t){for(let n=this.treeAdapter.getFirstChild(e);n;n=this.treeAdapter.getFirstChild(e))this.treeAdapter.detachNode(n),this.treeAdapter.appendChild(t,n)}_setEndLocation(e,t){var n,r;this.treeAdapter.getNodeSourceCodeLocation(e)&&t.location&&(n=t.location,r=this.treeAdapter.getTagName(e),r=t.type===Tr.END_TAG&&r===t.tagName?{endTag:{...n},endLine:n.endLine,endCol:n.endCol,endOffset:n.endOffset}:{endLine:n.startLine,endCol:n.startCol,endOffset:n.startOffset},this.treeAdapter.updateNodeSourceCodeLocation(e,r))}shouldProcessStartTagTokenInForeignContent(e){if(!this.currentNotInHTML)return!1;let t,n;return 0===this.openElements.stackTop&&this.fragmentContext?(t=this.fragmentContext,n=this.fragmentContextID):{current:t,currentTagId:n}=this.openElements,(e.tagID!==Ur.SVG||this.treeAdapter.getTagName(t)!==Fr.ANNOTATION_XML||this.treeAdapter.getNamespaceURI(t)!==Mr.MATHML)&&(this.tokenizer.inForeignNode||(e.tagID===Ur.MGLYPH||e.tagID===Ur.MALIGNMARK)&&!this._isIntegrationPoint(n,t,Mr.HTML))}_processToken(e){switch(e.type){case Tr.CHARACTER:this.onCharacter(e);break;case Tr.NULL_CHARACTER:this.onNullCharacter(e);break;case Tr.COMMENT:this.onComment(e);break;case Tr.DOCTYPE:this.onDoctype(e);break;case Tr.START_TAG:this._processStartTag(e);break;case Tr.END_TAG:this.onEndTag(e);break;case Tr.EOF:this.onEof(e);break;case Tr.WHITESPACE_CHARACTER:this.onWhitespaceCharacter(e)}}_isIntegrationPoint(e,t,n){return Hi(e,this.treeAdapter.getNamespaceURI(t),this.treeAdapter.getAttrList(t),n)}_reconstructActiveFormattingElements(){const e=this.activeFormattingElements.entries.length;if(e){var t=this.activeFormattingElements.entries.findIndex(e=>e.type===_i.Marker||this.openElements.contains(e.element));for(let n=t<0?e-1:t-1;0<=n;n--){const e=this.activeFormattingElements.entries[n];this._insertElement(e.token,this.treeAdapter.getNamespaceURI(e.element)),e.element=this.openElements.current}}}_closeTableCell(){this.openElements.generateImpliedEndTags(),this.openElements.popUntilTableCellPopped(),this.activeFormattingElements.clearToLastMarker(),this.insertionMode=Yi.IN_ROW}_closePElement(){this.openElements.generateImpliedEndTagsWithExclusion(Ur.P),this.openElements.popUntilTagNamePopped(Ur.P)}_resetInsertionMode(){for(let e=this.openElements.stackTop;0<=e;e--)switch(0===e&&this.fragmentContext?this.fragmentContextID:this.openElements.tagIDs[e]){case Ur.TR:return void(this.insertionMode=Yi.IN_ROW);case Ur.TBODY:case Ur.THEAD:case Ur.TFOOT:return void(this.insertionMode=Yi.IN_TABLE_BODY);case Ur.CAPTION:return void(this.insertionMode=Yi.IN_CAPTION);case Ur.COLGROUP:return void(this.insertionMode=Yi.IN_COLUMN_GROUP);case Ur.TABLE:return void(this.insertionMode=Yi.IN_TABLE);case Ur.BODY:return void(this.insertionMode=Yi.IN_BODY);case Ur.FRAMESET:return void(this.insertionMode=Yi.IN_FRAMESET);case Ur.SELECT:return void this._resetInsertionModeForSelect(e);case Ur.TEMPLATE:return void(this.insertionMode=this.tmplInsertionModeStack[0]);case Ur.HTML:return void(this.insertionMode=this.headElement?Yi.AFTER_HEAD:Yi.BEFORE_HEAD);case Ur.TD:case Ur.TH:if(0e===Pr.COLOR||e===Pr.SIZE||e===Pr.FACE)||Pi.has(t)}(t)?(n=e._getAdjustedCurrentElement(),(n=e.treeAdapter.getNamespaceURI(n))===Mr.MATHML?Bi(t):n===Mr.SVG&&(function(e){var t=Mi.get(e.tagName);null!=t&&(e.tagName=t,e.tagID=qr(e.tagName))}(t),Fi(t)),Ui(t),t.selfClosing?e._appendElement(t,n):e._insertElement(t,n),t.ackSelfClosing=!0):(qs(e),e._startTagOutsideForeignContent(t))}(this,e):this._startTagOutsideForeignContent(e)}_startTagOutsideForeignContent(e){switch(this.insertionMode){case Yi.INITIAL:is(this,e);break;case Yi.BEFORE_HTML:!function(e,t){t.tagID===Ur.HTML?(e._insertElement(t,Mr.HTML),e.insertionMode=Yi.BEFORE_HEAD):ss(e,t)}(this,e);break;case Yi.BEFORE_HEAD:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.HEAD:e._insertElement(t,Mr.HTML),e.headElement=e.openElements.current,e.insertionMode=Yi.IN_HEAD;break;default:as(e,t)}}(this,e);break;case Yi.IN_HEAD:os(this,e);break;case Yi.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.BASEFONT:case Ur.BGSOUND:case Ur.HEAD:case Ur.LINK:case Ur.META:case Ur.NOFRAMES:case Ur.STYLE:os(e,t);break;case Ur.NOSCRIPT:e._err(t,_r.nestedNoscriptInHead);break;default:ls(e,t)}}(this,e);break;case Yi.AFTER_HEAD:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.BODY:e._insertElement(t,Mr.HTML),e.framesetOk=!1,e.insertionMode=Yi.IN_BODY;break;case Ur.FRAMESET:e._insertElement(t,Mr.HTML),e.insertionMode=Yi.IN_FRAMESET;break;case Ur.BASE:case Ur.BASEFONT:case Ur.BGSOUND:case Ur.LINK:case Ur.META:case Ur.NOFRAMES:case Ur.SCRIPT:case Ur.STYLE:case Ur.TEMPLATE:case Ur.TITLE:e._err(t,_r.abandonedHeadElementChild),e.openElements.push(e.headElement,Ur.HEAD),os(e,t),e.openElements.remove(e.headElement);break;case Ur.HEAD:e._err(t,_r.misplacedStartTagForHeadElement);break;default:hs(e,t)}}(this,e);break;case Yi.IN_BODY:As(this,e);break;case Yi.IN_TABLE:Cs(this,e);break;case Yi.IN_TABLE_TEXT:ks(this,e);break;case Yi.IN_CAPTION:!function(e,t){var n=t.tagID;Ls.has(n)?e.openElements.hasInTableScope(Ur.CAPTION)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Ur.CAPTION),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Yi.IN_TABLE,Cs(e,t)):As(e,t)}(this,e);break;case Yi.IN_COLUMN_GROUP:Ds(this,e);break;case Yi.IN_TABLE_BODY:ws(this,e);break;case Yi.IN_ROW:Ms(this,e);break;case Yi.IN_CELL:!function(e,t){var n=t.tagID;Ls.has(n)?(e.openElements.hasInTableScope(Ur.TD)||e.openElements.hasInTableScope(Ur.TH))&&(e._closeTableCell(),Ms(e,t)):As(e,t)}(this,e);break;case Yi.IN_SELECT:Bs(this,e);break;case Yi.IN_SELECT_IN_TABLE:!function(e,t){var n=t.tagID;n===Ur.CAPTION||n===Ur.TABLE||n===Ur.TBODY||n===Ur.TFOOT||n===Ur.THEAD||n===Ur.TR||n===Ur.TD||n===Ur.TH?(e.openElements.popUntilTagNamePopped(Ur.SELECT),e._resetInsertionMode(),e._processStartTag(t)):Bs(e,t)}(this,e);break;case Yi.IN_TEMPLATE:!function(e,t){switch(t.tagID){case Ur.BASE:case Ur.BASEFONT:case Ur.BGSOUND:case Ur.LINK:case Ur.META:case Ur.NOFRAMES:case Ur.SCRIPT:case Ur.STYLE:case Ur.TEMPLATE:case Ur.TITLE:os(e,t);break;case Ur.CAPTION:case Ur.COLGROUP:case Ur.TBODY:case Ur.TFOOT:case Ur.THEAD:e.tmplInsertionModeStack[0]=Yi.IN_TABLE,e.insertionMode=Yi.IN_TABLE,Cs(e,t);break;case Ur.COL:e.tmplInsertionModeStack[0]=Yi.IN_COLUMN_GROUP,e.insertionMode=Yi.IN_COLUMN_GROUP,Ds(e,t);break;case Ur.TR:e.tmplInsertionModeStack[0]=Yi.IN_TABLE_BODY,e.insertionMode=Yi.IN_TABLE_BODY,ws(e,t);break;case Ur.TD:case Ur.TH:e.tmplInsertionModeStack[0]=Yi.IN_ROW,e.insertionMode=Yi.IN_ROW,Ms(e,t);break;default:e.tmplInsertionModeStack[0]=Yi.IN_BODY,e.insertionMode=Yi.IN_BODY,As(e,t)}}(this,e);break;case Yi.AFTER_BODY:!function(e,t){(t.tagID===Ur.HTML?As:Gs)(e,t)}(this,e);break;case Yi.IN_FRAMESET:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.FRAMESET:e._insertElement(t,Mr.HTML);break;case Ur.FRAME:e._appendElement(t,Mr.HTML),t.ackSelfClosing=!0;break;case Ur.NOFRAMES:os(e,t)}}(this,e);break;case Yi.AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.NOFRAMES:os(e,t)}}(this,e);break;case Yi.AFTER_AFTER_BODY:!function(e,t){(t.tagID===Ur.HTML?As:js)(e,t)}(this,e);break;case Yi.AFTER_AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.NOFRAMES:os(e,t)}}(this,e)}}onEndTag(e){this.skipNextNewLine=!1,this.currentToken=e,this.currentNotInHTML?function(e,t){if(t.tagID===Ur.P||t.tagID===Ur.BR)return qs(e),e._endTagOutsideForeignContent(t);for(let n=e.openElements.stackTop;0=qi;!n||o?(o&&e.activeFormattingElements.removeEntry(n),e.openElements.remove(a)):(a=Zi(e,n),r===t&&(e.activeFormattingElements.bookmark=n),e.treeAdapter.detachNode(r),e.treeAdapter.appendChild(a,r),r=a)}return r}function Zi(e,t){var n=e.treeAdapter.getNamespaceURI(t.element),n=e.treeAdapter.createElement(t.token.tagName,n,t.token.attrs);return e.openElements.replace(t.element,n),t.element=n}function Ji(e,t,n){var i,r=qr(e.treeAdapter.getTagName(t));e._isElementCausesFosterParenting(r)?e._fosterParentElement(n):(i=e.treeAdapter.getNamespaceURI(t),r===Ur.TEMPLATE&&i===Mr.HTML&&(t=e.treeAdapter.getTemplateContent(t)),e.treeAdapter.appendChild(t,n))}function es(e,t,n){var r=e.treeAdapter.getNamespaceURI(n.element),i=n.token,r=e.treeAdapter.createElement(i.tagName,r,i.attrs);e._adoptNodes(t,r),e.treeAdapter.appendChild(t,r),e.activeFormattingElements.insertElementAfterBookmark(r,i),e.activeFormattingElements.removeEntry(n),e.openElements.remove(n.element),e.openElements.insertAfter(t,r,i.tagID)}function ts(e,t){for(let n=0;n=n;r--)e._setEndLocation(e.openElements.items[r],t);if(!e.fragmentContext&&0<=e.openElements.stackTop){const n=e.openElements.items[0],r=e.treeAdapter.getNodeSourceCodeLocation(n);if(r&&!r.endTag&&(e._setEndLocation(n,t),1<=e.openElements.stackTop)){const n=e.openElements.items[1],r=e.treeAdapter.getNodeSourceCodeLocation(n);r&&!r.endTag&&e._setEndLocation(n,t)}}}}function is(e,t){e._err(t,_r.missingDoctype,!0),e.treeAdapter.setDocumentMode(e.document,Br.QUIRKS),e.insertionMode=Yi.BEFORE_HTML,e._processToken(t)}function ss(e,t){e._insertFakeRootElement(),e.insertionMode=Yi.BEFORE_HEAD,e._processToken(t)}function as(e,t){e._insertFakeElement(Fr.HEAD,Ur.HEAD),e.headElement=e.openElements.current,e.insertionMode=Yi.IN_HEAD,e._processToken(t)}function os(e,t){switch(t.tagID){case Ur.HTML:As(e,t);break;case Ur.BASE:case Ur.BASEFONT:case Ur.BGSOUND:case Ur.LINK:case Ur.META:e._appendElement(t,Mr.HTML),t.ackSelfClosing=!0;break;case Ur.TITLE:e._switchToTextParsing(t,zr.RCDATA);break;case Ur.NOSCRIPT:e.options.scriptingEnabled?e._switchToTextParsing(t,zr.RAWTEXT):(e._insertElement(t,Mr.HTML),e.insertionMode=Yi.IN_HEAD_NO_SCRIPT);break;case Ur.NOFRAMES:case Ur.STYLE:e._switchToTextParsing(t,zr.RAWTEXT);break;case Ur.SCRIPT:e._switchToTextParsing(t,zr.SCRIPT_DATA);break;case Ur.TEMPLATE:e._insertTemplate(t),e.activeFormattingElements.insertMarker(),e.framesetOk=!1,e.insertionMode=Yi.IN_TEMPLATE,e.tmplInsertionModeStack.unshift(Yi.IN_TEMPLATE);break;case Ur.HEAD:e._err(t,_r.misplacedStartTagForHeadElement);break;default:us(e,t)}}function cs(e,t){0=t&&e.openElements.shortenToLength(t);break}if(e._isSpecialElement(i,s))break}}function vs(e,t){switch(t.tagID){case Ur.A:case Ur.B:case Ur.I:case Ur.S:case Ur.U:case Ur.EM:case Ur.TT:case Ur.BIG:case Ur.CODE:case Ur.FONT:case Ur.NOBR:case Ur.SMALL:case Ur.STRIKE:case Ur.STRONG:ts(e,t);break;case Ur.P:!function(e){e.openElements.hasInButtonScope(Ur.P)||e._insertFakeElement(Fr.P,Ur.P),e._closePElement()}(e);break;case Ur.DL:case Ur.UL:case Ur.OL:case Ur.DIR:case Ur.DIV:case Ur.NAV:case Ur.PRE:case Ur.MAIN:case Ur.MENU:case Ur.ASIDE:case Ur.BUTTON:case Ur.CENTER:case Ur.FIGURE:case Ur.FOOTER:case Ur.HEADER:case Ur.HGROUP:case Ur.DIALOG:case Ur.ADDRESS:case Ur.ARTICLE:case Ur.DETAILS:case Ur.SECTION:case Ur.SUMMARY:case Ur.LISTING:case Ur.FIELDSET:case Ur.BLOCKQUOTE:case Ur.FIGCAPTION:!function(e,t){t=t.tagID,e.openElements.hasInScope(t)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(t))}(e,t);break;case Ur.LI:!function(e){e.openElements.hasInListItemScope(Ur.LI)&&(e.openElements.generateImpliedEndTagsWithExclusion(Ur.LI),e.openElements.popUntilTagNamePopped(Ur.LI))}(e);break;case Ur.DD:case Ur.DT:!function(e,t){t=t.tagID,e.openElements.hasInScope(t)&&(e.openElements.generateImpliedEndTagsWithExclusion(t),e.openElements.popUntilTagNamePopped(t))}(e,t);break;case Ur.H1:case Ur.H2:case Ur.H3:case Ur.H4:case Ur.H5:case Ur.H6:!function(e){e.openElements.hasNumberedHeaderInScope()&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilNumberedHeaderPopped())}(e);break;case Ur.BR:!function(e){e._reconstructActiveFormattingElements(),e._insertFakeElement(Fr.BR,Ur.BR),e.openElements.pop(),e.framesetOk=!1}(e);break;case Ur.BODY:!function(e,t){var n;e.openElements.hasInScope(Ur.BODY)&&(e.insertionMode=Yi.AFTER_BODY,e.options.sourceCodeLocationInfo)&&((n=e.openElements.tryPeekProperlyNestedBodyElement())&&e._setEndLocation(n,t))}(e,t);break;case Ur.HTML:!function(e,t){e.openElements.hasInScope(Ur.BODY)&&(e.insertionMode=Yi.AFTER_BODY,Hs(e,t))}(e,t);break;case Ur.FORM:!function(e){var t=0$\x80-\uFFFF]/g;var n=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]);function r(e){for(var i="",s=0;null!==(r=t.xmlReplacer.exec(e));)var r=r.index,o=e.charCodeAt(r),c=n.get(o),s=void 0!==c?(i+=e.substring(s,r)+c,r+1):(i+="".concat(e.substring(s,r),"&#x").concat((0,t.getCodePoint)(e,r).toString(16),";"),t.xmlReplacer.lastIndex+=Number(55296==(64512&o)));return i+e.substr(s)}function i(e,t){return function(n){for(var r,i=0,s="";r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}t.getCodePoint=null!=String.prototype.codePointAt?function(e,t){return e.codePointAt(t)}:function(e,t){return 55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t)},t.encodeXML=r,t.escape=r,t.escapeUTF8=i(/[&<>'"]/g,n),t.escapeAttribute=i(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),t.escapeText=i(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]]))}),Ks=(yr(Ys),Ys.escapeText),Ws=Ys.escapeAttribute;Ys.escapeUTF8,Ys.escape,Ys.encodeXML,Ys.getCodePoint,Ys.xmlReplacer;const Vs=new Set([Fr.AREA,Fr.BASE,Fr.BASEFONT,Fr.BGSOUND,Fr.BR,Fr.COL,Fr.EMBED,Fr.FRAME,Fr.HR,Fr.IMG,Fr.INPUT,Fr.KEYGEN,Fr.LINK,Fr.META,Fr.PARAM,Fr.SOURCE,Fr.TRACK,Fr.WBR]),$s={treeAdapter:gi,scriptingEnabled:!0};function Qs(e,t){return zs(e,{...$s,...t})}function zs(e,t){return t.treeAdapter.isElementNode(e)?function(e,t){var n=t.treeAdapter.getTagName(e);return`<${n}${function(e,{treeAdapter:t}){let n="";for(const r of t.getAttrList(e)){if(n+=" ",r.namespace)switch(r.namespace){case Mr.XML:n+="xml:"+r.name;break;case Mr.XMLNS:"xmlns"!==r.name&&(n+="xmlns:"),n+=r.name;break;case Mr.XLINK:n+="xlink:"+r.name;break;default:n+=r.prefix+":"+r.name}else n+=r.name;n+=`="${Ws(r.value)}"`}return n}(e,t)}>`+(function(e,t){return t.treeAdapter.isElementNode(e)&&t.treeAdapter.getNamespaceURI(e)===Mr.HTML&&Vs.has(t.treeAdapter.getTagName(e))}(e,t)?"":function(e,t){let n="";var e=t.treeAdapter.isElementNode(e)&&t.treeAdapter.getTagName(e)===Fr.TEMPLATE&&t.treeAdapter.getNamespaceURI(e)===Mr.HTML?t.treeAdapter.getTemplateContent(e):e,i=t.treeAdapter.getChildNodes(e);if(i)for(const e of i)n+=zs(e,t);return n}(e,t)+``)}(e,t):t.treeAdapter.isTextNode(e)?function(e,t){var n=t.treeAdapter,r=n.getTextNodeContent(e),s=(e=n.getParentNode(e))&&n.isElementNode(e)&&n.getTagName(e);return s&&n.getNamespaceURI(e)===Mr.HTML&&(n=s,e=t.scriptingEnabled,Vr.has(n)||e&&n===Fr.NOSCRIPT)?r:Ks(r)}(e,t):t.treeAdapter.isCommentNode(e)?function(e,{treeAdapter:t}){return``}(e,t):t.treeAdapter.isDocumentTypeNode(e)?function(e,{treeAdapter:t}){return``}(e,t):""}function Xs(e){return new _(e)}function Zs(e){var t=e.includes('"')?"'":'"';return t+e+t}const Js={isCommentNode:b,isElementNode:S,isTextNode:N,createDocument(){var e=new v([]);return e["x-mode"]=Br.NO_QUIRKS,e},createDocumentFragment:()=>new v([]),createElement(e,t,n){var r=Object.create(null),i=Object.create(null),s=Object.create(null);for(let e=0;enew E(e),appendChild(e,t){var n=e.children[e.children.length-1];n&&((n.next=t).prev=n),e.children.push(t),t.parent=e},insertBefore(e,t,n){var r=e.children.indexOf(n),i=n.prev;i&&((i.next=t).prev=i),(n.prev=t).next=n,e.children.splice(r,0,t),t.parent=e},setTemplateContent(e,t){Js.appendChild(e,t)},getTemplateContent:e=>e.children[0],setDocumentType(e,t,n,r){var i=function(e,t,n){let r="!DOCTYPE ";return e&&(r+=e),t?r+=" PUBLIC "+Zs(t):n&&(r+=" SYSTEM"),n&&(r+=" "+Zs(n)),r}(t,n,r);let s=e.children.find(e=>I(e)&&"!doctype"===e.name);s?s.data=null!=i?i:null:(s=new T("!doctype",i),Js.appendChild(e,s)),s["x-name"]=null!=t?t:void 0,s["x-publicId"]=null!=n?n:void 0,s["x-systemId"]=null!=r?r:void 0},setDocumentMode(e,t){e["x-mode"]=t},getDocumentMode:e=>e["x-mode"],detachNode(e){var t,n,r;e.parent&&(t=e.parent.children.indexOf(e),{prev:n,next:r}=e,e.prev=null,e.next=null,n&&(n.next=r),r&&(r.prev=n),e.parent.children.splice(t,1),e.parent=null)},insertText(e,t){var n=e.children[e.children.length-1];n&&N(n)?n.data+=t:Js.appendChild(e,Xs(t))},insertTextBefore(e,t,n){var r=e.children[e.children.indexOf(n)-1];r&&N(r)?r.data+=t:Js.insertBefore(e,Xs(t),n)},adoptAttributes(e,t){for(let n=0;ne.children[0],getChildNodes:e=>e.children,getParentNode:e=>e.parent,getAttrList:e=>e.attributes,getTagName:e=>e.name,getNamespaceURI:e=>e.namespace,getTextNodeContent:e=>e.data,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName(e){return null!=(e=e["x-name"])?e:""},getDocumentTypeNodePublicId(e){return null!=(e=e["x-publicId"])?e:""},getDocumentTypeNodeSystemId(e){return null!=(e=e["x-systemId"])?e:""},isDocumentTypeNode:e=>I(e)&&"!doctype"===e.name,setNodeSourceCodeLocation(e,t){t&&(e.startIndex=t.startOffset,e.endIndex=t.endOffset),e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){null!=t.endOffset&&(e.endIndex=t.endOffset),e.sourceCodeLocation={...e.sourceCodeLocation,...t}}};var ea=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;i=na.Zero&&e<=na.Nine}!function(e){e[e.Tab=9]="Tab",e[e.NewLine=10]="NewLine",e[e.FormFeed=12]="FormFeed",e[e.CarriageReturn=13]="CarriageReturn",e[e.Space=32]="Space",e[e.ExclamationMark=33]="ExclamationMark",e[e.Number=35]="Number",e[e.Amp=38]="Amp",e[e.SingleQuote=39]="SingleQuote",e[e.DoubleQuote=34]="DoubleQuote",e[e.Dash=45]="Dash",e[e.Slash=47]="Slash",e[e.Zero=48]="Zero",e[e.Nine=57]="Nine",e[e.Semi=59]="Semi",e[e.Lt=60]="Lt",e[e.Eq=61]="Eq",e[e.Gt=62]="Gt",e[e.Questionmark=63]="Questionmark",e[e.UpperA=65]="UpperA",e[e.LowerA=97]="LowerA",e[e.UpperF=70]="UpperF",e[e.LowerF=102]="LowerF",e[e.UpperZ=90]="UpperZ",e[e.LowerZ=122]="LowerZ",e[e.LowerX=120]="LowerX",e[e.OpeningSquareBracket=91]="OpeningSquareBracket"}(na=na||{}),function(e){e[e.Text=1]="Text",e[e.BeforeTagName=2]="BeforeTagName",e[e.InTagName=3]="InTagName",e[e.InSelfClosingTag=4]="InSelfClosingTag",e[e.BeforeClosingTagName=5]="BeforeClosingTagName",e[e.InClosingTagName=6]="InClosingTagName",e[e.AfterClosingTagName=7]="AfterClosingTagName",e[e.BeforeAttributeName=8]="BeforeAttributeName",e[e.InAttributeName=9]="InAttributeName",e[e.AfterAttributeName=10]="AfterAttributeName",e[e.BeforeAttributeValue=11]="BeforeAttributeValue",e[e.InAttributeValueDq=12]="InAttributeValueDq",e[e.InAttributeValueSq=13]="InAttributeValueSq",e[e.InAttributeValueNq=14]="InAttributeValueNq",e[e.BeforeDeclaration=15]="BeforeDeclaration",e[e.InDeclaration=16]="InDeclaration",e[e.InProcessingInstruction=17]="InProcessingInstruction",e[e.BeforeComment=18]="BeforeComment",e[e.CDATASequence=19]="CDATASequence",e[e.InSpecialComment=20]="InSpecialComment",e[e.InCommentLike=21]="InCommentLike",e[e.BeforeSpecialS=22]="BeforeSpecialS",e[e.SpecialStartSequence=23]="SpecialStartSequence",e[e.InSpecialTag=24]="InSpecialTag",e[e.BeforeEntity=25]="BeforeEntity",e[e.BeforeNumericEntity=26]="BeforeNumericEntity",e[e.InNamedEntity=27]="InNamedEntity",e[e.InNumericEntity=28]="InNumericEntity",e[e.InHexEntity=29]="InHexEntity"}(ra=ra||{}),function(e){e[e.NoValue=0]="NoValue",e[e.Unquoted=1]="Unquoted",e[e.Single=2]="Single",e[e.Double=3]="Double"}(ia=ia||{});const ua={Cdata:new Uint8Array([67,68,65,84,65,91]),CdataEnd:new Uint8Array([93,93,62]),CommentEnd:new Uint8Array([45,45,62]),ScriptEnd:new Uint8Array([60,47,115,99,114,105,112,116]),StyleEnd:new Uint8Array([60,47,115,116,121,108,101]),TitleEnd:new Uint8Array([60,47,116,105,116,108,101])};class la{constructor({xmlMode:e=!1,decodeEntities:t=!0},n){this.cbs=n,this.state=ra.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=ra.Text,this.isSpecial=!1,this.running=!0,this.offset=0,this.currentSequence=void 0,this.sequenceIndex=0,this.trieIndex=0,this.trieCurrent=0,this.entityResult=0,this.entityExcess=0,this.xmlMode=e,this.decodeEntities=t,this.entityTrie=e?Hr:Gr}reset(){this.state=ra.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=ra.Text,this.currentSequence=void 0,this.running=!0,this.offset=0}write(e){this.offset+=this.buffer.length,this.buffer=e,this.parse()}end(){this.running&&this.finish()}pause(){this.running=!1}resume(){this.running=!0,this.indexthis.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=ra.BeforeTagName,this.sectionStart=this.index):this.decodeEntities&&e===na.Amp&&(this.state=ra.BeforeEntity)}stateSpecialStartSequence(e){var t=this.sequenceIndex===this.currentSequence.length;if(t?oa(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t)return void this.sequenceIndex++}else this.isSpecial=!1;this.sequenceIndex=0,this.state=ra.InTagName,this.stateInTagName(e)}stateInSpecialTag(e){if(this.sequenceIndex===this.currentSequence.length){if(e===na.Gt||aa(e)){var t=this.index-this.currentSequence.length;if(this.sectionStart=na.LowerA&&e<=na.LowerZ||e>=na.UpperA&&e<=na.UpperZ}(e)}startSpecial(e,t){this.isSpecial=!0,this.currentSequence=e,this.sequenceIndex=t,this.state=ra.SpecialStartSequence}stateBeforeTagName(e){var t;e===na.ExclamationMark?(this.state=ra.BeforeDeclaration,this.sectionStart=this.index+1):e===na.Questionmark?(this.state=ra.InProcessingInstruction,this.sectionStart=this.index+1):this.isTagStartChar(e)?(t=32|e,this.sectionStart=this.index,this.xmlMode||t!==ua.TitleEnd[2]?this.state=this.xmlMode||t!==ua.ScriptEnd[2]?ra.InTagName:ra.BeforeSpecialS:this.startSpecial(ua.TitleEnd,3)):e===na.Slash?this.state=ra.BeforeClosingTagName:(this.state=ra.Text,this.stateText(e))}stateInTagName(e){oa(e)&&(this.cbs.onopentagname(this.sectionStart,this.index),this.sectionStart=-1,this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateBeforeClosingTagName(e){aa(e)||(e===na.Gt?this.state=ra.Text:(this.state=this.isTagStartChar(e)?ra.InClosingTagName:ra.InSpecialComment,this.sectionStart=this.index))}stateInClosingTagName(e){e!==na.Gt&&!aa(e)||(this.cbs.onclosetag(this.sectionStart,this.index),this.sectionStart=-1,this.state=ra.AfterClosingTagName,this.stateAfterClosingTagName(e))}stateAfterClosingTagName(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.state=ra.Text,this.baseState=ra.Text,this.sectionStart=this.index+1)}stateBeforeAttributeName(e){e===na.Gt?(this.cbs.onopentagend(this.index),this.isSpecial?(this.state=ra.InSpecialTag,this.sequenceIndex=0):this.state=ra.Text,this.baseState=this.state,this.sectionStart=this.index+1):e===na.Slash?this.state=ra.InSelfClosingTag:aa(e)||(this.state=ra.InAttributeName,this.sectionStart=this.index)}stateInSelfClosingTag(e){e===na.Gt?(this.cbs.onselfclosingtag(this.index),this.state=ra.Text,this.baseState=ra.Text,this.sectionStart=this.index+1,this.isSpecial=!1):aa(e)||(this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateInAttributeName(e){e!==na.Eq&&!oa(e)||(this.cbs.onattribname(this.sectionStart,this.index),this.sectionStart=-1,this.state=ra.AfterAttributeName,this.stateAfterAttributeName(e))}stateAfterAttributeName(e){e===na.Eq?this.state=ra.BeforeAttributeValue:e===na.Slash||e===na.Gt?(this.cbs.onattribend(ia.NoValue,this.index),this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e)):aa(e)||(this.cbs.onattribend(ia.NoValue,this.index),this.state=ra.InAttributeName,this.sectionStart=this.index)}stateBeforeAttributeValue(e){e===na.DoubleQuote?(this.state=ra.InAttributeValueDq,this.sectionStart=this.index+1):e===na.SingleQuote?(this.state=ra.InAttributeValueSq,this.sectionStart=this.index+1):aa(e)||(this.sectionStart=this.index,this.state=ra.InAttributeValueNq,this.stateInAttributeValueNoQuotes(e))}handleInAttributeValue(e,t){e===t||!this.decodeEntities&&this.fastForwardTo(t)?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(t===na.DoubleQuote?ia.Double:ia.Single,this.index),this.state=ra.BeforeAttributeName):this.decodeEntities&&e===na.Amp&&(this.baseState=this.state,this.state=ra.BeforeEntity)}stateInAttributeValueDoubleQuotes(e){this.handleInAttributeValue(e,na.DoubleQuote)}stateInAttributeValueSingleQuotes(e){this.handleInAttributeValue(e,na.SingleQuote)}stateInAttributeValueNoQuotes(e){aa(e)||e===na.Gt?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(ia.Unquoted,this.index),this.state=ra.BeforeAttributeName,this.stateBeforeAttributeName(e)):this.decodeEntities&&e===na.Amp&&(this.baseState=this.state,this.state=ra.BeforeEntity)}stateBeforeDeclaration(e){e===na.OpeningSquareBracket?(this.state=ra.CDATASequence,this.sequenceIndex=0):this.state=e===na.Dash?ra.BeforeComment:ra.InDeclaration}stateInDeclaration(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.cbs.ondeclaration(this.sectionStart,this.index),this.state=ra.Text,this.sectionStart=this.index+1)}stateInProcessingInstruction(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.cbs.onprocessinginstruction(this.sectionStart,this.index),this.state=ra.Text,this.sectionStart=this.index+1)}stateBeforeComment(e){e===na.Dash?(this.state=ra.InCommentLike,this.currentSequence=ua.CommentEnd,this.sequenceIndex=2,this.sectionStart=this.index+1):this.state=ra.InDeclaration}stateInSpecialComment(e){e!==na.Gt&&!this.fastForwardTo(na.Gt)||(this.cbs.oncomment(this.sectionStart,this.index,0),this.state=ra.Text,this.sectionStart=this.index+1)}stateBeforeSpecialS(e){var t=32|e;t===ua.ScriptEnd[3]?this.startSpecial(ua.ScriptEnd,4):t===ua.StyleEnd[3]?this.startSpecial(ua.StyleEnd,4):(this.state=ra.InTagName,this.stateInTagName(e))}stateBeforeEntity(e){this.entityExcess=1,this.entityResult=0,e===na.Number?this.state=ra.BeforeNumericEntity:e!==na.Amp&&(this.trieIndex=0,this.trieCurrent=this.entityTrie[0],this.state=ra.InNamedEntity,this.stateInNamedEntity(e))}stateInNamedEntity(e){if(this.entityExcess+=1,this.trieIndex=Dr(this.entityTrie,this.trieCurrent,this.trieIndex+1,e),this.trieIndex<0)this.emitNamedEntity(),this.index--;else{this.trieCurrent=this.entityTrie[this.trieIndex];var t=this.trieCurrent&Rr.VALUE_LENGTH;if(t)if(t=(t>>14)-1,this.allowLegacyEntity()||e===na.Semi){const e=this.index-this.entityExcess+1;e>this.sectionStart&&this.emitPartial(this.sectionStart,e),this.entityResult=this.trieIndex,this.trieIndex+=t,this.entityExcess=0,this.sectionStart=this.index+1,0==t&&this.emitNamedEntity()}else this.trieIndex+=t}}emitNamedEntity(){if(this.state=this.baseState,0!==this.entityResult)switch((this.entityTrie[this.entityResult]&Rr.VALUE_LENGTH)>>14){case 1:this.emitCodePoint(this.entityTrie[this.entityResult]&~Rr.VALUE_LENGTH);break;case 2:this.emitCodePoint(this.entityTrie[this.entityResult+1]);break;case 3:this.emitCodePoint(this.entityTrie[this.entityResult+1]),this.emitCodePoint(this.entityTrie[this.entityResult+2])}}stateBeforeNumericEntity(e){(32|e)===na.LowerX?(this.entityExcess++,this.state=ra.InHexEntity):(this.state=ra.InNumericEntity,this.stateInNumericEntity(e))}emitNumericEntity(e){var t=this.index-this.entityExcess-1;2+t+Number(this.state===ra.InHexEntity)!==this.index&&(t>this.sectionStart&&this.emitPartial(this.sectionStart,t),this.sectionStart=this.index+Number(e),this.emitCodePoint(xr(this.entityResult))),this.state=this.baseState}stateInNumericEntity(e){e===na.Semi?this.emitNumericEntity(!0):ca(e)?(this.entityResult=10*this.entityResult+(e-na.Zero),this.entityExcess++):(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--)}stateInHexEntity(e){e===na.Semi?this.emitNumericEntity(!0):ca(e)?(this.entityResult=16*this.entityResult+(e-na.Zero),this.entityExcess++):function(e){return e>=na.UpperA&&e<=na.UpperF||e>=na.LowerA&&e<=na.LowerF}(e)?(this.entityResult=16*this.entityResult+((32|e)-na.LowerA+10),this.entityExcess++):(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--)}allowLegacyEntity(){return!this.xmlMode&&(this.baseState===ra.Text||this.baseState===ra.InSpecialTag)}cleanup(){this.running&&this.sectionStart!==this.index&&(this.state===ra.Text||this.state===ra.InSpecialTag&&0===this.sequenceIndex?(this.cbs.ontext(this.sectionStart,this.index),this.sectionStart=this.index):this.state!==ra.InAttributeValueDq&&this.state!==ra.InAttributeValueSq&&this.state!==ra.InAttributeValueNq||(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=this.index))}shouldContinue(){return this.index=this.buffers[0].length;)this.shiftBuffer();let n=this.buffers[0].slice(e-this.bufferOffset,t-this.bufferOffset);for(;t-this.bufferOffset>this.buffers[0].length;)this.shiftBuffer(),n+=this.buffers[0].slice(0,t-this.bufferOffset);return n}shiftBuffer(){this.bufferOffset+=this.buffers[0].length,this.writeIndex--,this.buffers.shift()}write(e){var t,n;this.ended?null!=(n=(t=this.cbs).onerror)&&n.call(t,new Error(".write() after done!")):(this.buffers.push(e),this.tokenizer.running&&(this.tokenizer.write(e),this.writeIndex++))}end(e){var t,n;this.ended?null!=(n=(t=this.cbs).onerror)&&n.call(t,new Error(".end() after done!")):(e&&this.write(e),this.ended=!0,this.tokenizer.end())}pause(){this.tokenizer.pause()}resume(){for(this.tokenizer.resume();this.tokenizer.running&&this.writeIndex(t=this._getPreferredOutput(t),a&&Array.isArray(t)?e=e.concat(t):e.push(t),e),[]):this._getPreferredOutput(e[0]):o?[]:void 0},Fa.prototype._getPreferredOutput=function(e){const t=this.currResultType;switch(t){case"all":{const t=Array.isArray(e.path)?e.path:Fa.toPathArray(e.path);return e.pointer=Fa.toPointer(t),e.path="string"==typeof e.path?e.path:Fa.toPathString(e.path),e}case"value":case"parent":case"parentProperty":return e[t];case"path":return Fa.toPathString(e[t]);case"pointer":return Fa.toPointer(e.path);default:throw new TypeError("Unknown result type")}},Fa.prototype._handleCallback=function(e,t,n){var r;t&&(r=this._getPreferredOutput(e),e.path="string"==typeof e.path?e.path:Fa.toPathString(e.path),t(r,n,e))},Fa.prototype._trace=function(e,t,n,r,i,s,a,o){let c;if(!e.length)return c={path:n,value:t,parent:r,parentProperty:i,hasArrExpr:a},this._handleCallback(c,s,"value"),c;const u=e[0],l=e.slice(1),h=[];function f(e){Array.isArray(e)?e.forEach(e=>{h.push(e)}):h.push(e)}if(("string"!=typeof u||o)&&t&&xa.call(t,u))f(this._trace(l,t[u],Ma(n,u),t,u,s,a));else if("*"===u)this._walk(t,e=>{f(this._trace(l,t[e],Ma(n,e),t,e,s,!0,!0))});else if(".."===u)f(this._trace(l,t,n,r,i,s,a)),this._walk(t,r=>{"object"==typeof t[r]&&f(this._trace(e.slice(),t[r],Ma(n,r),t,r,s,!0))});else{if("^"===u)return this._hasParentSelector=!0,{path:n.slice(0,-1),expr:l,isParentSelector:!0};if("~"===u)return c={path:Ma(n,u),value:i,parent:r,parentProperty:null},this._handleCallback(c,s,"property"),c;if("$"===u)f(this._trace(l,t,n,null,null,s,a));else if(/^(-?\d*):(-?\d*):?(\d*)$/u.test(u))f(this._slice(u,l,t,n,r,i,s));else if(0===u.indexOf("?(")){if(this.currPreventEval)throw new Error("Eval [?(expr)] prevented in JSONPath expression.");const e=u.replace(/^\?\((.*?)\)$/u,"$1");this._walk(t,a=>{this._eval(e,t[a],a,n,r,i)&&f(this._trace(l,t[a],Ma(n,a),t,a,s,!0))})}else if("("===u[0]){if(this.currPreventEval)throw new Error("Eval [(expr)] prevented in JSONPath expression.");f(this._trace(Pa(this._eval(u,t,n[n.length-1],n.slice(0,-1),r,i),l),t,n,r,i,s,a))}else if("@"===u[0]){let e=!1;const a=u.slice(1,-2);switch(a){case"scalar":t&&["object","function"].includes(typeof t)||(e=!0);break;case"boolean":case"string":case"undefined":case"function":typeof t===a&&(e=!0);break;case"integer":!Number.isFinite(t)||t%1||(e=!0);break;case"number":Number.isFinite(t)&&(e=!0);break;case"nonFinite":"number"!=typeof t||Number.isFinite(t)||(e=!0);break;case"object":t&&typeof t===a&&(e=!0);break;case"array":Array.isArray(t)&&(e=!0);break;case"other":e=this.currOtherTypeCallback(t,n,r,i);break;case"null":null===t&&(e=!0);break;default:throw new TypeError("Unknown value type "+a)}if(e)return c={path:n,value:t,parent:r,parentProperty:i},this._handleCallback(c,s,"value"),c}else if("`"===u[0]&&t&&xa.call(t,u.slice(1))){const e=u.slice(1);f(this._trace(l,t[e],Ma(n,e),t,e,s,a,!0))}else if(u.includes(",")){const e=u.split(",");for(const a of e)f(this._trace(Pa(a,l),t,n,r,i,s,!0))}else!o&&t&&xa.call(t,u)&&f(this._trace(l,t[u],Ma(n,u),t,u,s,a,!0))}if(this._hasParentSelector)for(let e=0;e{t(e)})},Fa.prototype._slice=function(e,t,n,r,i,s,a){if(Array.isArray(n)){var o=n.length,u=(e=e.split(":"))[2]&&Number.parseInt(e[2])||1,l=e[0]&&Number.parseInt(e[0])||0,h=e[1]&&Number.parseInt(e[1])||o,l=l<0?Math.max(0,l+o):Math.min(o,l),h=h<0?Math.max(0,h+o):Math.min(o,h);const f=[];for(let e=l;e{f.push(e)});return f}},Fa.prototype._eval=function(e,t,n,r,i,s){if(this.currSandbox._$_parentProperty=s,this.currSandbox._$_parent=i,this.currSandbox._$_property=n,this.currSandbox._$_root=this.json,this.currSandbox._$_v=t,(s=e.includes("@path"))&&(this.currSandbox._$_path=Fa.toPathString(r.concat([n]))),!Fa.cache[i="script:"+e]){let t=e.replace(/@parentProperty/gu,"_$_parentProperty").replace(/@parent/gu,"_$_parent").replace(/@property/gu,"_$_property").replace(/@root/gu,"_$_root").replace(/@([.\s)[])/gu,"_$_v$1");s&&(t=t.replace(/@path/gu,"_$_path")),Fa.cache[i]=new this.vm.Script(t)}try{return Fa.cache[i].runInNewContext(this.currSandbox)}catch(t){throw new Error("jsonPath: "+t.message+": "+e)}},Fa.cache={},Fa.toPathString=function(e){var t=e,n=t.length;let r="$";for(let e=1;e":">"},i=/[&"'<>]/g,s=e.exports={};function a(e,t){return n.hasOwnProperty.call(e,t)}function o(e){return r[e]}function c(e,t,n){var r,i,a,s,o;return e instanceof Error&&(e=(i=e).name+": "+i.message),Object.setPrototypeOf?(r=new Error(e),Object.setPrototypeOf(r,c.prototype)):(r=this,Object.defineProperty(r,"message",{enumerable:!1,writable:!0,value:e})),Object.defineProperty(r,"name",{value:"Template render error"}),Error.captureStackTrace&&Error.captureStackTrace(r,this.constructor),s=i?(a=Object.getOwnPropertyDescriptor(i,"stack"))&&(a.get||function(){return a.value})||function(){return i.stack}:(o=new Error(e).stack,function(){return o}),Object.defineProperty(r,"stack",{get:function(){return s.call(r)}}),Object.defineProperty(r,"cause",{value:i}),r.lineno=t,r.colno=n,r.firstUpdate=!0,r.Update=function(e){return e="("+(e||"unknown path")+")",this.firstUpdate&&(this.lineno&&this.colno?e+=" [Line "+this.lineno+", Column "+this.colno+"]":this.lineno&&(e+=" [Line "+this.lineno+"]")),e+="\n ",this.firstUpdate&&(e+=" "),this.message=e+(this.message||""),this.firstUpdate=!1,this},r}function u(e){return"[object Function]"===n.toString.call(e)}function l(e){return"[object Array]"===n.toString.call(e)}function h(e){return"[object String]"===n.toString.call(e)}function f(e){return"[object Object]"===n.toString.call(e)}function p(e){var t=function(e){return e?"string"==typeof e?e.split("."):[e]:[]}(e);return function(e){for(var n=e,r=0;rr&&(a.warned=!0,(r=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit")).name="MaxListenersExceededWarning",r.emitter=e,r.type=t,r.count=a.length,function(e){"function"==typeof console.warn?console.warn(e):console.log(e)}(r))):(a=s[t]=n,++e._eventsCount),e}function mo(e,t,n){var r=!1;function i(){e.removeListener(t,i),r||(r=!0,n.apply(e,arguments))}return i.listener=n,i}function _o(e){var t=this._events;if(t){if("function"==typeof(t=t[e]))return 1;if(t)return t.length}return 0}function Eo(e,t){for(var n=new Array(t);t--;)n[t]=e[t];return n}function To(){var e;ho.call(this),this.__emitError=(e=this,function(t){e.emit("error",t)})}function Ao(){return new To}lo.prototype=Object.create(null),(ho.EventEmitter=ho).usingDomains=!1,ho.prototype.domain=void 0,ho.prototype._events=void 0,ho.prototype._maxListeners=void 0,ho.defaultMaxListeners=10,ho.init=function(){this.domain=null,ho.usingDomains&&(void 0).active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new lo,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},ho.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},ho.prototype.getMaxListeners=function(){return fo(this)},ho.prototype.emit=function(e){var n,r,i,s,a,c="error"===e;if(a=this._events)c=c&&null==a.error;else if(!c)return!1;if(o=this.domain,c){if(c=arguments[1],o)return(c=c||new Error('Uncaught, unspecified "error" event')).domainEmitter=this,c.domain=o,c.domainThrown=!1,o.emit("error",c),!1;if(c instanceof Error)throw c;var o=new Error('Uncaught, unspecified "error" event. ('+c+")");throw o.context=c,o}if(!(n=a[e]))return!1;var l="function"==typeof n;switch(r=arguments.length){case 1:!function(e,n){if(l)e.call(n);else for(var r=e.length,i=Eo(e,r),s=0;s=","//","**"],u=r+this.current();switch(-1!==Ua.indexOf(c,u)&&(this.forward(),-1!==Ua.indexOf(c,(r=u)+this.current()))&&(r=u+this.current(),this.forward()),r){case"(":o=Ko;break;case")":o=Wo;break;case"[":o=Vo;break;case"]":o=$o;break;case"{":o=Qo;break;case"}":o=zo;break;case",":o=Zo;break;case":":o=Jo;break;case"~":o=ec;break;case"|":o=tc;break;default:o=Xo}return oc(o,r,t,n)}if((e=this._extractUntil(Mo+Po)).match(/^[-+]?[0-9]+$/))return"."===this.current()?(this.forward(),c=this._extract("0123456789"),oc(nc,e+"."+c,t,n)):oc("int",e,t,n);if(e.match(/^(true|false)$/))return oc(rc,e,t,n);if("none"===e)return oc(ic,e,t,n);if("null"===e)return oc(ic,e,t,n);if(e)return oc(sc,e,t,n);throw new Error("Unexpected value while parsing: "+e)}var h,f=this.tags.BLOCK_START.charAt(0)+this.tags.VARIABLE_START.charAt(0)+this.tags.COMMENT_START.charAt(0)+this.tags.COMMENT_END.charAt(0);if(this.isFinished())return null;if(e=(e=this._extractString(this.tags.BLOCK_START+"-"))||this._extractString(this.tags.BLOCK_START))return this.in_code=!0,oc(Ho,e,t,n);if(e=(e=this._extractString(this.tags.VARIABLE_START+"-"))||this._extractString(this.tags.VARIABLE_START))return this.in_code=!0,oc(jo,e,t,n);var e="",p=!1;for(this._matches(this.tags.COMMENT_START)&&(p=!0,e=this._extractString(this.tags.COMMENT_START));null!==(h=this._extractUntil(f));){if(e+=h,(this._matches(this.tags.BLOCK_START)||this._matches(this.tags.VARIABLE_START)||this._matches(this.tags.COMMENT_START))&&!p){if(this.lstripBlocks&&this._matches(this.tags.BLOCK_START)&&0this.len?null:this.str.slice(this.index,this.index+e.length)===e},t._extractString=function(e){return this._matches(e)?(this.forwardN(e.length),e):null},t._extractUntil=function(e){return this._extractMatching(!0,e||"")},t._extract=function(e){return this._extractMatching(!1,e)},t._extractMatching=function(e,t){if(this.isFinished())return null;var n=t.indexOf(this.current());if(e&&-1===n||!e&&-1!==n){var r=this.current();this.forward();for(var i=t.indexOf(this.current());(e&&-1===i||!e&&-1!==i)&&!this.isFinished();)r+=this.current(),this.forward(),i=t.indexOf(this.current());return r}return""},t._extractRegex=function(e){return(e=this.currentStr().match(e))?(this.forwardN(e[0].length),e):null},t.isFinished=function(){return this.index>=this.len},t.forwardN=function(e){for(var t=0;t",n+2),e(t,n+4)}))}};function Iu(e,t){return(Iu=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}bu.Node,bu.Root,bu.NodeList,bu.Value,bu.Literal,bu.Group,bu.Pair,bu.Dict,bu.Output,bu.Capture,bu.TemplateData,bu.If,bu.IfAsync,bu.InlineIf,bu.For,bu.AsyncEach,bu.AsyncAll,bu.Macro,bu.Caller,bu.Import,bu.FromImport,bu.FunCall,bu.Filter,bu.FilterAsync,bu.KeywordArgs,bu.Block,bu.Super,bu.Extends,bu.Include,bu.Switch,bu.Case,bu.LookupVal,bu.BinOp,bu.In,bu.Is,bu.Or,bu.And,bu.Not,bu.Add,bu.Concat,bu.Sub,bu.Mul,bu.Div,bu.FloorDiv,bu.Mod,bu.Pow,bu.Neg,bu.Pos,bu.Compare,bu.CompareOperand,bu.CallExtension,bu.CallExtensionAsync,bu.printNodes;var Ou=function(e){var n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),Iu(t.prototype.constructor=t,n);var t=r.prototype;return t.init=function(e){this.tokens=e,this.peeked=null,this.breakOnBlocks=null,this.dropLeadingWhitespace=!1,this.extensions=[]},t.nextToken=function(e){var t;if(this.peeked){if(e||this.peeked.type!==uc.TOKEN_WHITESPACE)return t=this.peeked,this.peeked=null,t;this.peeked=null}if(t=this.tokens.nextToken(),!e)for(;t&&t.type===uc.TOKEN_WHITESPACE;)t=this.tokens.nextToken();return t},t.peekToken=function(){return this.peeked=this.peeked||this.nextToken(),this.peeked},t.pushToken=function(e){if(this.peeked)throw new Error("pushToken: can only push one token on between reads");this.peeked=e},t.error=function(e,t,n){var r;return void 0!==t&&void 0!==n||(t=(r=this.peekToken()||{}).lineno,n=r.colno),void 0!==t&&(t+=1),void 0!==n&&(n+=1),new Ua.TemplateError(e,t,n)},t.fail=function(e,t,n){throw this.error(e,t,n)},t.skip=function(e){var t=this.nextToken();return!(!t||t.type!==e)||(this.pushToken(t),!1)},t.expect=function(e){var t=this.nextToken();return t.type!==e&&this.fail("expected "+e+", got "+t.type,t.lineno,t.colno),t},t.skipValue=function(e,t){var n=this.nextToken();return!(!n||n.type!==e||n.value!==t)||(this.pushToken(n),!1)},t.skipSymbol=function(e){return this.skipValue(uc.TOKEN_SYMBOL,e)},t.advanceAfterBlockEnd=function(e){var t;return e||((t=this.peekToken())||this.fail("unexpected end of file"),t.type!==uc.TOKEN_SYMBOL&&this.fail("advanceAfterBlockEnd: expected symbol token or explicit name to be passed"),e=this.nextToken().value),(t=this.nextToken())&&t.type===uc.TOKEN_BLOCK_END?"-"===t.value.charAt(0)&&(this.dropLeadingWhitespace=!0):this.fail("expected block end in "+e+" statement"),t},t.advanceAfterVariableEnd=function(){var e=this.nextToken();e&&e.type===uc.TOKEN_VARIABLE_END?this.dropLeadingWhitespace="-"===e.value.charAt(e.value.length-this.tokens.tags.VARIABLE_END.length-1):(this.pushToken(e),this.fail("expected variable end"))},t.parseFor=function(){var e,t,n=this.peekToken();if(this.skipSymbol("for")?(e=new bu.For(n.lineno,n.colno),t="endfor"):this.skipSymbol("asyncEach")?(e=new bu.AsyncEach(n.lineno,n.colno),t="endeach"):this.skipSymbol("asyncAll")?(e=new bu.AsyncAll(n.lineno,n.colno),t="endall"):this.fail("parseFor: expected for{Async}",n.lineno,n.colno),e.name=this.parsePrimary(),e.name instanceof bu.Symbol||this.fail("parseFor: variable name expected for loop"),this.peekToken().type===uc.TOKEN_COMMA){var r=e.name;for(e.name=new bu.Array(r.lineno,r.colno),e.name.addChild(r);this.skip(uc.TOKEN_COMMA);){var i=this.parsePrimary();e.name.addChild(i)}}return this.skipSymbol("in")||this.fail('parseFor: expected "in" keyword for loop',n.lineno,n.colno),e.arr=this.parseExpression(),this.advanceAfterBlockEnd(n.value),e.body=this.parseUntilBlocks(t,"else"),this.skipSymbol("else")&&(this.advanceAfterBlockEnd("else"),e.else_=this.parseUntilBlocks(t)),this.advanceAfterBlockEnd(),e},t.parseMacro=function(){var e=this.peekToken(),t=(this.skipSymbol("macro")||this.fail("expected macro"),this.parsePrimary(!0)),n=this.parseSignature(),t=new bu.Macro(e.lineno,e.colno,t,n);return this.advanceAfterBlockEnd(e.value),t.body=this.parseUntilBlocks("endmacro"),this.advanceAfterBlockEnd(),t},t.parseCall=function(){var e=this.peekToken(),t=(this.skipSymbol("call")||this.fail("expected call"),this.parseSignature(!0)||new bu.NodeList),n=this.parsePrimary(),r=(this.advanceAfterBlockEnd(e.value),this.parseUntilBlocks("endcall")),i=(this.advanceAfterBlockEnd(),new bu.Symbol(e.lineno,e.colno,"caller")),t=new bu.Caller(e.lineno,e.colno,i,t,r);return(r=n.args.children)[r.length-1]instanceof bu.KeywordArgs||r.push(new bu.KeywordArgs),r[r.length-1].addChild(new bu.Pair(e.lineno,e.colno,i,t)),new bu.Output(e.lineno,e.colno,[n])},t.parseWithContext=function(){var e=this.peekToken(),t=null;return this.skipSymbol("with")?t=!0:this.skipSymbol("without")&&(t=!1),null===t||this.skipSymbol("context")||this.fail("parseFrom: expected context after with/without",e.lineno,e.colno),t},t.parseImport=function(){var e=this.peekToken(),t=(this.skipSymbol("import")||this.fail("parseImport: expected import",e.lineno,e.colno),this.parseExpression()),n=(this.skipSymbol("as")||this.fail('parseImport: expected "as" keyword',e.lineno,e.colno),this.parseExpression()),r=this.parseWithContext(),t=new bu.Import(e.lineno,e.colno,t,n,r);return this.advanceAfterBlockEnd(e.value),t},t.parseFrom=function(){var e=this.peekToken(),t=(this.skipSymbol("from")||this.fail("parseFrom: expected from"),this.parseExpression());this.skipSymbol("import")||this.fail("parseFrom: expected import",e.lineno,e.colno);for(var n,r=new bu.NodeList;;){var a,i=this.peekToken();if(i.type===uc.TOKEN_BLOCK_END){r.children.length||this.fail("parseFrom: Expected at least one import name",e.lineno,e.colno),"-"===i.value.charAt(0)&&(this.dropLeadingWhitespace=!0),this.nextToken();break}0","<=",">="],t=this.parseConcat(),n=[];;){var r=this.nextToken();if(!r)break;if(-1===e.indexOf(r.value)){this.pushToken(r);break}n.push(new bu.CompareOperand(r.lineno,r.colno,this.parseConcat(),r.value))}return n.length?new bu.Compare(n[0].lineno,n[0].colno,t,n):t},t.parseConcat=function(){for(var e=this.parseAdd();this.skipValue(uc.TOKEN_TILDE,"~");)var t=this.parseAdd(),e=new bu.Concat(e.lineno,e.colno,e,t);return e},t.parseAdd=function(){for(var e=this.parseSub();this.skipValue(uc.TOKEN_OPERATOR,"+");)var t=this.parseSub(),e=new bu.Add(e.lineno,e.colno,e,t);return e},t.parseSub=function(){for(var e=this.parseMul();this.skipValue(uc.TOKEN_OPERATOR,"-");)var t=this.parseMul(),e=new bu.Sub(e.lineno,e.colno,e,t);return e},t.parseMul=function(){for(var e=this.parseDiv();this.skipValue(uc.TOKEN_OPERATOR,"*");)var t=this.parseDiv(),e=new bu.Mul(e.lineno,e.colno,e,t);return e},t.parseDiv=function(){for(var e=this.parseFloorDiv();this.skipValue(uc.TOKEN_OPERATOR,"/");)var t=this.parseFloorDiv(),e=new bu.Div(e.lineno,e.colno,e,t);return e},t.parseFloorDiv=function(){for(var e=this.parseMod();this.skipValue(uc.TOKEN_OPERATOR,"//");)var t=this.parseMod(),e=new bu.FloorDiv(e.lineno,e.colno,e,t);return e},t.parseMod=function(){for(var e=this.parsePow();this.skipValue(uc.TOKEN_OPERATOR,"%");)var t=this.parsePow(),e=new bu.Mod(e.lineno,e.colno,e,t);return e},t.parsePow=function(){for(var e=this.parseUnary();this.skipValue(uc.TOKEN_OPERATOR,"**");)var t=this.parseUnary(),e=new bu.Pow(e.lineno,e.colno,e,t);return e},t.parseUnary=function(e){var n=this.peekToken(),n=this.skipValue(uc.TOKEN_OPERATOR,"-")?new bu.Neg(n.lineno,n.colno,this.parseUnary(!0)):this.skipValue(uc.TOKEN_OPERATOR,"+")?new bu.Pos(n.lineno,n.colno,this.parseUnary(!0)):this.parsePrimary();return e?n:this.parseFilter(n)},t.parsePrimary=function(e){var t,n=this.nextToken();if(n?n.type===uc.TOKEN_STRING?t=n.value:n.type===uc.TOKEN_INT?t=parseInt(n.value,10):n.type===uc.TOKEN_FLOAT?t=parseFloat(n.value):n.type===uc.TOKEN_BOOLEAN?"true"===n.value?t=!0:"false"===n.value?t=!1:this.fail("invalid boolean: "+n.value,n.lineno,n.colno):n.type===uc.TOKEN_NONE?t=null:n.type===uc.TOKEN_REGEX&&(t=new RegExp(n.value.body,n.value.flags)):this.fail("expected expression, got end of file"),t=void 0!==t?new bu.Literal(n.lineno,n.colno,t):n.type===uc.TOKEN_SYMBOL?new bu.Symbol(n.lineno,n.colno,n.value):(this.pushToken(n),this.parseAggregate()),t=e?t:this.parsePostfix(t))return t;throw this.error("unexpected token: "+n.value,n.lineno,n.colno)},t.parseFilterName=function(){for(var e=this.expect(uc.TOKEN_SYMBOL),t=e.value;this.skipValue(uc.TOKEN_OPERATOR,".");)t+="."+this.expect(uc.TOKEN_SYMBOL).value;return new bu.Symbol(e.lineno,e.colno,t)},t.parseFilterArgs=function(e){return this.peekToken().type===uc.TOKEN_LEFT_PAREN?this.parsePostfix(e).args.children:[]},t.parseFilter=function(e){for(;this.skip(uc.TOKEN_PIPE);){var t=this.parseFilterName();e=new bu.Filter(t.lineno,t.colno,t,new bu.NodeList(t.lineno,t.colno,[e].concat(this.parseFilterArgs(e))))}return e},t.parseFilterStatement=function(){var e=this.peekToken(),t=(this.skipSymbol("filter")||this.fail("parseFilterStatement: expected filter"),this.parseFilterName()),n=this.parseFilterArgs(t),e=(this.advanceAfterBlockEnd(e.value),new bu.Capture(t.lineno,t.colno,this.parseUntilBlocks("endfilter"))),e=(this.advanceAfterBlockEnd(),new bu.Filter(t.lineno,t.colno,t,new bu.NodeList(t.lineno,t.colno,[e].concat(n))));return new bu.Output(t.lineno,t.colno,[e])},t.parseAggregate=function(){var e,t=this.nextToken();switch(t.type){case uc.TOKEN_LEFT_PAREN:e=new bu.Group(t.lineno,t.colno);break;case uc.TOKEN_LEFT_BRACKET:e=new bu.Array(t.lineno,t.colno);break;case uc.TOKEN_LEFT_CURLY:e=new bu.Dict(t.lineno,t.colno);break;default:return null}for(;;){var i,n=this.peekToken().type;if(n===uc.TOKEN_RIGHT_PAREN||n===uc.TOKEN_RIGHT_BRACKET||n===uc.TOKEN_RIGHT_CURLY){this.nextToken();break}0e.length)a=i.slice(0,e.length),i.slice(a.length,o).forEach(function(e,n){n":">","<=":"<=",">=":">="},Qu=function(e){var n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),Ku(t.prototype.constructor=t,n);var t=r.prototype;return t.init=function(e,t){this.templateName=e,this.codebuf=[],this.lastId=0,this.buffer=null,this.bufferStack=[],this._scopeClosers="",this.inBlock=!1,this.throwOnUndefined=t},t.fail=function(e,t,n){throw void 0!==t&&(t+=1),void 0!==n&&(n+=1),new Wu(e,t,n)},t._pushBuffer=function(){var e=this._tmpid();return this.bufferStack.push(this.buffer),this.buffer=e,this._emit("var "+this.buffer+' = "";'),e},t._popBuffer=function(){this.buffer=this.bufferStack.pop()},t._emit=function(e){this.codebuf.push(e)},t._emitLine=function(e){this._emit(e+"\n")},t._emitLines=function(){for(var e=this,t=arguments.length,n=new Array(t),r=0;r=(t=t||80)?e:(t-=e.length,i=Ua.repeat(" ",t/2-t%2),t=Ua.repeat(" ",t/2),Yu.copySafeness(e,i+e+t))},e.default=function(e,t,n){return n?e||t:void 0!==e?e:t},e.dictsort=function(e,t,n){if(!Ua.isObject(e))throw new Ua.TemplateError("dictsort filter: val must be an object");var r,s,i=[];for(s in e)i.push([s,e[s]]);if(void 0===n||"key"===n)r=0;else{if("value"!==n)throw new Ua.TemplateError("dictsort filter: You can only sort by either key or value");r=1}return i.sort(function(e,n){return e=e[r],n=n[r],t||(Ua.isString(e)&&(e=e.toUpperCase()),Ua.isString(n)&&(n=n.toUpperCase())),n\n"))},e.random=function(e){return e[Math.floor(Math.random()*e.length)]},e.reject=a(!1),e.rejectattr=function(e,t){return e.filter(function(e){return!e[t]})},e.select=a(!0),e.selectattr=function(e,t){return e.filter(function(e){return!!e[t]})},e.replace=function(e,t,n,r){var i=e;if(t instanceof RegExp)return e.replace(t,n);void 0===r&&(r=-1);var s="";if("number"==typeof t)t=""+t;else if("string"!=typeof t)return e;if("string"!=typeof(e="number"==typeof e?""+e:e)&&!(e instanceof Yu.SafeString))return e;if(""===t)return s=n+e.split("").join(n)+n,Yu.copySafeness(e,s);var a=e.indexOf(t);if(0===r||-1===a)return e;for(var o=0,c=0;-1]*>|/gi,"")),t=t?r.replace(/^ +| +$/gm,"").replace(/ +/g," ").replace(/(\r\n)/g,"\n").replace(/\n\n\n+/g,"\n\n"):r.replace(/\s+/gi," ");return Yu.copySafeness(e,t)},e.title=function(e){var t=(e=n(e,"")).split(" ").map(i);return Yu.copySafeness(e,t.join(" "))},e.trim=o,e.truncate=function(e,t,r,i){var s=e;return(e=n(e,"")).length<=(t=t||255)?e:(e=r?e.substring(0,t):(-1===(r=e.lastIndexOf(" ",t))&&(r=t),e.substring(0,r)),Yu.copySafeness(s,e+=null!=i?i:"..."))},e.upper=function(e){return(e=n(e,"")).toUpperCase()},e.urlencode=function(e){var t=encodeURIComponent;return Ua.isString(e)?t(e):(Ua.isArray(e)?e:Ua._entries(e)).map(function(e){var n=e[0],e=e[1];return t(n)+"="+t(e)}).join("&")};var c=/^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/,u=/^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i,l=/^https?:\/\/.*$/,h=/^www\./,f=/\.(?:org|net|com)(?:\:|\/|$)/,p=(e.urlize=function(e,t,n){r(t)&&(t=1/0);var i=!0===n?' rel="nofollow"':"";return e.split(/(\s+)/).filter(function(e){return e&&e.length}).map(function(e){var n=e.match(c),s=(n=n?n[1]:e).substr(0,t);return l.test(n)?'"+s+"":h.test(n)?'"+s+"":u.test(n)?''+n+"":f.test(n)?'"+s+"":e}).join("")},e.wordcount=function(e){return(e=(e=n(e,""))?e.match(/\w+/g):null)?e.length:null},e.float=function(e,t){return r(e=parseFloat(e))?t:e},Yu.makeMacro(["value","default","base"],[],function(e,t,n){return void 0===n&&(n=10),r(e=parseInt(e,n))?t:e}));e.int=p,e.d=e.default,e.e=e.escape}),Zu={};function Ju(e,t){for(var n=0,r=e.length-1;0<=r;r--){var i=e[r];"."===i?e.splice(r,1):".."===i?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;)e.unshift("..");return e}var el=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,tl=function(e){return el.exec(e).slice(1)};function nl(){for(var e="",t=!1,n=arguments.length-1;-1<=n&&!t;n--){var r=0<=n?arguments[n]:"/";if("string"!=typeof r)throw new TypeError("Arguments to path.resolve must be strings");r&&(e=r+"/"+e,t="/"===r.charAt(0))}return(t?"/":"")+Ju(al(e.split("/"),function(e){return!!e}),!t).join("/")||"."}function rl(e){var t=il(e),n="/"===ol(e,-1);return(e=(e=Ju(al(e.split("/"),function(e){return!!e}),!t).join("/"))||t?e:".")&&n&&(e+="/"),(t?"/":"")+e}function il(e){return"/"===e.charAt(0)}var sl={extname:function(e){return tl(e)[3]},basename:function(e,t){return e=tl(e)[2],t&&e.substr(-1*t.length)===t?e.substr(0,e.length-t.length):e},dirname:function(e){var n=(e=tl(e))[0],e=e[1];return n||e?n+(e&&e.substr(0,e.length-1)):"."},sep:"/",delimiter:":",relative:function(e,t){function n(e){for(var t=0;t=e.length&&(t=0),this.current=e[t],this.current}};var e,t},joiner:function(e){e=e||",";var t=!0;return function(){var n=t?"":e;return t=!1,n}}}});function Al(e,t){e.prototype=Object.create(t.prototype),gl(e.prototype.constructor=e,t)}function gl(e,t){return(gl=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e})(e,t)}var vl=_l.FileSystemLoader,yl=_l.WebLoader,Sl=_l.PrecompiledLoader,Cl=Ec.Obj,Nl=Ec.EmitterObj,bl=Yu.handleError,Il=Yu.Frame;function Ol(e,t,n){Ro(function(){e(t,n)})}var kl={type:"code",obj:{root:function(e,t,n,r,i){try{i(null,"")}catch(e){i(bl(e,null,null))}}}},Ll=function(e){function t(){return e.apply(this,arguments)||this}Al(t,e);var n=t.prototype;return n.init=function(e,t){var n=this;t=this.opts=t||{},this.opts.dev=!!t.dev,this.opts.autoescape=null==t.autoescape||t.autoescape,this.opts.throwOnUndefined=!!t.throwOnUndefined,this.opts.trimBlocks=!!t.trimBlocks,this.opts.lstripBlocks=!!t.lstripBlocks,this.loaders=[],e?this.loaders=Ua.isArray(e)?e:[e]:vl?this.loaders=[new vl("views")]:yl&&(this.loaders=[new yl("/views")]),"undefined"!=typeof window&&window.nunjucksPrecompiled&&this.loaders.unshift(new Sl(window.nunjucksPrecompiled)),this._initLoaders(),this.globals=Tl(),this.filters={},this.tests={},this.asyncFilters=[],this.extensions={},this.extensionsList=[],Ua._entries(Xu).forEach(function(e){var t=e[0],e=e[1];return n.addFilter(t,e)}),Ua._entries(El).forEach(function(e){var t=e[0],e=e[1];return n.addTest(t,e)})},n._initLoaders=function(){var e=this;this.loaders.forEach(function(t){t.cache={},"function"==typeof t.on&&(t.on("update",function(n,r){t.cache[n]=null,e.emit("update",n,r,t)}),t.on("load",function(n,r){e.emit("load",n,r,t)}))})},n.invalidateCache=function(){this.loaders.forEach(function(e){e.cache={}})},n.addExtension=function(e,t){return t.__name=e,this.extensions[e]=t,this.extensionsList.push(t),this},n.removeExtension=function(e){var t=this.getExtension(e);t&&(this.extensionsList=Ua.without(this.extensionsList,t),delete this.extensions[e])},n.getExtension=function(e){return this.extensions[e]},n.hasExtension=function(e){return!!this.extensions[e]},n.addGlobal=function(e,t){return this.globals[e]=t,this},n.getGlobal=function(e){if(void 0===this.globals[e])throw new Error("global not found: "+e);return this.globals[e]},n.addFilter=function(e,t,n){return n&&this.asyncFilters.push(e),this.filters[e]=t,this},n.getFilter=function(e){if(this.filters[e])return this.filters[e];throw new Error("filter not found: "+e)},n.addTest=function(e,t){return this.tests[e]=t,this},n.getTest=function(e){if(this.tests[e])return this.tests[e];throw new Error("test not found: "+e)},n.resolveTemplate=function(e,t,n){return e.isRelative&&t&&e.isRelative(n)&&e.resolve?e.resolve(t,n):n},n.getTemplate=function(e,t,n,r,i){var s,a=this,o=this,c=null;if(e&&e.raw&&(e=e.raw),Ua.isFunction(n)&&(i=n,n=null,t=t||!1),Ua.isFunction(t)&&(i=t,t=!1),e instanceof Rl)c=e;else{if("string"!=typeof e)throw new Error("template names must be a string: "+e);for(var u=0;u{var t=["th","st","nd","rd"],n=e%100;return`[${e}${t[(n-20)%10]||t[n]||t[0]}]`}};const th=(e,t,n)=>{var r=String(e);return!r||r.length>=t?e:""+Array(t+1-r.length).join(n)+e},nh=(e,t)=>{var n,r,i;return e.date(){var e=-e.utcOffset(),n=Math.abs(e),r=Math.floor(n/60),n=n%60;return(e<=0?"+":"-")+th(r,2,"0")+":"+th(n,2,"0")},m:nh,a:e=>e<0?Math.ceil(e)||0:Math.floor(e),p:e=>({M:Vl,y:Ql,w:Wl,d:Kl,D:zl,h:Yl,m:ql,s:jl,ms:Gl,Q:$l})[e]||String(e||"").toLowerCase().replace(/s$/,""),u:e=>void 0===e};let ih="en";const sh={},ah=(sh[ih]=eh,e=>e instanceof lh),oh=(e,t,n)=>{let r;if(!e)return ih;if("string"==typeof e){const n=e.toLowerCase();if(sh[n]&&(r=n),t&&(sh[n]=t,r=n),t=e.split("-"),!r&&1ch(e,{locale:t.$L,utc:t.$u,x:t.$x,$offset:t.$offset});class lh{constructor(e){this.$L=oh(e.locale,null,!0),this.parse(e)}parse(e){this.$d=(e=>{const{date:t,utc:n}=e;if(null===t)return new Date(NaN);if(uh.u(t))return new Date;if(!(t instanceof Date||"string"!=typeof t||/Z$/i.test(t))){const e=t.match(Zl);if(e){const t=e[2]-1||0,r=(e[7]||"0").substring(0,3);return n?new Date(Date.UTC(e[1],t,e[3]||1,e[4]||0,e[5]||0,e[6]||0,r)):new Date(e[1],t,e[3]||1,e[4]||0,e[5]||0,e[6]||0,r)}}return new Date(t)})(e),this.$x=e.x||{},this.init()}init(){var e=this.$d;this.$y=e.getFullYear(),this.$M=e.getMonth(),this.$D=e.getDate(),this.$W=e.getDay(),this.$H=e.getHours(),this.$m=e.getMinutes(),this.$s=e.getSeconds(),this.$ms=e.getMilliseconds()}$utils(){return uh}isValid(){return!(this.$d.toString()===Xl)}isSame(e,t){return e=ch(e),this.startOf(t)<=e&&e<=this.endOf(t)}isAfter(e,t){return ch(e)(t=uh.w(this.$u?Date.UTC(this.$y,t,e):new Date(this.$y,t,e),this),n?t:t.endOf(Kl)),s=(e,t)=>uh.w(this.toDate()[e].apply(this.toDate("s"),(n?[0,0,0,0]:[23,59,59,999]).slice(t)),this),{$W:a,$M:o,$D:c}=this,u="set"+(this.$u?"UTC":"");switch(r){case Ql:return n?i(1,0):i(31,11);case Vl:return n?i(1,o):i(0,o+1);case Wl:{const e=this.$locale().weekStart||0,t=(a{var n=ch(this);return uh.w(n.date(n.date()+Math.round(t*e)),this)};return(t=uh.p(t))===Vl?this.set(Vl,this.$M+e):t===Ql?this.set(Ql,this.$y+e):t===Kl?r(1):t===Wl?r(7):(r={[ql]:Ul,[Yl]:Hl,[jl]:Fl}[t]||1,t=this.$d.getTime()+e*r,uh.w(t,this))}subtract(e,t){return this.add(-1*e,t)}format(e){var t=this.$locale();if(!this.isValid())return t.invalidDate||Xl;const n=e||"YYYY-MM-DDTHH:mm:ssZ",r=uh.z(this),{$H:i,$m:s,$M:a}=this,{weekdays:o,months:c,meridiem:u}=t,l=(e,t,r,i)=>e&&(e[t]||e(this,n))||r[t].slice(0,i),h=e=>uh.s(i%12||12,e,"0"),f=u||((e,t,n)=>(e=e<12?"AM":"PM",n?e.toLowerCase():e)),p={YY:String(this.$y).slice(-2),YYYY:this.$y,M:a+1,MM:uh.s(a+1,2,"0"),MMM:l(t.monthsShort,a,c,3),MMMM:l(c,a),D:this.$D,DD:uh.s(this.$D,2,"0"),d:String(this.$W),dd:l(t.weekdaysMin,this.$W,o,2),ddd:l(t.weekdaysShort,this.$W,o,3),dddd:o[this.$W],H:String(i),HH:uh.s(i,2,"0"),h:h(1),hh:h(2),a:f(i,s,!0),A:f(i,s,!1),m:String(s),mm:uh.s(s,2,"0"),s:String(this.$s),ss:uh.s(this.$s,2,"0"),SSS:uh.s(this.$ms,3,"0"),Z:r};return n.replace(Jl,(e,t)=>t||p[e]||r.replace(":",""))}utcOffset(){return 15*-Math.round(this.$d.getTimezoneOffset()/15)}diff(e,t,n){var t=uh.p(t),s=((e=ch(e)).utcOffset()-this.utcOffset())*Ul,a=this-e,e=uh.m(this,e),e={[Ql]:e/12,[Vl]:e,[$l]:e/3,[Wl]:(a-s)/6048e5,[Kl]:(a-s)/864e5,[Yl]:a/Hl,[ql]:a/Ul,[jl]:a/Fl}[t]||a;return n?e:uh.a(e)}daysInMonth(){return this.endOf(Vl).$D}$locale(){return sh[this.$L]}locale(e,t){var n;return e?(n=this.clone(),(e=oh(e,t,!0))&&(n.$L=e),n):this.$L}clone(){return uh.w(this.$d,this)}toDate(){return new Date(this.valueOf())}toJSON(){return this.isValid()?this.toISOString():null}toISOString(){return this.$d.toISOString()}toString(){return this.$d.toUTCString()}}const hh=lh.prototype;ch.prototype=hh,[["$ms",Gl],["$s",jl],["$m",ql],["$H",Yl],["$W",Kl],["$M",Vl],["$y",Ql],["$D",zl]].forEach(e=>{hh[e[1]]=function(t){return this.$g(t,e[0],e[1])}}),ch.extend=(e,t)=>(e.$i||(e(t,lh,ch),e.$i=!0),ch),ch.locale=oh,ch.isDayjs=ah,ch.unix=e=>ch(1e3*e),ch.en=sh[ih],ch.Ls=sh,ch.p={};var fh=Sr(function(e){function r(e){return e&&(e=e.toString().replace(n.pluses,"%20"),e=decodeURIComponent(e)),e}function i(e){var t,i,s,a,o,c,u,l=[];if(null!=e&&""!==e)for(u=(i=(e=(t=0)===e.indexOf("?")?e.substring(1):e).toString().split(n.query_separator)).length;t>>0;if("function"!=typeof e)throw new TypeError(e+" is not a function");for(1"']/g,$=RegExp(W.source),Q=RegExp(V.source),z=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,Z=/<%=([\s\S]+?)%>/g,J=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,ee=/^\w*$/,te=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,ne=/[\\^$.*+?()[\]{}|]/g,re=RegExp(ne.source),ie=/^\s+/,se=/\s/,ae=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,oe=/\{\n\/\* \[wrapped with (.+)\] \*/,ce=/,? & /,ue=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,le=/[()=,{}\[\]\/\s]/,he=/\\(\\)?/g,fe=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,pe=/\w*$/,de=/^[-+]0x[0-9a-f]+$/i,me=/^0b[01]+$/i,_e=/^\[object .+?Constructor\]$/,Ee=/^0o[0-7]+$/i,Te=/^(?:0|[1-9]\d*)$/,Ae=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,ge=/($^)/,ve=/['\n\r\u2028\u2029\\]/g,ye="\\ud800-\\udfff",Se="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",Ce="\\u2700-\\u27bf",be="A-Z\\xc0-\\xd6\\xd8-\\xde",Ie="\\ufe0e\\ufe0f",Le="["+ye+"]",De="["+(Oe="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000")+"]",Re="["+Se+"]",xe="["+Ce+"]",Me="["+(Ne="a-z\\xdf-\\xf6\\xf8-\\xff")+"]",He="[\\ud800-\\udbff][\\udc00-\\udfff]",je="\\u200d",qe="(?:"+Me+"|"+(Oe="[^"+ye+Oe+"\\d+"+Ce+Ne+be+"]")+")",Oe="(?:"+(be="["+be+"]")+"|"+Oe+")",Ke="(?:['’](?:d|ll|m|re|s|t|ve))?",We="(?:['’](?:D|LL|M|RE|S|T|VE))?",$e=($e="["+Ie+"]?")+(Ve="(?:"+Re+"|"+(Ce="\\ud83c[\\udffb-\\udfff]")+")?")+"(?:"+je+"(?:"+[Ne="[^"+ye+"]",Ue="(?:\\ud83c[\\udde6-\\uddff]){2}",He].join("|")+")"+$e+Ve+")*",Ve="(?:"+[xe,Ue,He].join("|")+")"+$e,xe="(?:"+[Ne+Re+"?",Re,Ue,He,Le].join("|")+")",Ze=RegExp("['’]","g"),Je=RegExp(Re,"g"),et=RegExp(Ce+"(?="+Ce+")|"+xe+$e,"g"),tt=RegExp([be+"?"+Me+"+"+Ke+"(?="+[De,be,"$"].join("|")+")",Oe+"+"+We+"(?="+[De,be+qe,"$"].join("|")+")",be+"?"+qe+"+"+Ke,be+"+"+We,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])","\\d+",Ve].join("|"),"g"),nt=RegExp("["+je+ye+Se+Ie+"]"),rt=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,it=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],st=-1,at={},ot=(at[x]=at[M]=at[P]=at[B]=at[F]=at[U]=at[H]=at[G]=at[j]=!0,at[_]=at[E]=at[R]=at[T]=at[w]=at[A]=at[g]=at[v]=at[S]=at[C]=at[N]=at[I]=at[O]=at[k]=at[D]=!1,{}),ct=(ot[_]=ot[E]=ot[R]=ot[w]=ot[T]=ot[A]=ot[x]=ot[M]=ot[P]=ot[B]=ot[F]=ot[S]=ot[C]=ot[N]=ot[I]=ot[O]=ot[k]=ot[L]=ot[U]=ot[H]=ot[G]=ot[j]=!0,ot[g]=ot[v]=ot[D]=!1,{"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"}),ut=parseFloat,lt=parseInt,Ne="object"==typeof gr&&gr&&gr.Object===Object&&gr,Ue="object"==typeof self&&self&&self.Object===Object&&self,pt=Ne||Ue||Function("return this")(),mt=(He=t&&!t.nodeType&&t)&&e&&!e.nodeType&&e,_t=mt&&mt.exports===He,Et=_t&&Ne.process,At=(Le=function(){try{return mt&&mt.require&&mt.require("util").types||Et&&Et.binding&&Et.binding("util")}catch(e){}}())&&Le.isArrayBuffer,gt=Le&&Le.isDate,vt=Le&&Le.isMap,yt=Le&&Le.isRegExp,St=Le&&Le.isSet,Ct=Le&&Le.isTypedArray;function Nt(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function bt(e,t,n,r){for(var i=-1,s=null==e?0:e.length;++i":">",'"':""","'":"'"});function sn(e){return"\\"+ct[e]}function an(e){return nt.test(e)}function on(e){var t=-1,n=Array(e.size);return e.forEach(function(e,r){n[++t]=[r,e]}),n}function cn(e,t){return function(n){return e(t(n))}}function un(e,t){for(var n=-1,r=e.length,i=0,a=[];++n",""":'"',"'":"'"}),_n=function e(t){var ye=(t=null==t?pt:_n.defaults(pt.Object(),t,_n.pick(pt,it))).Array,Se=t.Date,Ce=t.Error,Ne=t.Function,be=t.Math,Ie=t.Object,Oe=t.RegExp,ke=t.String,Le=t.TypeError,De=ye.prototype,Re=Ne.prototype,we=Ie.prototype,xe=t["__core-js_shared__"],Me=Re.toString,Pe=we.hasOwnProperty,Be=0,Fe=(Re=/[^.]+$/.exec(xe&&xe.keys&&xe.keys.IE_PROTO||""))?"Symbol(src)_1."+Re:"",Ue=we.toString,He=Me.call(Ie),Ge=pt._,je=Oe("^"+Me.call(Pe).replace(ne,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Re=_t?t.Buffer:n,Ye=t.Symbol,Ke=t.Uint8Array,We=Re?Re.allocUnsafe:n,Ve=cn(Ie.getPrototypeOf,Ie),$e=Ie.create,Qe=we.propertyIsEnumerable,ze=De.splice,Xe=Ye?Ye.isConcatSpreadable:n,et=Ye?Ye.iterator:n,nt=Ye?Ye.toStringTag:n,ct=function(){try{var e=fs(Ie,"defineProperty");return e({},"",{}),e}catch(e){}}(),ht=t.clearTimeout!==pt.clearTimeout&&t.clearTimeout,ft=Se&&Se.now!==pt.Date.now&&Se.now,dt=t.setTimeout!==pt.setTimeout&&t.setTimeout,mt=be.ceil,Et=be.floor,Tt=Ie.getOwnPropertySymbols,Re=Re?Re.isBuffer:n,Wt=t.isFinite,En=De.join,Tn=cn(Ie.keys,Ie),An=be.max,gn=be.min,vn=Se.now,yn=t.parseInt,Sn=be.random,Cn=De.reverse,Se=fs(t,"DataView"),bn=fs(t,"Map"),In=fs(t,"Promise"),On=fs(t,"Set"),t=fs(t,"WeakMap"),Ln=fs(Ie,"create"),Dn=t&&new t,Rn={},wn=Fs(Se),xn=Fs(bn),Mn=Fs(In),Pn=Fs(On),Bn=Fs(t),Un=(Ye=Ye?Ye.prototype:n)?Ye.valueOf:n,Hn=Ye?Ye.toString:n;function Gn(e){if(to(e)&&!Ya(e)&&!(e instanceof Kn)){if(e instanceof Yn)return e;if(Pe.call(e,"__wrapped__"))return Us(e)}return new Yn(e)}var jn=function(){function e(){}return function(t){return eo(t)?$e?$e(t):(e.prototype=t,t=new e,e.prototype=n,t):{}}}();function qn(){}function Yn(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=n}function Kn(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=d,this.__views__=[]}function Wn(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t>>0,t>>>=0;for(var s=ye(i);++r>>1,a=e[s];null!==a&&!co(a)&&(n?a<=t:a>>0)?(e=Ao(e))&&("string"==typeof t||null!=t&&!so(t))&&!(t=li(t))&&an(e)?yi(pn(e),0,r):e.split(t,r):[]},Gn.spread=function(e,t){if("function"!=typeof e)throw new Le(r);return t=null==t?0:An(mo(t),0),Xr(function(n){var r=n[t],n=yi(n,0,t);return r&&xt(n,r),Nt(e,this,n)})},Gn.tail=function(e){var t=null==e?0:e.length;return t?ii(e,1,t):[]},Gn.take=function(e,t,r){return e&&e.length?ii(e,0,(t=r||t===n?1:mo(t))<0?0:t):[]},Gn.takeRight=function(e,t,r){var i=null==e?0:e.length;return i?ii(e,(t=i-(r||t===n?1:mo(t)))<0?0:t,i):[]},Gn.takeRightWhile=function(e,t){return e&&e.length?di(e,us(t,3),!1,!0):[]},Gn.takeWhile=function(e,t){return e&&e.length?di(e,us(t,3)):[]},Gn.tap=function(e,t){return t(e),e},Gn.throttle=function(e,t,n){var i=!0,s=!0;if("function"!=typeof e)throw new Le(r);return eo(n)&&(i="leading"in n?!!n.leading:i,s="trailing"in n?!!n.trailing:s),Da(e,t,{leading:i,maxWait:t,trailing:s})},Gn.thru=pa,Gn.toArray=fo,Gn.toPairs=Uo,Gn.toPairsIn=Ho,Gn.toPath=function(e){return Ya(e)?wt(e,Bs):co(e)?[e]:Li(Ps(Ao(e)))},Gn.toPlainObject=To,Gn.transform=function(e,t,n){var s,r=Ya(e),i=r||$a(e)||uo(e);return t=us(t,4),null==n&&(s=e&&e.constructor,n=i?r?new s:[]:eo(e)&&Xa(s)?jn(Ve(e)):{}),(i?It:vr)(e,function(e,r,i){return t(n,e,r,i)}),n},Gn.unary=function(e){return Ia(e,1)},Gn.union=ta,Gn.unionBy=na,Gn.unionWith=ra,Gn.uniq=function(e){return e&&e.length?hi(e):[]},Gn.uniqBy=function(e,t){return e&&e.length?hi(e,us(t,2)):[]},Gn.uniqWith=function(e,t){return t="function"==typeof t?t:n,e&&e.length?hi(e,n,t):[]},Gn.unset=function(e,t){return null==e||fi(e,t)},Gn.unzip=ia,Gn.unzipWith=sa,Gn.update=function(e,t,n){return null==e?e:pi(e,t,Ai(n))},Gn.updateWith=function(e,t,r,i){return i="function"==typeof i?i:n,null==e?e:pi(e,t,Ai(r),i)},Gn.values=Go,Gn.valuesIn=function(e){return null==e?[]:Zt(e,wo(e))},Gn.without=aa,Gn.words=Zo,Gn.wrap=function(e,t){return Ba(Ai(t),e)},Gn.xor=oa,Gn.xorBy=ca,Gn.xorWith=ua,Gn.zip=la,Gn.zipObject=function(e,t){return Ei(e||[],t||[],nr)},Gn.zipObjectDeep=function(e,t){return Ei(e||[],t||[],ei)},Gn.zipWith=ha,Gn.entries=Uo,Gn.entriesIn=Ho,Gn.extend=vo,Gn.extendWith=yo,cc(Gn,Gn),Gn.add=Tc,Gn.attempt=Jo,Gn.camelCase=jo,Gn.capitalize=qo,Gn.ceil=Ac,Gn.clamp=function(e,t,r){return r===n&&(r=t,t=n),r!==n&&(r=(r=Eo(r))==r?r:0),t!==n&&(t=(t=Eo(t))==t?t:0),cr(Eo(e),t,r)},Gn.clone=function(e){return ur(e,4)},Gn.cloneDeep=function(e){return ur(e,5)},Gn.cloneDeepWith=function(e,t){return ur(e,5,t="function"==typeof t?t:n)},Gn.cloneWith=function(e,t){return ur(e,4,t="function"==typeof t?t:n)},Gn.conformsTo=function(e,t){return null==t||lr(e,t,Ro(t))},Gn.deburr=Yo,Gn.defaultTo=function(e,t){return null==e||e!=e?t:e},Gn.divide=gc,Gn.endsWith=function(e,t,r){e=Ao(e),t=li(t);var i=e.length,i=r=r===n?i:cr(mo(r),0,i);return 0<=(r-=t.length)&&e.slice(r,i)==t},Gn.eq=Ha,Gn.escape=function(e){return(e=Ao(e))&&Q.test(e)?e.replace(V,rn):e},Gn.escapeRegExp=function(e){return(e=Ao(e))&&re.test(e)?e.replace(ne,"\\$&"):e},Gn.every=function(e,t,r){return(Ya(e)?kt:function(e,t){var n=!0;return pr(e,function(e,r,i){return n=!!t(e,r,i)}),n})(e,us(t=r&&gs(e,t,r)?n:t,3))},Gn.find=_a,Gn.findIndex=qs,Gn.findKey=function(e,t){return Ut(e,us(t,3),vr)},Gn.findLast=Ea,Gn.findLastIndex=Ys,Gn.findLastKey=function(e,t){return Ut(e,us(t,3),yr)},Gn.floor=vc,Gn.forEach=Ta,Gn.forEachRight=Aa,Gn.forIn=function(e,t){return null==e?e:Ar(e,us(t,3),wo)},Gn.forInRight=function(e,t){return null==e?e:gr(e,us(t,3),wo)},Gn.forOwn=function(e,t){return e&&vr(e,us(t,3))},Gn.forOwnRight=function(e,t){return e&&yr(e,us(t,3))},Gn.get=Io,Gn.gt=Ga,Gn.gte=ja,Gn.has=function(e,t){return null!=e&&_s(e,t,Or)},Gn.hasIn=Oo,Gn.head=Ws,Gn.identity=ic,Gn.includes=function(e,t,n,r){return e=Wa(e)?e:Go(e),n=n&&!r?mo(n):0,r=e.length,n<0&&(n=An(r+n,0)),oo(e)?n<=r&&-1=gn(t,n)&&e=this.__values__.length;return{done:e,value:e?n:this.__values__[this.__index__++]}},Gn.prototype.plant=function(e){for(var t,r=this;r instanceof qn;)var i=Us(r),s=(i.__index__=0,i.__values__=n,t?s.__wrapped__=i:t=i,i),r=r.__wrapped__;return s.__wrapped__=e,t},Gn.prototype.reverse=function(){var e=this.__wrapped__;return e instanceof Kn?((e=(e=this.__actions__.length?new Kn(this):e).reverse()).__actions__.push({func:pa,args:[ea],thisArg:n}),new Yn(e,this.__chain__)):this.thru(ea)},Gn.prototype.toJSON=Gn.prototype.valueOf=Gn.prototype.value=function(){return mi(this.__wrapped__,this.__actions__)},Gn.prototype.first=Gn.prototype.head,et&&(Gn.prototype[et]=function(){return this}),Gn}();mt?((mt.exports=_n)._=_n,He._=_n):pt._=_n}.call(gr)}),dh=Sr(function(e,t){e.exports=function(e){var n;if("undefined"!=typeof window&&window.crypto&&(n=window.crypto),"undefined"!=typeof self&&self.crypto&&(n=self.crypto),!(n=!(n=!(n="undefined"!=typeof globalThis&&globalThis.crypto?globalThis.crypto:n)&&"undefined"!=typeof window&&window.msCrypto?window.msCrypto:n)&&void 0!==gr&&gr.crypto?gr.crypto:n))try{n=Zu}catch(e){}function r(){if(n){if("function"==typeof n.getRandomValues)try{return n.getRandomValues(new Uint32Array(1))[0]}catch(e){}if("function"==typeof n.randomBytes)try{return n.randomBytes(4).readInt32LE()}catch(e){}}throw new Error("Native crypto module could not be used to get secure random number.")}var i=Object.create||function(){function e(){}return function(t){return e.prototype=t,t=new e,e.prototype=null,t}}(),s={},a=s.lib={},o=a.Base={extend:function(e){var t=i(this);return e&&t.mixIn(e),t.hasOwnProperty("init")&&this.init!==t.init||(t.init=function(){t.$super.init.apply(this,arguments)}),(t.init.prototype=t).$super=this,t},create:function(){var e=this.extend();return e.init.apply(e,arguments),e},init:function(){},mixIn:function(e){for(var t in e)e.hasOwnProperty(t)&&(this[t]=e[t]);e.hasOwnProperty("toString")&&(this.toString=e.toString)},clone:function(){return this.init.prototype.extend(this)}},c=a.WordArray=o.extend({init:function(e,n){e=this.words=e||[],this.sigBytes=null!=n?n:4*e.length},toString:function(e){return(e||l).stringify(this)},concat:function(e){var t=this.words,n=e.words,r=this.sigBytes,i=e.sigBytes;if(this.clamp(),r%4)for(var s=0;s>>2]>>>24-s%4*8&255;t[r+s>>>2]|=a<<24-(r+s)%4*8}else for(var o=0;o>>2]=n[o>>>2];return this.sigBytes+=i,this},clamp:function(){var t=this.words,n=this.sigBytes;t[n>>>2]&=4294967295<<32-n%4*8,t.length=e.ceil(n/4)},clone:function(){var e=o.clone.call(this);return e.words=this.words.slice(0),e},random:function(e){for(var t=[],n=0;n>>2]>>>24-i%4*8&255;r.push((s>>>4).toString(16)),r.push((15&s).toString(16))}return r.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r>>3]|=parseInt(e.substr(r,2),16)<<24-r%8*4;return new c.init(n,t/2)}},h=u.Latin1={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i>>2]>>>24-i%4*8&255;r.push(String.fromCharCode(s))}return r.join("")},parse:function(e){for(var t=e.length,n=[],r=0;r>>2]|=(255&e.charCodeAt(r))<<24-r%4*8;return new c.init(n,t)}},f=u.Utf8={stringify:function(e){try{return decodeURIComponent(escape(h.stringify(e)))}catch(e){throw new Error("Malformed UTF-8 data")}},parse:function(e){return h.parse(unescape(encodeURIComponent(e)))}},p=a.BufferedBlockAlgorithm=o.extend({reset:function(){this._data=new c.init,this._nDataBytes=0},_append:function(e){"string"==typeof e&&(e=f.parse(e)),this._data.concat(e),this._nDataBytes+=e.sigBytes},_process:function(t){var n,r=this._data,i=r.words,s=r.sigBytes,a=this.blockSize,o=s/(4*a),u=(t?e.ceil(o):e.max((0|o)-this._minBufferSize,0))*a,t=e.min(4*u,s);if(u){for(var h=0;h>>2]|=e[i]<<24-i%4*8;t.call(this,r,n)}else t.apply(this,arguments)}).prototype=e)}(),n.lib.WordArray)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var t=n.lib.WordArray,r=n.enc;function i(e){return e<<8&4278255360|e>>>8&16711935}r.Utf16=r.Utf16BE={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],i=0;i>>2]>>>16-i%4*8&65535;r.push(String.fromCharCode(s))}return r.join("")},parse:function(e){for(var n=e.length,r=[],i=0;i>>1]|=e.charCodeAt(i)<<16-i%2*16;return t.create(r,2*n)}},r.Utf16LE={stringify:function(e){for(var t=e.words,n=e.sigBytes,r=[],s=0;s>>2]>>>16-s%4*8&65535);r.push(String.fromCharCode(a))}return r.join("")},parse:function(e){for(var n=e.length,r=[],s=0;s>>1]|=i(e.charCodeAt(s)<<16-s%2*16);return t.create(r,2*n)}}}(),n.enc.Utf16)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.WordArray;function r(e,n,r){for(var o,i=[],s=0,a=0;a>>6-a%4*2,i[s>>>2]|=o<<24-s%4*8,s++);return t.create(i,s)}e.enc.Base64={stringify:function(e){var t=e.words,n=e.sigBytes,r=this._map;e.clamp();for(var i=[],s=0;s>>2]>>>24-s%4*8&255)<<16|(t[s+1>>>2]>>>24-(s+1)%4*8&255)<<8|t[s+2>>>2]>>>24-(s+2)%4*8&255,o=0;o<4&&s+.75*o>>6*(3-o)&63));var c=r.charAt(64);if(c)for(;i.length%4;)i.push(c);return i.join("")},parse:function(e){var t=e.length,n=this._map;if(!(i=this._reverseMap))for(var i=this._reverseMap=[],s=0;s>>6-a%4*2,i[s>>>2]|=o<<24-s%4*8,s++);return t.create(i,s)}e.enc.Base64url={stringify:function(e,t=!0){var n=e.words,r=e.sigBytes,i=t?this._safe_map:this._map;e.clamp();for(var s=[],a=0;a>>2]>>>24-a%4*8&255)<<16|(n[a+1>>>2]>>>24-(a+1)%4*8&255)<<8|n[a+2>>>2]>>>24-(a+2)%4*8&255,c=0;c<4&&a+.75*c>>6*(3-c)&63));var u=i.charAt(64);if(u)for(;s.length%4;)s.push(u);return s.join("")},parse:function(e,t=!0){var n=e.length,i=t?this._safe_map:this._map;if(!(s=this._reverseMap))for(var s=this._reverseMap=[],a=0;a>>24)|4278255360&(i<<24|i>>>8)}var s=this._hash.words,a=e[t+0],c=e[t+1],p=e[t+2],d=e[t+3],m=e[t+4],_=e[t+5],E=e[t+6],T=e[t+7],A=e[t+8],g=e[t+9],v=e[t+10],y=e[t+11],S=e[t+12],C=e[t+13],N=e[t+14],b=e[t+15],I=u(s[0],O=s[1],k=s[2],L=s[3],a,7,o[0]),L=u(L,I,O,k,c,12,o[1]),k=u(k,L,I,O,p,17,o[2]),O=u(O,k,L,I,d,22,o[3]),I=u(I,O,k,L,m,7,o[4]),L=u(L,I,O,k,_,12,o[5]),k=u(k,L,I,O,E,17,o[6]),O=u(O,k,L,I,T,22,o[7]);I=u(I,O,k,L,A,7,o[8]),L=u(L,I,O,k,g,12,o[9]),k=u(k,L,I,O,v,17,o[10]),O=u(O,k,L,I,y,22,o[11]),I=u(I,O,k,L,S,7,o[12]),L=u(L,I,O,k,C,12,o[13]),k=u(k,L,I,O,N,17,o[14]),I=l(I,O=u(O,k,L,I,b,22,o[15]),k,L,c,5,o[16]),L=l(L,I,O,k,E,9,o[17]),k=l(k,L,I,O,y,14,o[18]),O=l(O,k,L,I,a,20,o[19]),I=l(I,O,k,L,_,5,o[20]),L=l(L,I,O,k,v,9,o[21]),k=l(k,L,I,O,b,14,o[22]),O=l(O,k,L,I,m,20,o[23]),I=l(I,O,k,L,g,5,o[24]),L=l(L,I,O,k,N,9,o[25]),k=l(k,L,I,O,d,14,o[26]),O=l(O,k,L,I,A,20,o[27]),I=l(I,O,k,L,C,5,o[28]),L=l(L,I,O,k,p,9,o[29]),k=l(k,L,I,O,T,14,o[30]),I=h(I,O=l(O,k,L,I,S,20,o[31]),k,L,_,4,o[32]),L=h(L,I,O,k,A,11,o[33]),k=h(k,L,I,O,y,16,o[34]),O=h(O,k,L,I,N,23,o[35]),I=h(I,O,k,L,c,4,o[36]),L=h(L,I,O,k,m,11,o[37]),k=h(k,L,I,O,T,16,o[38]),O=h(O,k,L,I,v,23,o[39]),I=h(I,O,k,L,C,4,o[40]),L=h(L,I,O,k,a,11,o[41]),k=h(k,L,I,O,d,16,o[42]),O=h(O,k,L,I,E,23,o[43]),I=h(I,O,k,L,g,4,o[44]),L=h(L,I,O,k,S,11,o[45]),k=h(k,L,I,O,b,16,o[46]),I=f(I,O=h(O,k,L,I,p,23,o[47]),k,L,a,6,o[48]),L=f(L,I,O,k,T,10,o[49]),k=f(k,L,I,O,N,15,o[50]),O=f(O,k,L,I,_,21,o[51]),I=f(I,O,k,L,S,6,o[52]),L=f(L,I,O,k,d,10,o[53]),k=f(k,L,I,O,v,15,o[54]),O=f(O,k,L,I,c,21,o[55]),I=f(I,O,k,L,A,6,o[56]),L=f(L,I,O,k,b,10,o[57]),k=f(k,L,I,O,E,15,o[58]),O=f(O,k,L,I,C,21,o[59]),I=f(I,O,k,L,m,6,o[60]),L=f(L,I,O,k,y,10,o[61]),k=f(k,L,I,O,p,15,o[62]),O=f(O,k,L,I,g,21,o[63]),s[0]=s[0]+I|0,s[1]=s[1]+O|0,s[2]=s[2]+k|0,s[3]=s[3]+L|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes,s=(n[i>>>5]|=128<<24-i%32,e.floor(r/4294967296));n[15+(64+i>>>9<<4)]=16711935&(s<<8|s>>>24)|4278255360&(s<<24|s>>>8),n[14+(64+i>>>9<<4)]=16711935&(r<<8|r>>>24)|4278255360&(r<<24|r>>>8),t.sigBytes=4*(n.length+1),this._process();for(var c=(s=this._hash).words,u=0;u<4;u++){var l=c[u];c[u]=16711935&(l<<8|l>>>24)|4278255360&(l<<24|l>>>8)}return s},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}}));function u(e,t,n,r,i,s,a){return((e=e+(t&n|~t&r)+i+a)<>>32-s)+t}function l(e,t,n,r,i,s,a){return((e=e+(t&r|n&~r)+i+a)<>>32-s)+t}function h(e,t,n,r,i,s,a){return((e=e+(t^n^r)+i+a)<>>32-s)+t}function f(e,t,n,r,i,s,a){return((e=e+(n^(t|~r))+i+a)<>>32-s)+t}t.MD5=s._createHelper(r),t.HmacMD5=s._createHmacHelper(r)}(Math),n.MD5)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,r=(t=e.lib).WordArray,i=t.Hasher,t=e.algo,a=[],t=t.SHA1=i.extend({_doReset:function(){this._hash=new r.init([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],s=n[2],o=n[3],c=n[4],u=0;u<80;u++){a[u]=u<16?0|e[t+u]:(l=a[u-3]^a[u-8]^a[u-14]^a[u-16])<<1|l>>>31;var l=(r<<5|r>>>27)+c+a[u];l+=u<20?1518500249+(i&s|~i&o):u<40?1859775393+(i^s^o):u<60?(i&s|i&o|s&o)-1894007588:(i^s^o)-899497514,c=o,o=s,s=i<<30|i>>>2,i=r,r=l}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+s|0,n[3]=n[3]+o|0,n[4]=n[4]+c|0},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,r=8*e.sigBytes;return t[r>>>5]|=128<<24-r%32,t[14+(64+r>>>9<<4)]=Math.floor(n/4294967296),t[15+(64+r>>>9<<4)]=n,e.sigBytes=4*t.length,this._process(),this._hash},clone:function(){var e=i.clone.call(this);return e._hash=this._hash.clone(),e}});e.SHA1=i._createHelper(t),e.HmacSHA1=i._createHmacHelper(t)}(),n.SHA1)}),Sr(function(e,t){var n;e.exports=(n=dh,function(e){var t=n,i=(r=t.lib).WordArray,s=r.Hasher,r=t.algo,o=[],c=[],u=(function(){function n(e){return 4294967296*(e-(0|e))|0}for(var r=2,i=0;i<64;)!function(t){for(var n=e.sqrt(t),r=2;r<=n;r++)if(!(t%r))return;return 1}(r)||(i<8&&(o[i]=n(e.pow(r,.5))),c[i]=n(e.pow(r,1/3)),i++),r++}(),[]),r=r.SHA256=s.extend({_doReset:function(){this._hash=new i.init(o.slice(0))},_doProcessBlock:function(e,t){for(var n=this._hash.words,r=n[0],i=n[1],s=n[2],a=n[3],o=n[4],l=n[5],h=n[6],f=n[7],p=0;p<64;p++){u[p]=p<16?0|e[t+p]:(((d=u[p-15])<<25|d>>>7)^(d<<14|d>>>18)^d>>>3)+u[p-7]+(((d=u[p-2])<<15|d>>>17)^(d<<13|d>>>19)^d>>>10)+u[p-16];var d=r&i^r&s^i&s,g=f+((o<<26|o>>>6)^(o<<21|o>>>11)^(o<<7|o>>>25))+(o&l^~o&h)+c[p]+u[p],f=h,h=l,l=o,o=a+g|0,a=s,s=i,i=r,r=g+(((r<<30|r>>>2)^(r<<19|r>>>13)^(r<<10|r>>>22))+d)|0}n[0]=n[0]+r|0,n[1]=n[1]+i|0,n[2]=n[2]+s|0,n[3]=n[3]+a|0,n[4]=n[4]+o|0,n[5]=n[5]+l|0,n[6]=n[6]+h|0,n[7]=n[7]+f|0},_doFinalize:function(){var t=this._data,n=t.words,r=8*this._nDataBytes,i=8*t.sigBytes;return n[i>>>5]|=128<<24-i%32,n[14+(64+i>>>9<<4)]=e.floor(r/4294967296),n[15+(64+i>>>9<<4)]=r,t.sigBytes=4*n.length,this._process(),this._hash},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}});t.SHA256=s._createHelper(r),t.HmacSHA256=s._createHmacHelper(r)}(Math),n.SHA256)}),Sr(function(e,t){var n,r,s,i;e.exports=(r=(n=e=dh).lib.WordArray,i=n.algo,s=i.SHA256,i=i.SHA224=s.extend({_doReset:function(){this._hash=new r.init([3238371032,914150663,812702999,4144912697,4290775857,1750603025,1694076839,3204075428])},_doFinalize:function(){var e=s._doFinalize.call(this);return e.sigBytes-=4,e}}),n.SHA224=s._createHelper(i),n.HmacSHA224=s._createHmacHelper(i),e.SHA224)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.Hasher,i=(r=e.x64).Word,s=r.WordArray,r=e.algo;function o(){return i.create.apply(i,arguments)}var c=[o(1116352408,3609767458),o(1899447441,602891725),o(3049323471,3964484399),o(3921009573,2173295548),o(961987163,4081628472),o(1508970993,3053834265),o(2453635748,2937671579),o(2870763221,3664609560),o(3624381080,2734883394),o(310598401,1164996542),o(607225278,1323610764),o(1426881987,3590304994),o(1925078388,4068182383),o(2162078206,991336113),o(2614888103,633803317),o(3248222580,3479774868),o(3835390401,2666613458),o(4022224774,944711139),o(264347078,2341262773),o(604807628,2007800933),o(770255983,1495990901),o(1249150122,1856431235),o(1555081692,3175218132),o(1996064986,2198950837),o(2554220882,3999719339),o(2821834349,766784016),o(2952996808,2566594879),o(3210313671,3203337956),o(3336571891,1034457026),o(3584528711,2466948901),o(113926993,3758326383),o(338241895,168717936),o(666307205,1188179964),o(773529912,1546045734),o(1294757372,1522805485),o(1396182291,2643833823),o(1695183700,2343527390),o(1986661051,1014477480),o(2177026350,1206759142),o(2456956037,344077627),o(2730485921,1290863460),o(2820302411,3158454273),o(3259730800,3505952657),o(3345764771,106217008),o(3516065817,3606008344),o(3600352804,1432725776),o(4094571909,1467031594),o(275423344,851169720),o(430227734,3100823752),o(506948616,1363258195),o(659060556,3750685593),o(883997877,3785050280),o(958139571,3318307427),o(1322822218,3812723403),o(1537002063,2003034995),o(1747873779,3602036899),o(1955562222,1575990012),o(2024104815,1125592928),o(2227730452,2716904306),o(2361852424,442776044),o(2428436474,593698344),o(2756734187,3733110249),o(3204031479,2999351573),o(3329325298,3815920427),o(3391569614,3928383900),o(3515267271,566280711),o(3940187606,3454069534),o(4118630271,4000239992),o(116418474,1914138554),o(174292421,2731055270),o(289380356,3203993006),o(460393269,320620315),o(685471733,587496836),o(852142971,1086792851),o(1017036298,365543100),o(1126000580,2618297676),o(1288033470,3409855158),o(1501505948,4234509866),o(1607167915,987167468),o(1816402316,1246189591)],u=[],r=(function(){for(var e=0;e<80;e++)u[e]=o()}(),r.SHA512=t.extend({_doReset:function(){this._hash=new s.init([new i.init(1779033703,4089235720),new i.init(3144134277,2227873595),new i.init(1013904242,4271175723),new i.init(2773480762,1595750129),new i.init(1359893119,2917565137),new i.init(2600822924,725511199),new i.init(528734635,4215389547),new i.init(1541459225,327033209)])},_doProcessBlock:function(e,t){for(var r=(n=this._hash.words)[0],i=n[1],s=n[2],a=n[3],o=n[4],l=n[5],h=n[6],n=n[7],p=r.high,d=r.low,m=i.high,_=i.low,E=s.high,T=s.low,A=a.high,g=a.low,v=o.high,y=o.low,S=l.high,C=l.low,N=h.high,b=h.low,I=n.high,O=n.low,k=p,L=d,D=m,R=_,w=E,x=T,M=A,P=g,B=v,F=y,U=S,H=C,G=N,j=b,q=I,Y=O,K=0;K<80;K++){var W,V,$=u[K];K<16?(V=$.high=0|e[t+2*K],W=$.low=0|e[t+2*K+1]):(z=(Q=u[K-15]).high,Q=Q.low,te=(ee=u[K-2]).high,ee=ee.low,V=(V=(V=((z>>>1|Q<<31)^(z>>>8|Q<<24)^z>>>7)+(se=u[K-7]).high+((W=(Q=(Q>>>1|z<<31)^(Q>>>8|z<<24)^(Q>>>7|z<<25))+se.low)>>>0>>0?1:0))+((te>>>19|ee<<13)^(te<<3|ee>>>29)^te>>>6)+((W+=z=(ee>>>19|te<<13)^(ee<<3|te>>>29)^(ee>>>6|te<<26))>>>0>>0?1:0))+(se=u[K-16]).high+((W+=Q=se.low)>>>0>>0?1:0),$.high=V,$.low=W);var he,ee=B&U^~B&G,te=F&H^~F&j,z=k&D^k&w^D&w,se=(L>>>28|k<<4)^(L<<30|k>>>2)^(L<<25|k>>>7),Q=c[K],$=Q.high,ye=Q.low,Se=q+((B>>>14|F<<18)^(B>>>18|F<<14)^(B<<23|F>>>9))+((he=Y+((F>>>14|B<<18)^(F>>>18|B<<14)^(F<<23|B>>>9)))>>>0>>0?1:0),Ce=se+(L&R^L&x^R&x),q=G,Y=j,G=U,j=H,U=B,H=F,B=M+(Se=Se+ee+((he+=te)>>>0>>0?1:0)+$+((he+=ye)>>>0>>0?1:0)+V+((he+=W)>>>0>>0?1:0))+((F=P+he|0)>>>0

>>0?1:0)|0,M=w,P=x,w=D,x=R,D=k,R=L,k=Se+(((k>>>28|L<<4)^(k<<30|L>>>2)^(k<<25|L>>>7))+z+(Ce>>>0>>0?1:0))+((L=he+Ce|0)>>>0>>0?1:0)|0}d=r.low=d+L,r.high=p+k+(d>>>0>>0?1:0),_=i.low=_+R,i.high=m+D+(_>>>0>>0?1:0),T=s.low=T+x,s.high=E+w+(T>>>0>>0?1:0),g=a.low=g+P,a.high=A+M+(g>>>0

>>0?1:0),y=o.low=y+F,o.high=v+B+(y>>>0>>0?1:0),C=l.low=C+H,l.high=S+U+(C>>>0>>0?1:0),b=h.low=b+j,h.high=N+G+(b>>>0>>0?1:0),O=n.low=O+Y,n.high=I+q+(O>>>0>>0?1:0)},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,r=8*e.sigBytes;return t[r>>>5]|=128<<24-r%32,t[30+(128+r>>>10<<5)]=Math.floor(n/4294967296),t[31+(128+r>>>10<<5)]=n,e.sigBytes=4*t.length,this._process(),this._hash.toX32()},clone:function(){var e=t.clone.call(this);return e._hash=this._hash.clone(),e},blockSize:32}));e.SHA512=t._createHelper(r),e.HmacSHA512=t._createHmacHelper(r)}(),n.SHA512)}),Sr(function(e,t){var n,i,s,o,r;e.exports=(r=(n=e=dh).x64,i=r.Word,s=r.WordArray,r=n.algo,o=r.SHA512,r=r.SHA384=o.extend({_doReset:function(){this._hash=new s.init([new i.init(3418070365,3238371032),new i.init(1654270250,914150663),new i.init(2438529370,812702999),new i.init(355462360,4144912697),new i.init(1731405415,4290775857),new i.init(2394180231,1750603025),new i.init(3675008525,1694076839),new i.init(1203062813,3204075428)])},_doFinalize:function(){var e=o._doFinalize.call(this);return e.sigBytes-=16,e}}),n.SHA384=o._createHelper(r),n.HmacSHA384=o._createHmacHelper(r),e.SHA384)}),Sr(function(e,t){var n;e.exports=(n=dh,function(e){var t=n,i=(r=t.lib).WordArray,s=r.Hasher,a=t.x64.Word,r=t.algo,c=[],u=[],l=[],h=(function(){for(var e=1,t=0,n=0;n<24;n++){c[e+5*t]=(n+1)*(n+2)/2%64;var r=(2*e+3*t)%5,e=t%5,t=r}for(e=0;e<5;e++)for(t=0;t<5;t++)u[e+5*t]=t+(2*e+3*t)%5*5;for(var i=1,s=0;s<24;s++){for(var p,o=0,h=0,f=0;f<7;f++)1&i&&((p=(1<>>24)|4278255360&(s<<24|s>>>8);(O=n[i]).high^=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8),O.low^=s}for(var o=0;o<24;o++){for(var f=0;f<5;f++){for(var p=0,d=0,m=0;m<5;m++)p^=(O=n[f+5*m]).high,d^=O.low;var _=h[f];_.high=p,_.low=d}for(f=0;f<5;f++)for(var E=h[(f+4)%5],A=(T=h[(f+1)%5]).high,T=T.low,p=E.high^(A<<1|T>>>31),d=E.low^(T<<1|A>>>31),m=0;m<5;m++)(O=n[f+5*m]).high^=p,O.low^=d;for(var v=1;v<25;v++){var y=(O=n[v]).high,S=O.low,C=c[v];d=C<32?(p=y<>>32-C,S<>>32-C):(p=S<>>64-C,y<>>64-C),(y=h[u[v]]).high=p,y.low=d}var b=h[0],I=n[0];for(b.high=I.high,b.low=I.low,f=0;f<5;f++)for(m=0;m<5;m++){var O=n[v=f+5*m],k=h[v],L=h[(f+1)%5+5*m],D=h[(f+2)%5+5*m];O.high=k.high^~L.high&D.high,O.low=k.low^~L.low&D.low}(O=n[0]).high^=(b=l[o]).high,O.low^=b.low}},_doFinalize:function(){var t=this._data,n=t.words,r=(this._nDataBytes,8*t.sigBytes),s=32*this.blockSize;n[r>>>5]|=1<<24-r%32,n[(e.ceil((1+r)/s)*s>>>5)-1]|=128,t.sigBytes=4*n.length,this._process();for(var a=this._state,c=(r=this.cfg.outputLength/8)/8,u=[],l=0;l>>24)|4278255360&(f<<24|f>>>8);u.push(16711935&(h<<8|h>>>24)|4278255360&(h<<24|h>>>8)),u.push(f)}return new i.init(u,r)},clone:function(){for(var e=s.clone.call(this),t=e._state=this._state.slice(0),n=0;n<25;n++)t[n]=t[n].clone();return e}}));t.SHA3=s._createHelper(r),t.HmacSHA3=s._createHmacHelper(r)}(Math),n.SHA3)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var t=n,i=(r=t.lib).WordArray,s=r.Hasher,r=t.algo,o=i.create([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,7,4,13,1,10,6,15,3,12,0,9,5,2,14,11,8,3,10,14,4,9,15,8,1,2,7,0,6,13,11,5,12,1,9,11,10,0,8,12,4,13,3,7,15,14,5,6,2,4,0,5,9,7,12,2,10,14,1,3,8,11,6,15,13]),c=i.create([5,14,7,0,9,2,11,4,13,6,15,8,1,10,3,12,6,11,3,7,0,13,5,10,14,15,8,12,4,9,1,2,15,5,1,3,7,14,6,9,11,8,12,2,10,0,4,13,8,6,4,1,3,11,15,0,5,12,2,13,9,7,10,14,12,15,10,4,1,5,8,7,6,2,13,14,0,3,9,11]),u=i.create([11,14,15,12,5,8,7,9,11,13,14,15,6,7,9,8,7,6,8,13,11,9,7,15,7,12,15,9,11,7,13,12,11,13,6,7,14,9,13,15,14,8,13,6,5,12,7,5,11,12,14,15,14,15,9,8,9,14,5,6,8,6,5,12,9,15,5,11,6,8,13,12,5,12,13,14,11,8,5,6]),l=i.create([8,9,9,11,13,15,15,5,7,7,8,11,14,14,12,6,9,13,15,7,12,8,9,11,7,7,12,7,6,15,13,11,9,7,15,11,8,6,6,14,12,13,5,14,13,13,7,5,15,5,8,11,14,14,6,14,6,9,12,9,12,5,15,8,8,5,12,9,12,5,14,6,8,13,6,5,15,13,11,11]),h=i.create([0,1518500249,1859775393,2400959708,2840853838]),f=i.create([1352829926,1548603684,1836072691,2053994217,0]),r=r.RIPEMD160=s.extend({_doReset:function(){this._hash=i.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(e,t){for(var n=0;n<16;n++){var r=t+n,i=e[r];e[r]=16711935&(i<<8|i>>>24)|4278255360&(i<<24|i>>>8)}for(var s,a,p,g,v,I,O=this._hash.words,k=h.words,L=f.words,D=o.words,R=c.words,w=u.words,x=l.words,y=s=O[0],S=a=O[1],C=p=O[2],N=g=O[3],b=v=O[4],n=0;n<80;n+=1)I=(I=A(I=(I=s+e[t+D[n]]|0)+(n<16?(a^p^g)+k[0]:n<32?m(a,p,g)+k[1]:n<48?((a|~p)^g)+k[2]:n<64?E(a,p,g)+k[3]:(a^(p|~g))+k[4])|0,w[n]))+v|0,s=v,v=g,g=A(p,10),p=a,a=I,I=(I=A(I=(I=y+e[t+R[n]]|0)+(n<16?(S^(C|~N))+L[0]:n<32?E(S,C,N)+L[1]:n<48?((S|~C)^N)+L[2]:n<64?m(S,C,N)+L[3]:(S^C^N)+L[4])|0,x[n]))+b|0,y=b,b=N,N=A(C,10),C=S,S=I;I=O[1]+p+N|0,O[1]=O[2]+g+b|0,O[2]=O[3]+v+y|0,O[3]=O[4]+s+S|0,O[4]=O[0]+a+C|0,O[0]=I},_doFinalize:function(){var e=this._data,t=e.words,n=8*this._nDataBytes,r=8*e.sigBytes;t[r>>>5]|=128<<24-r%32,t[14+(64+r>>>9<<4)]=16711935&(n<<8|n>>>24)|4278255360&(n<<24|n>>>8),e.sigBytes=4*(t.length+1),this._process();for(var s=(r=this._hash).words,a=0;a<5;a++){var o=s[a];s[a]=16711935&(o<<8|o>>>24)|4278255360&(o<<24|o>>>8)}return r},clone:function(){var e=s.clone.call(this);return e._hash=this._hash.clone(),e}});function m(e,t,n){return e&t|~e&n}function E(e,t,n){return e&n|t&~n}function A(e,t){return e<>>32-t}t.RIPEMD160=s._createHelper(r),t.HmacRIPEMD160=s._createHmacHelper(r)}(),n.RIPEMD160)}),Sr(function(e,t){var i;e.exports=(e=dh.lib.Base,i=dh.enc.Utf8,void(dh.algo.HMAC=e.extend({init:function(e,t){e=this._hasher=new e.init,"string"==typeof t&&(t=i.parse(t));var n=e.blockSize,r=4*n;(t=t.sigBytes>r?e.finalize(t):t).clamp();for(var e=this._oKey=t.clone(),t=this._iKey=t.clone(),o=e.words,c=t.words,u=0;u>>2];e.sigBytes-=t}},m=(r.BlockCipher=l.extend({cfg:l.cfg.extend({mode:o,padding:d}),reset:function(){l.reset.call(this);var e,n=(t=this.cfg).iv,t=t.mode;this._xformMode==this._ENC_XFORM_MODE?e=t.createEncryptor:(e=t.createDecryptor,this._minBufferSize=1),this._mode&&this._mode.__creator==e?this._mode.init(this,n&&n.words):(this._mode=e.call(t,this,n&&n.words),this._mode.__creator=e)},_doProcessBlock:function(e,t){this._mode.processBlock(e,t)},_doFinalize:function(){var e,t=this.cfg.padding;return this._xformMode==this._ENC_XFORM_MODE?(t.pad(this._data,this.blockSize),e=this._process(!0)):(e=this._process(!0),t.unpad(e)),e},blockSize:4}),r.CipherParams=i.extend({init:function(e){this.mixIn(e)},toString:function(e){return(e||this.formatter).stringify(this)}})),o=(t.format={}).OpenSSL={stringify:function(e){var t=e.ciphertext;return((e=e.salt)?s.create([1398893684,1701076831]).concat(e).concat(t):t).toString(c)},parse:function(e){var t,r=(e=c.parse(e)).words;return 1398893684==r[0]&&1701076831==r[1]&&(t=s.create(r.slice(2,4)),r.splice(0,4),e.sigBytes-=16),m.create({ciphertext:e,salt:t})}},E=r.SerializableCipher=i.extend({cfg:i.extend({format:o}),encrypt:function(e,t,n,r){r=this.cfg.extend(r);var t=(i=e.createEncryptor(n,r)).finalize(t),i=i.cfg;return m.create({ciphertext:t,key:n,iv:i.iv,algorithm:e,mode:i.mode,padding:i.padding,blockSize:e.blockSize,formatter:r.format})},decrypt:function(e,t,n,r){return r=this.cfg.extend(r),t=this._parse(t,r.format),e.createDecryptor(n,r).finalize(t.ciphertext)},_parse:function(e,t){return"string"==typeof e?t.parse(e,this):e}}),d=(t.kdf={}).OpenSSL={execute:function(e,t,n,r){return r=r||s.random(8),e=u.create({keySize:t+n}).compute(e,r),n=s.create(e.words.slice(t),4*n),e.sigBytes=4*t,m.create({key:e,iv:n,salt:r})}},A=r.PasswordBasedCipher=E.extend({cfg:E.cfg.extend({kdf:d}),encrypt:function(e,t,n,r){return n=(r=this.cfg.extend(r)).kdf.execute(n,e.keySize,e.ivSize),r.iv=n.iv,(e=E.encrypt.call(this,e,t,n.key,r)).mixIn(n),e},decrypt:function(e,t,n,r){return r=this.cfg.extend(r),t=this._parse(t,r.format),n=r.kdf.execute(n,e.keySize,e.ivSize,t.salt),r.iv=n.iv,E.decrypt.call(this,e,t,n.key,r)}});function e(e){return"string"==typeof e?A:E}}())}),Sr(function(e,t){var n;e.exports=((n=dh).mode.CFB=function(){var e=n.lib.BlockCipherMode.extend();function t(e,t,n,r){var i,s=this._iv;s?(i=s.slice(0),this._iv=void 0):i=this._prevBlock,r.encryptBlock(i,0);for(var a=0;a>24&255)?(n=e>>8&255,r=255&e,255==(t=e>>16&255)?(t=0,255===n?(n=0,255===r?r=0:++r):++n):++t,e=0,e=(e+=t<<16)+(n<<8)+r):e+=1<<24,e}function r(e){0===(e[0]=t(e[0]))&&(e[1]=t(e[1]))}var i=e.Encryptor=e.extend({processBlock:function(e,t){var n=this._cipher,i=n.blockSize,s=this._iv,a=this._counter,o=(s&&(a=this._counter=s.slice(0),this._iv=void 0),r(a),a.slice(0));n.encryptBlock(o,0);for(var c=0;c>>2]|=t<<24-n%4*8,e.sigBytes+=t},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},dh.pad.Ansix923)}),Sr(function(e,t){var n;e.exports=((n=dh).pad.Iso10126={pad:function(e,t){t=(t*=4)-e.sigBytes%t,e.concat(n.lib.WordArray.random(t-1)).concat(n.lib.WordArray.create([t<<24],1))},unpad:function(e){var t=255&e.words[e.sigBytes-1>>>2];e.sigBytes-=t}},n.pad.Iso10126)}),Sr(function(e,t){var n;e.exports=((n=dh).pad.Iso97971={pad:function(e,t){e.concat(n.lib.WordArray.create([2147483648],1)),n.pad.ZeroPadding.pad(e,t)},unpad:function(e){n.pad.ZeroPadding.unpad(e),e.sigBytes--}},n.pad.Iso97971)}),Sr(function(e,t){e.exports=(dh.pad.ZeroPadding={pad:function(e,t){t*=4,e.clamp(),e.sigBytes+=t-(e.sigBytes%t||t)},unpad:function(e){for(var t=e.words,n=e.sigBytes-1,n=e.sigBytes-1;0<=n;n--)if(t[n>>>2]>>>24-n%4*8&255){e.sigBytes=n+1;break}}},dh.pad.ZeroPadding)}),Sr(function(e,t){e.exports=(dh.pad.NoPadding={pad:function(){},unpad:function(){}},dh.pad.NoPadding)}),Sr(function(e,t){var r,i;e.exports=(r=(e=dh).lib.CipherParams,i=e.enc.Hex,e.format.Hex={stringify:function(e){return e.ciphertext.toString(i)},parse:function(e){return e=i.parse(e),r.create({ciphertext:e})}},e.format.Hex)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.BlockCipher,r=e.algo,i=[],s=[],a=[],o=[],c=[],u=[],l=[],h=[],f=[],p=[],d=(function(){for(var e=[],t=0;t<256;t++)e[t]=t<128?t<<1:t<<1^283;for(var n=0,r=0,t=0;t<256;t++){var d=r^r<<1^r<<2^r<<3^r<<4,m=(i[n]=d=d>>>8^255&d^99,e[s[d]=n]),_=e[m],E=e[_],T=257*e[d]^16843008*d;a[n]=T<<24|T>>>8,o[n]=T<<16|T>>>16,c[n]=T<<8|T>>>24,u[n]=T,l[d]=(T=16843009*E^65537*_^257*m^16843008*n)<<24|T>>>8,h[d]=T<<16|T>>>16,f[d]=T<<8|T>>>24,p[d]=T,n?(n=m^e[e[e[E^m]]],r^=e[e[r]]):n=r=1}}(),[0,1,2,4,8,16,32,64,128,27,54]),r=r.AES=t.extend({_doReset:function(){if(!this._nRounds||this._keyPriorReset!==this._key){for(var e=this._keyPriorReset=this._key,t=e.words,n=e.sigBytes/4,r=4*(1+(this._nRounds=6+n)),s=this._keySchedule=[],a=0;a>>24]<<24|i[u>>>16&255]<<16|i[u>>>8&255]<<8|i[255&u]):(u=i[(u=u<<8|u>>>24)>>>24]<<24|i[u>>>16&255]<<16|i[u>>>8&255]<<8|i[255&u],u^=d[a/n|0]<<24),s[a]=s[a-n]^u);for(var o=this._invKeySchedule=[],c=0;c>>24]]^h[i[u>>>16&255]]^f[i[u>>>8&255]]^p[i[255&u]]}}},encryptBlock:function(e,t){this._doCryptBlock(e,t,this._keySchedule,a,o,c,u,i)},decryptBlock:function(e,t){var n=e[t+1];e[t+1]=e[t+3],e[t+3]=n,this._doCryptBlock(e,t,this._invKeySchedule,l,h,f,p,s),n=e[t+1],e[t+1]=e[t+3],e[t+3]=n},_doCryptBlock:function(e,t,n,r,i,s,a,o){for(var c=this._nRounds,u=e[t]^n[0],l=e[t+1]^n[1],h=e[t+2]^n[2],f=e[t+3]^n[3],p=4,d=1;d>>24]^i[l>>>16&255]^s[h>>>8&255]^a[255&f]^n[p++],_=r[l>>>24]^i[h>>>16&255]^s[f>>>8&255]^a[255&u]^n[p++],E=r[h>>>24]^i[f>>>16&255]^s[u>>>8&255]^a[255&l]^n[p++],T=r[f>>>24]^i[u>>>16&255]^s[l>>>8&255]^a[255&h]^n[p++],u=m,l=_,h=E,f=T;m=(o[u>>>24]<<24|o[l>>>16&255]<<16|o[h>>>8&255]<<8|o[255&f])^n[p++],_=(o[l>>>24]<<24|o[h>>>16&255]<<16|o[f>>>8&255]<<8|o[255&u])^n[p++],E=(o[h>>>24]<<24|o[f>>>16&255]<<16|o[u>>>8&255]<<8|o[255&l])^n[p++],T=(o[f>>>24]<<24|o[u>>>16&255]<<16|o[l>>>8&255]<<8|o[255&h])^n[p++],e[t]=m,e[t+1]=_,e[t+2]=E,e[t+3]=T},keySize:8});e.AES=t._createHelper(r)}(),n.AES)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,r=(t=e.lib).WordArray,t=t.BlockCipher,s=e.algo,a=[57,49,41,33,25,17,9,1,58,50,42,34,26,18,10,2,59,51,43,35,27,19,11,3,60,52,44,36,63,55,47,39,31,23,15,7,62,54,46,38,30,22,14,6,61,53,45,37,29,21,13,5,28,20,12,4],o=[14,17,11,24,1,5,3,28,15,6,21,10,23,19,12,4,26,8,16,7,27,20,13,2,41,52,31,37,47,55,30,40,51,45,33,48,44,49,39,56,34,53,46,42,50,36,29,32],c=[1,2,4,6,8,10,12,14,15,17,19,21,23,25,27,28],u=[{0:8421888,268435456:32768,536870912:8421378,805306368:2,1073741824:512,1342177280:8421890,1610612736:8389122,1879048192:8388608,2147483648:514,2415919104:8389120,2684354560:33280,2952790016:8421376,3221225472:32770,3489660928:8388610,3758096384:0,4026531840:33282,134217728:0,402653184:8421890,671088640:33282,939524096:32768,1207959552:8421888,1476395008:512,1744830464:8421378,2013265920:2,2281701376:8389120,2550136832:33280,2818572288:8421376,3087007744:8389122,3355443200:8388610,3623878656:32770,3892314112:514,4160749568:8388608,1:32768,268435457:2,536870913:8421888,805306369:8388608,1073741825:8421378,1342177281:33280,1610612737:512,1879048193:8389122,2147483649:8421890,2415919105:8421376,2684354561:8388610,2952790017:33282,3221225473:514,3489660929:8389120,3758096385:32770,4026531841:0,134217729:8421890,402653185:8421376,671088641:8388608,939524097:512,1207959553:32768,1476395009:8388610,1744830465:2,2013265921:33282,2281701377:32770,2550136833:8389122,2818572289:514,3087007745:8421888,3355443201:8389120,3623878657:0,3892314113:33280,4160749569:8421378},{0:1074282512,16777216:16384,33554432:524288,50331648:1074266128,67108864:1073741840,83886080:1074282496,100663296:1073758208,117440512:16,134217728:540672,150994944:1073758224,167772160:1073741824,184549376:540688,201326592:524304,218103808:0,234881024:16400,251658240:1074266112,8388608:1073758208,25165824:540688,41943040:16,58720256:1073758224,75497472:1074282512,92274688:1073741824,109051904:524288,125829120:1074266128,142606336:524304,159383552:0,176160768:16384,192937984:1074266112,209715200:1073741840,226492416:540672,243269632:1074282496,260046848:16400,268435456:0,285212672:1074266128,301989888:1073758224,318767104:1074282496,335544320:1074266112,352321536:16,369098752:540688,385875968:16384,402653184:16400,419430400:524288,436207616:524304,452984832:1073741840,469762048:540672,486539264:1073758208,503316480:1073741824,520093696:1074282512,276824064:540688,293601280:524288,310378496:1074266112,327155712:16384,343932928:1073758208,360710144:1074282512,377487360:16,394264576:1073741824,411041792:1074282496,427819008:1073741840,444596224:1073758224,461373440:524304,478150656:0,494927872:16400,511705088:1074266128,528482304:540672},{0:260,1048576:0,2097152:67109120,3145728:65796,4194304:65540,5242880:67108868,6291456:67174660,7340032:67174400,8388608:67108864,9437184:67174656,10485760:65792,11534336:67174404,12582912:67109124,13631488:65536,14680064:4,15728640:256,524288:67174656,1572864:67174404,2621440:0,3670016:67109120,4718592:67108868,5767168:65536,6815744:65540,7864320:260,8912896:4,9961472:256,11010048:67174400,12058624:65796,13107200:65792,14155776:67109124,15204352:67174660,16252928:67108864,16777216:67174656,17825792:65540,18874368:65536,19922944:67109120,20971520:256,22020096:67174660,23068672:67108868,24117248:0,25165824:67109124,26214400:67108864,27262976:4,28311552:65792,29360128:67174400,30408704:260,31457280:65796,32505856:67174404,17301504:67108864,18350080:260,19398656:67174656,20447232:0,21495808:65540,22544384:67109120,23592960:256,24641536:67174404,25690112:65536,26738688:67174660,27787264:65796,28835840:67108868,29884416:67109124,30932992:67174400,31981568:4,33030144:65792},{0:2151682048,65536:2147487808,131072:4198464,196608:2151677952,262144:0,327680:4198400,393216:2147483712,458752:4194368,524288:2147483648,589824:4194304,655360:64,720896:2147487744,786432:2151678016,851968:4160,917504:4096,983040:2151682112,32768:2147487808,98304:64,163840:2151678016,229376:2147487744,294912:4198400,360448:2151682112,425984:0,491520:2151677952,557056:4096,622592:2151682048,688128:4194304,753664:4160,819200:2147483648,884736:4194368,950272:4198464,1015808:2147483712,1048576:4194368,1114112:4198400,1179648:2147483712,1245184:0,1310720:4160,1376256:2151678016,1441792:2151682048,1507328:2147487808,1572864:2151682112,1638400:2147483648,1703936:2151677952,1769472:4198464,1835008:2147487744,1900544:4194304,1966080:64,2031616:4096,1081344:2151677952,1146880:2151682112,1212416:0,1277952:4198400,1343488:4194368,1409024:2147483648,1474560:2147487808,1540096:64,1605632:2147483712,1671168:4096,1736704:2147487744,1802240:2151678016,1867776:4160,1933312:2151682048,1998848:4194304,2064384:4198464},{0:128,4096:17039360,8192:262144,12288:536870912,16384:537133184,20480:16777344,24576:553648256,28672:262272,32768:16777216,36864:537133056,40960:536871040,45056:553910400,49152:553910272,53248:0,57344:17039488,61440:553648128,2048:17039488,6144:553648256,10240:128,14336:17039360,18432:262144,22528:537133184,26624:553910272,30720:536870912,34816:537133056,38912:0,43008:553910400,47104:16777344,51200:536871040,55296:553648128,59392:16777216,63488:262272,65536:262144,69632:128,73728:536870912,77824:553648256,81920:16777344,86016:553910272,90112:537133184,94208:16777216,98304:553910400,102400:553648128,106496:17039360,110592:537133056,114688:262272,118784:536871040,122880:0,126976:17039488,67584:553648256,71680:16777216,75776:17039360,79872:537133184,83968:536870912,88064:17039488,92160:128,96256:553910272,100352:262272,104448:553910400,108544:0,112640:553648128,116736:16777344,120832:262144,124928:537133056,129024:536871040},{0:268435464,256:8192,512:270532608,768:270540808,1024:268443648,1280:2097152,1536:2097160,1792:268435456,2048:0,2304:268443656,2560:2105344,2816:8,3072:270532616,3328:2105352,3584:8200,3840:270540800,128:270532608,384:270540808,640:8,896:2097152,1152:2105352,1408:268435464,1664:268443648,1920:8200,2176:2097160,2432:8192,2688:268443656,2944:270532616,3200:0,3456:270540800,3712:2105344,3968:268435456,4096:268443648,4352:270532616,4608:270540808,4864:8200,5120:2097152,5376:268435456,5632:268435464,5888:2105344,6144:2105352,6400:0,6656:8,6912:270532608,7168:8192,7424:268443656,7680:270540800,7936:2097160,4224:8,4480:2105344,4736:2097152,4992:268435464,5248:268443648,5504:8200,5760:270540808,6016:270532608,6272:270540800,6528:270532616,6784:8192,7040:2105352,7296:2097160,7552:0,7808:268435456,8064:268443656},{0:1048576,16:33555457,32:1024,48:1049601,64:34604033,80:0,96:1,112:34603009,128:33555456,144:1048577,160:33554433,176:34604032,192:34603008,208:1025,224:1049600,240:33554432,8:34603009,24:0,40:33555457,56:34604032,72:1048576,88:33554433,104:33554432,120:1025,136:1049601,152:33555456,168:34603008,184:1048577,200:1024,216:34604033,232:1,248:1049600,256:33554432,272:1048576,288:33555457,304:34603009,320:1048577,336:33555456,352:34604032,368:1049601,384:1025,400:34604033,416:1049600,432:1,448:0,464:34603008,480:33554433,496:1024,264:1049600,280:33555457,296:34603009,312:1,328:33554432,344:1048576,360:1025,376:34604032,392:33554433,408:34603008,424:0,440:34604033,456:1049601,472:1024,488:33555456,504:1048577},{0:134219808,1:131072,2:134217728,3:32,4:131104,5:134350880,6:134350848,7:2048,8:134348800,9:134219776,10:133120,11:134348832,12:2080,13:0,14:134217760,15:133152,2147483648:2048,2147483649:134350880,2147483650:134219808,2147483651:134217728,2147483652:134348800,2147483653:133120,2147483654:133152,2147483655:32,2147483656:134217760,2147483657:2080,2147483658:131104,2147483659:134350848,2147483660:0,2147483661:134348832,2147483662:134219776,2147483663:131072,16:133152,17:134350848,18:32,19:2048,20:134219776,21:134217760,22:134348832,23:131072,24:0,25:131104,26:134348800,27:134219808,28:134350880,29:133120,30:2080,31:134217728,2147483664:131072,2147483665:2048,2147483666:134348832,2147483667:133152,2147483668:32,2147483669:134348800,2147483670:134217728,2147483671:134219808,2147483672:134350880,2147483673:134217760,2147483674:134219776,2147483675:0,2147483676:133120,2147483677:2080,2147483678:131104,2147483679:134350848}],l=[4160749569,528482304,33030144,2064384,129024,8064,504,2147483679],h=s.DES=t.extend({_doReset:function(){for(var e=this._key.words,t=[],n=0;n<56;n++){var r=a[n]-1;t[n]=e[r>>>5]>>>31-r%32&1}for(var i=this._subKeys=[],s=0;s<16;s++){for(var u=i[s]=[],l=c[s],n=0;n<24;n++)u[n/6|0]|=t[(o[n]-1+l)%28]<<31-n%6,u[4+(n/6|0)]|=t[28+(o[n+24]-1+l)%28]<<31-n%6;for(u[0]=u[0]<<1|u[0]>>>31,n=1;n<7;n++)u[n]=u[n]>>>4*(n-1)+3;u[7]=u[7]<<5|u[7]>>>27}var h=this._invSubKeys=[];for(n=0;n<16;n++)h[n]=i[15-n]},encryptBlock:function(e,t){this._doCryptBlock(e,t,this._subKeys)},decryptBlock:function(e,t){this._doCryptBlock(e,t,this._invSubKeys)},_doCryptBlock:function(e,t,n){this._lBlock=e[t],this._rBlock=e[t+1],f.call(this,4,252645135),f.call(this,16,65535),p.call(this,2,858993459),p.call(this,8,16711935),f.call(this,1,1431655765);for(var r=0;r<16;r++){for(var i=n[r],s=this._lBlock,a=this._rBlock,o=0,c=0;c<8;c++)o|=u[c][((a^i[c])&l[c])>>>0];this._lBlock=a,this._rBlock=s^o}var h=this._lBlock;this._lBlock=this._rBlock,this._rBlock=h,f.call(this,1,1431655765),p.call(this,8,16711935),p.call(this,2,858993459),f.call(this,16,65535),f.call(this,4,252645135),e[t]=this._lBlock,e[t+1]=this._rBlock},keySize:2,ivSize:2,blockSize:2});function f(e,t){t=(this._lBlock>>>e^this._rBlock)&t,this._rBlock^=t,this._lBlock^=t<>>e^this._lBlock)&t,this._lBlock^=t,this._rBlock^=t<192.");var t=e.slice(0,2),n=e.length<4?e.slice(0,2):e.slice(2,4),e=e.length<6?e.slice(0,2):e.slice(4,6);this._des1=h.createEncryptor(r.create(t)),this._des2=h.createEncryptor(r.create(n)),this._des3=h.createEncryptor(r.create(e))},encryptBlock:function(e,t){this._des1.encryptBlock(e,t),this._des2.decryptBlock(e,t),this._des3.encryptBlock(e,t)},decryptBlock:function(e,t){this._des3.decryptBlock(e,t),this._des2.encryptBlock(e,t),this._des1.decryptBlock(e,t)},keySize:6,ivSize:2,blockSize:2}),e.TripleDES=t._createHelper(s)}(),n.TripleDES)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.StreamCipher,r=e.algo,i=r.RC4=t.extend({_doReset:function(){for(var e=this._key,t=e.words,n=e.sigBytes,r=this._S=[],i=0;i<256;i++)r[i]=i;for(var i=0,s=0;i<256;i++){var a=t[(a=i%n)>>>2]>>>24-a%4*8&255,s=(s+r[i]+a)%256,a=r[i];r[i]=r[s],r[s]=a}this._i=this._j=0},_doProcessBlock:function(e,t){e[t]^=s.call(this)},keySize:8,ivSize:0});function s(){for(var e=this._S,t=this._i,n=this._j,r=0,i=0;i<4;i++){var n=(n+e[t=(t+1)%256])%256,s=e[t];e[t]=e[n],e[n]=s,r|=e[(e[t]+e[n])%256]<<24-8*i}return this._i=t,this._j=n,r}e.RC4=t._createHelper(i),r=r.RC4Drop=i.extend({cfg:i.cfg.extend({drop:192}),_doReset:function(){i._doReset.call(this);for(var e=this.cfg.drop;0>>24)|4278255360&(e[n]<<24|e[n]>>>8);for(var r=this._X=[e[0],e[3]<<16|e[2]>>>16,e[1],e[0]<<16|e[3]>>>16,e[2],e[1]<<16|e[0]>>>16,e[3],e[2]<<16|e[1]>>>16],i=this._C=[e[2]<<16|e[2]>>>16,4294901760&e[0]|65535&e[1],e[3]<<16|e[3]>>>16,4294901760&e[1]|65535&e[2],e[0]<<16|e[0]>>>16,4294901760&e[2]|65535&e[3],e[1]<<16|e[1]>>>16,4294901760&e[3]|65535&e[0]],n=this._b=0;n<4;n++)c.call(this);for(n=0;n<8;n++)i[n]^=r[n+4&7];if(t){var a=(t=t.words)[0],h=(a=16711935&(a<<8|a>>>24)|4278255360&(a<<24|a>>>8))>>>16|4294901760&(t=16711935&((t=t[1])<<8|t>>>24)|4278255360&(t<<24|t>>>8)),f=t<<16|65535&a;for(i[0]^=a,i[1]^=h,i[2]^=t,i[3]^=f,i[4]^=a,i[5]^=h,i[6]^=t,i[7]^=f,n=0;n<4;n++)c.call(this)}},_doProcessBlock:function(e,t){var n=this._X;c.call(this),i[0]=n[0]^n[5]>>>16^n[3]<<16,i[1]=n[2]^n[7]>>>16^n[5]<<16,i[2]=n[4]^n[1]>>>16^n[7]<<16,i[3]=n[6]^n[3]>>>16^n[1]<<16;for(var r=0;r<4;r++)i[r]=16711935&(i[r]<<8|i[r]>>>24)|4278255360&(i[r]<<24|i[r]>>>8),e[t+r]^=i[r]},blockSize:4,ivSize:2});function c(){for(var e=this._X,t=this._C,n=0;n<8;n++)s[n]=t[n];for(t[0]=t[0]+1295307597+this._b|0,t[1]=t[1]+3545052371+(t[0]>>>0>>0?1:0)|0,t[2]=t[2]+886263092+(t[1]>>>0>>0?1:0)|0,t[3]=t[3]+1295307597+(t[2]>>>0>>0?1:0)|0,t[4]=t[4]+3545052371+(t[3]>>>0>>0?1:0)|0,t[5]=t[5]+886263092+(t[4]>>>0>>0?1:0)|0,t[6]=t[6]+1295307597+(t[5]>>>0>>0?1:0)|0,t[7]=t[7]+3545052371+(t[6]>>>0>>0?1:0)|0,this._b=t[7]>>>0>>0?1:0,n=0;n<8;n++){var r=e[n]+t[n],i=65535&r,o=r>>>16;a[n]=((i*i>>>17)+i*o>>>15)+o*o^((4294901760&r)*r|0)+((65535&r)*r|0)}e[0]=a[0]+(a[7]<<16|a[7]>>>16)+(a[6]<<16|a[6]>>>16)|0,e[1]=a[1]+(a[0]<<8|a[0]>>>24)+a[7]|0,e[2]=a[2]+(a[1]<<16|a[1]>>>16)+(a[0]<<16|a[0]>>>16)|0,e[3]=a[3]+(a[2]<<8|a[2]>>>24)+a[1]|0,e[4]=a[4]+(a[3]<<16|a[3]>>>16)+(a[2]<<16|a[2]>>>16)|0,e[5]=a[5]+(a[4]<<8|a[4]>>>24)+a[3]|0,e[6]=a[6]+(a[5]<<16|a[5]>>>16)+(a[4]<<16|a[4]>>>16)|0,e[7]=a[7]+(a[6]<<8|a[6]>>>24)+a[5]|0}e.Rabbit=t._createHelper(r)}(),n.Rabbit)}),Sr(function(e,t){var n;e.exports=(n=dh,function(){var e=n,t=e.lib.StreamCipher,r=e.algo,i=[],s=[],a=[],r=r.RabbitLegacy=t.extend({_doReset:function(){for(var e=this._key.words,t=this.cfg.iv,n=this._X=[e[0],e[3]<<16|e[2]>>>16,e[1],e[0]<<16|e[3]>>>16,e[2],e[1]<<16|e[0]>>>16,e[3],e[2]<<16|e[1]>>>16],r=this._C=[e[2]<<16|e[2]>>>16,4294901760&e[0]|65535&e[1],e[3]<<16|e[3]>>>16,4294901760&e[1]|65535&e[2],e[0]<<16|e[0]>>>16,4294901760&e[2]|65535&e[3],e[1]<<16|e[1]>>>16,4294901760&e[3]|65535&e[0]],i=this._b=0;i<4;i++)c.call(this);for(i=0;i<8;i++)r[i]^=n[i+4&7];if(t){var h=(t=16711935&((t=(e=t.words)[0])<<8|t>>>24)|4278255360&(t<<24|t>>>8))>>>16|4294901760&(e=16711935&((e=e[1])<<8|e>>>24)|4278255360&(e<<24|e>>>8)),f=e<<16|65535&t;for(r[0]^=t,r[1]^=h,r[2]^=e,r[3]^=f,r[4]^=t,r[5]^=h,r[6]^=e,r[7]^=f,i=0;i<4;i++)c.call(this)}},_doProcessBlock:function(e,t){var n=this._X;c.call(this),i[0]=n[0]^n[5]>>>16^n[3]<<16,i[1]=n[2]^n[7]>>>16^n[5]<<16,i[2]=n[4]^n[1]>>>16^n[7]<<16,i[3]=n[6]^n[3]>>>16^n[1]<<16;for(var r=0;r<4;r++)i[r]=16711935&(i[r]<<8|i[r]>>>24)|4278255360&(i[r]<<24|i[r]>>>8),e[t+r]^=i[r]},blockSize:4,ivSize:2});function c(){for(var e=this._X,t=this._C,n=0;n<8;n++)s[n]=t[n];for(t[0]=t[0]+1295307597+this._b|0,t[1]=t[1]+3545052371+(t[0]>>>0>>0?1:0)|0,t[2]=t[2]+886263092+(t[1]>>>0>>0?1:0)|0,t[3]=t[3]+1295307597+(t[2]>>>0>>0?1:0)|0,t[4]=t[4]+3545052371+(t[3]>>>0>>0?1:0)|0,t[5]=t[5]+886263092+(t[4]>>>0>>0?1:0)|0,t[6]=t[6]+1295307597+(t[5]>>>0>>0?1:0)|0,t[7]=t[7]+3545052371+(t[6]>>>0>>0?1:0)|0,this._b=t[7]>>>0>>0?1:0,n=0;n<8;n++){var r=e[n]+t[n],i=65535&r,o=r>>>16;a[n]=((i*i>>>17)+i*o>>>15)+o*o^((4294901760&r)*r|0)+((65535&r)*r|0)}e[0]=a[0]+(a[7]<<16|a[7]>>>16)+(a[6]<<16|a[6]>>>16)|0,e[1]=a[1]+(a[0]<<8|a[0]>>>24)+a[7]|0,e[2]=a[2]+(a[1]<<16|a[1]>>>16)+(a[0]<<16|a[0]>>>16)|0,e[3]=a[3]+(a[2]<<8|a[2]>>>24)+a[1]|0,e[4]=a[4]+(a[3]<<16|a[3]>>>16)+(a[2]<<16|a[2]>>>16)|0,e[5]=a[5]+(a[4]<<8|a[4]>>>24)+a[3]|0,e[6]=a[6]+(a[5]<<16|a[5]>>>16)+(a[4]<<16|a[4]>>>16)|0,e[7]=a[7]+(a[6]<<8|a[6]>>>24)+a[5]|0}e.RabbitLegacy=t._createHelper(r)}(),n.RabbitLegacy)}),Sr(function(e,t){e.exports=dh})),_h=function(t,n,r,i){return"string"==typeof(t="undefined"!=typeof Buffer&&Buffer.isBuffer(t)?t.toString():t)?function(e,t,n,r){return t.xmlMode||t._useHtmlParser2?function(e,t){var n=new w(void 0,t);return new va(n,t).end(e),n.root}(e,t):ta(e,t,n,r)}(t,n,r,i):(n=t,!Array.isArray(n)&&O(n)?n:(Bn(n,r=new v([])),r))},Eh=function(e){return function r(s,a,o){if(void 0===o&&(o=!0),null==s)throw new Error("cheerio.load() expects a string");var c=rr(rr({},n),i(a)),u=e(s,c,o,null),l=function(n){function r(){return null!==n&&n.apply(this,arguments)||this}return nr(r,n),r.prototype._make=function(e,t){return(e=h(e,t)).prevObject=this,e},r.prototype._parse=function(t,n,r,i){return e(t,n,r,i)},r.prototype._render=function(e){return function(e,t){return t.xmlMode||t._useHtmlParser2?W(e,t):function(e){for(var t,n=("length"in e?e:[e]),r=0;r0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(e){this.children=e}}class g extends A{constructor(){super(...arguments),this.type=e.CDATA}get nodeType(){return 4}}class N extends A{constructor(){super(...arguments),this.type=e.Root}get nodeType(){return 9}}class C extends A{constructor(t,n,r=[],i=("script"===t?e.Script:"style"===t?e.Style:e.Tag)){super(r),this.name=t,this.attribs=n,this.type=i}get nodeType(){return 1}get tagName(){return this.name}set tagName(e){this.name=e}get attributes(){return Object.keys(this.attribs).map((e=>{var t,n;return{name:e,value:this.attribs[e],namespace:null===(t=this["x-attribsNamespace"])||void 0===t?void 0:t[e],prefix:null===(n=this["x-attribsPrefix"])||void 0===n?void 0:n[e]}}))}}function I(t){return(n=t).type===e.Tag||n.type===e.Script||n.type===e.Style;var n}function S(t){return t.type===e.CDATA}function b(t){return t.type===e.Text}function O(t){return t.type===e.Comment}function y(t){return t.type===e.Directive}function L(t){return t.type===e.Root}function k(e){return Object.prototype.hasOwnProperty.call(e,"children")}function v(e,t=!1){let n;if(b(e))n=new m(e.data);else if(O(e))n=new T(e.data);else if(I(e)){const r=t?D(e.children):[],i=new C(e.name,{...e.attribs},r);r.forEach((e=>e.parent=i)),null!=e.namespace&&(i.namespace=e.namespace),e["x-attribsNamespace"]&&(i["x-attribsNamespace"]={...e["x-attribsNamespace"]}),e["x-attribsPrefix"]&&(i["x-attribsPrefix"]={...e["x-attribsPrefix"]}),n=i}else if(S(e)){const r=t?D(e.children):[],i=new g(r);r.forEach((e=>e.parent=i)),n=i}else if(L(e)){const r=t?D(e.children):[],i=new N(r);r.forEach((e=>e.parent=i)),e["x-mode"]&&(i["x-mode"]=e["x-mode"]),n=i}else{if(!y(e))throw new Error(`Not implemented yet: ${e.type}`);{const t=new _(e.name,e.data);null!=e["x-name"]&&(t["x-name"]=e["x-name"],t["x-publicId"]=e["x-publicId"],t["x-systemId"]=e["x-systemId"]),n=t}}return n.startIndex=e.startIndex,n.endIndex=e.endIndex,null!=e.sourceCodeLocation&&(n.sourceCodeLocation=e.sourceCodeLocation),n}function D(e){const t=e.map((e=>v(e,!0)));for(let e=1;e$\x80-\uFFFF]/g,x=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]),w=null!=String.prototype.codePointAt?(e,t)=>e.codePointAt(t):(e,t)=>55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t);function B(e){let t,n="",r=0;for(;null!==(t=P.exec(e));){const i=t.index,s=e.charCodeAt(i),a=x.get(s);void 0!==a?(n+=e.substring(r,i)+a,r=i+1):(n+=`${e.substring(r,i)}&#x${w(e,i).toString(16)};`,r=P.lastIndex+=Number(55296==(64512&s)))}return n+e.substr(r)}function F(e,t){return function(n){let r,i=0,s="";for(;r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}const U=F(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),H=F(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]])),G=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map((e=>[e.toLowerCase(),e]))),Y=new Map(["definitionURL","attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map((e=>[e.toLowerCase(),e]))),q=new Set(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]);function K(e){return e.replace(/"/g,""")}const j=new Set(["area","base","basefont","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr"]);function V(e,t={}){const n="length"in e?e:[e];let r="";for(let e=0;e`;case c:return function(e){return`\x3c!--${e.data}--\x3e`}(e);case p:return function(e){return``}(e);case l:case h:case u:return function(e,t){var n;"foreign"===t.xmlMode&&(e.name=null!==(n=G.get(e.name))&&void 0!==n?n:e.name,e.parent&&Q.has(e.parent.name)&&(t={...t,xmlMode:!1}));!t.xmlMode&&X.has(e.name)&&(t={...t,xmlMode:"foreign"});let r=`<${e.name}`;const i=function(e,t){var n;if(!e)return;const r=!1===(null!==(n=t.encodeEntities)&&void 0!==n?n:t.decodeEntities)?K:t.xmlMode||"utf8"!==t.encodeEntities?B:U;return Object.keys(e).map((n=>{var i,s;const a=null!==(i=e[n])&&void 0!==i?i:"";return"foreign"===t.xmlMode&&(n=null!==(s=Y.get(n))&&void 0!==s?s:n),t.emptyAttrs||t.xmlMode||""!==a?`${n}="${r(a)}"`:n})).join(" ")}(e.attribs,t);i&&(r+=` ${i}`);0===e.children.length&&(t.xmlMode?!1!==t.selfClosingTags:t.selfClosingTags&&j.has(e.name))?(t.xmlMode||(r+=" "),r+="/>"):(r+=">",e.children.length>0&&(r+=V(e.children,t)),!t.xmlMode&&j.has(e.name)||(r+=``));return r}(e,t);case a:return function(e,t){var n;let r=e.data||"";!1===(null!==(n=t.encodeEntities)&&void 0!==n?n:t.decodeEntities)||!t.xmlMode&&e.parent&&q.has(e.parent.name)||(r=t.xmlMode||"utf8"!==t.encodeEntities?B(r):H(r));return r}(e,t)}}const Q=new Set(["mi","mo","mn","ms","mtext","annotation-xml","foreignObject","desc","title"]),X=new Set(["svg","math"]);function $(e,t){return V(e,t)}function z(e){return Array.isArray(e)?e.map(z).join(""):k(e)&&!O(e)?z(e.children):b(e)?e.data:""}function J(t){return Array.isArray(t)?t.map(J).join(""):k(t)&&(t.type===e.Tag||S(t))?J(t.children):b(t)?t.data:""}function Z(e){return k(e)?e.children:[]}function ee(e){return e.parent||null}function te(e){const t=ee(e);if(null!=t)return Z(t);const n=[e];let{prev:r,next:i}=e;for(;null!=r;)n.unshift(r),({prev:r}=r);for(;null!=i;)n.push(i),({next:i}=i);return n}function ne(e){let{next:t}=e;for(;null!==t&&!I(t);)({next:t}=t);return t}function re(e){let{prev:t}=e;for(;null!==t&&!I(t);)({prev:t}=t);return t}function ie(e){if(e.prev&&(e.prev.next=e.next),e.next&&(e.next.prev=e.prev),e.parent){const t=e.parent.children;t.splice(t.lastIndexOf(e),1)}}function se(e,t,n=!0,r=1/0){return Array.isArray(t)||(t=[t]),ae(e,t,n,r)}function ae(e,t,n,r){const i=[];for(const s of t){if(e(s)&&(i.push(s),--r<=0))break;if(n&&k(s)&&s.children.length>0){const t=ae(e,s.children,n,r);if(i.push(...t),r-=t.length,r<=0)break}}return i}function oe(e,t,n=!0){let r=null;for(let i=0;i0&&(r=oe(e,s.children,!0)))}return r}const ce={tag_name:e=>"function"==typeof e?t=>I(t)&&e(t.name):"*"===e?I:t=>I(t)&&t.name===e,tag_type:e=>"function"==typeof e?t=>e(t.type):t=>t.type===e,tag_contains:e=>"function"==typeof e?t=>b(t)&&e(t.data):t=>b(t)&&t.data===e};function le(e,t){return"function"==typeof t?n=>I(n)&&t(n.attribs[e]):n=>I(n)&&n.attribs[e]===t}function he(e,t){return n=>e(n)||t(n)}function ue(e){const t=Object.keys(e).map((t=>{const n=e[t];return Object.prototype.hasOwnProperty.call(ce,t)?ce[t](n):le(t,n)}));return 0===t.length?null:t.reduce(he)}function pe(e,t,n=!0,r=1/0){return se(ce.tag_name(e),t,n,r)}var fe;function de(e,t){const n=[],r=[];if(e===t)return 0;let i=k(e)?e:e.parent;for(;i;)n.unshift(i),i=i.parent;for(i=k(t)?t:t.parent;i;)r.unshift(i),i=i.parent;const s=Math.min(n.length,r.length);let a=0;for(;ac.indexOf(h)?o===t?fe.FOLLOWING|fe.CONTAINED_BY:fe.FOLLOWING:o===e?fe.PRECEDING|fe.CONTAINS:fe.PRECEDING}function Ee(e){return(e=e.filter(((e,t,n)=>!n.includes(e,t+1)))).sort(((e,t)=>{const n=de(e,t);return n&fe.PRECEDING?-1:n&fe.FOLLOWING?1:0})),e}!function(e){e[e.DISCONNECTED=1]="DISCONNECTED",e[e.PRECEDING=2]="PRECEDING",e[e.FOLLOWING=4]="FOLLOWING",e[e.CONTAINS=8]="CONTAINS",e[e.CONTAINED_BY=16]="CONTAINED_BY"}(fe||(fe={}));const me=["url","type","lang"],Te=["fileSize","bitrate","framerate","samplingrate","channels","duration","height","width"];function _e(e){return pe("media:content",e).map((e=>{const{attribs:t}=e,n={medium:t.medium,isDefault:!!t.isDefault};for(const e of me)t[e]&&(n[e]=t[e]);for(const e of Te)t[e]&&(n[e]=parseInt(t[e],10));return t.expression&&(n.expression=t.expression),n}))}function Ae(e,t){return pe(e,t,!0,1)[0]}function ge(e,t,n=!1){return z(pe(e,t,n,1)).trim()}function Ne(e,t,n,r,i=!1){const s=ge(n,r,i);s&&(e[t]=s)}function Ce(e){return"rss"===e||"feed"===e||"rdf:RDF"===e}var Ie,Se=Object.freeze({__proto__:null,isTag:I,isCDATA:S,isText:b,isComment:O,isDocument:L,hasChildren:k,getOuterHTML:$,getInnerHTML:function(e,t){return k(e)?e.children.map((e=>$(e,t))).join(""):""},getText:function e(t){return Array.isArray(t)?t.map(e).join(""):I(t)?"br"===t.name?"\n":e(t.children):S(t)?e(t.children):b(t)?t.data:""},textContent:z,innerText:J,getChildren:Z,getParent:ee,getSiblings:te,getAttributeValue:function(e,t){var n;return null===(n=e.attribs)||void 0===n?void 0:n[t]},hasAttrib:function(e,t){return null!=e.attribs&&Object.prototype.hasOwnProperty.call(e.attribs,t)&&null!=e.attribs[t]},getName:function(e){return e.name},nextElementSibling:ne,prevElementSibling:re,removeElement:ie,replaceElement:function(e,t){const n=t.prev=e.prev;n&&(n.next=t);const r=t.next=e.next;r&&(r.prev=t);const i=t.parent=e.parent;if(i){const n=i.children;n[n.lastIndexOf(e)]=t,e.parent=null}},appendChild:function(e,t){if(ie(t),t.next=null,t.parent=e,e.children.push(t)>1){const n=e.children[e.children.length-2];n.next=t,t.prev=n}else t.prev=null},append:function(e,t){ie(t);const{parent:n}=e,r=e.next;if(t.next=r,t.prev=e,e.next=t,t.parent=n,r){if(r.prev=t,n){const e=n.children;e.splice(e.lastIndexOf(r),0,t)}}else n&&n.children.push(t)},prependChild:function(e,t){if(ie(t),t.parent=e,t.prev=null,1!==e.children.unshift(t)){const n=e.children[1];n.prev=t,t.next=n}else t.next=null},prepend:function(e,t){ie(t);const{parent:n}=e;if(n){const r=n.children;r.splice(r.indexOf(e),0,t)}e.prev&&(e.prev.next=t),t.parent=n,t.prev=e.prev,t.next=e,e.prev=t},filter:se,find:ae,findOneChild:function(e,t){return t.find(e)},findOne:oe,existsOne:function e(t,n){return n.some((n=>I(n)&&(t(n)||n.children.length>0&&e(t,n.children))))},findAll:function(e,t){var n;const r=[],i=t.filter(I);let s;for(;s=i.shift();){const t=null===(n=s.children)||void 0===n?void 0:n.filter(I);t&&t.length>0&&i.unshift(...t),e(s)&&r.push(s)}return r},testElement:function(e,t){const n=ue(e);return!n||n(t)},getElements:function(e,t,n,r=1/0){const i=ue(e);return i?se(i,t,n,r):[]},getElementById:function(e,t,n=!0){return Array.isArray(t)||(t=[t]),oe(le("id",e),t,n)},getElementsByTagName:pe,getElementsByTagType:function(e,t,n=!0,r=1/0){return se(ce.tag_type(e),t,n,r)},removeSubsets:function(e){let t=e.length;for(;--t>=0;){const n=e[t];if(t>0&&e.lastIndexOf(n,t-1)>=0)e.splice(t,1);else for(let r=n.parent;r;r=r.parent)if(e.includes(r)){e.splice(t,1);break}}return e},get DocumentPosition(){return fe},compareDocumentPosition:de,uniqueSort:Ee,getFeed:function(e){const t=Ae(Ce,e);return t?"feed"===t.name?function(e){var t;const n=e.children,r={type:"atom",items:pe("entry",n).map((e=>{var t;const{children:n}=e,r={media:_e(n)};Ne(r,"id","id",n),Ne(r,"title","title",n);const i=null===(t=Ae("link",n))||void 0===t?void 0:t.attribs.href;i&&(r.link=i);const s=ge("summary",n)||ge("content",n);s&&(r.description=s);const a=ge("updated",n);return a&&(r.pubDate=new Date(a)),r}))};Ne(r,"id","id",n),Ne(r,"title","title",n);const i=null===(t=Ae("link",n))||void 0===t?void 0:t.attribs.href;i&&(r.link=i);Ne(r,"description","subtitle",n);const s=ge("updated",n);s&&(r.updated=new Date(s));return Ne(r,"author","email",n,!0),r}(t):function(e){var t,n;const r=null!==(n=null===(t=Ae("channel",e.children))||void 0===t?void 0:t.children)&&void 0!==n?n:[],i={type:e.name.substr(0,3),id:"",items:pe("item",e.children).map((e=>{const{children:t}=e,n={media:_e(t)};Ne(n,"id","guid",t),Ne(n,"title","title",t),Ne(n,"link","link",t),Ne(n,"description","description",t);const r=ge("pubDate",t);return r&&(n.pubDate=new Date(r)),n}))};Ne(i,"title","title",r),Ne(i,"link","link",r),Ne(i,"description","description",r);const s=ge("lastBuildDate",r);s&&(i.updated=new Date(s));return Ne(i,"author","managingEditor",r,!0),i}(t):null}}),be="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};function Oe(e){if(Object.keys)return Object.keys(e);var t=[];for(var n in e)t.push(n);return t}function ye(e,t){if(e.forEach)return e.forEach(t);for(var n=0;n1&&c.shift(),this._hasParentSelector=null;const l=this._trace(c,t,["$"],i,s,n).filter((function(e){return e&&!e.isParentSelector}));return l.length?o||1!==l.length||l[0].hasArrExpr?l.reduce(((e,t)=>{const n=this._getPreferredOutput(t);return a&&Array.isArray(n)?e=e.concat(n):e.push(n),e}),[]):this._getPreferredOutput(l[0]):o?[]:void 0},He.prototype._getPreferredOutput=function(e){const t=this.currResultType;switch(t){case"all":{const t=Array.isArray(e.path)?e.path:He.toPathArray(e.path);return e.pointer=He.toPointer(t),e.path="string"==typeof e.path?e.path:He.toPathString(e.path),e}case"value":case"parent":case"parentProperty":return e[t];case"path":return He.toPathString(e[t]);case"pointer":return He.toPointer(e.path);default:throw new TypeError("Unknown result type")}},He.prototype._handleCallback=function(e,t,n){if(t){const r=this._getPreferredOutput(e);e.path="string"==typeof e.path?e.path:He.toPathString(e.path),t(r,n,e)}},He.prototype._trace=function(e,t,n,r,i,s,a,o){let c;if(!e.length)return c={path:n,value:t,parent:r,parentProperty:i,hasArrExpr:a},this._handleCallback(c,s,"value"),c;const l=e[0],h=e.slice(1),u=[];function p(e){Array.isArray(e)?e.forEach((e=>{u.push(e)})):u.push(e)}if(("string"!=typeof l||o)&&t&&we.call(t,l))p(this._trace(h,t[l],Be(n,l),t,l,s,a));else if("*"===l)this._walk(t,(e=>{p(this._trace(h,t[e],Be(n,e),t,e,s,!0,!0))}));else if(".."===l)p(this._trace(h,t,n,r,i,s,a)),this._walk(t,(r=>{"object"==typeof t[r]&&p(this._trace(e.slice(),t[r],Be(n,r),t,r,s,!0))}));else{if("^"===l)return this._hasParentSelector=!0,{path:n.slice(0,-1),expr:h,isParentSelector:!0};if("~"===l)return c={path:Be(n,l),value:i,parent:r,parentProperty:null},this._handleCallback(c,s,"property"),c;if("$"===l)p(this._trace(h,t,n,null,null,s,a));else if(/^(-?\d*):(-?\d*):?(\d*)$/u.test(l))p(this._slice(l,h,t,n,r,i,s));else if(0===l.indexOf("?(")){if(this.currPreventEval)throw new Error("Eval [?(expr)] prevented in JSONPath expression.");const e=l.replace(/^\?\((.*?)\)$/u,"$1");this._walk(t,(a=>{this._eval(e,t[a],a,n,r,i)&&p(this._trace(h,t[a],Be(n,a),t,a,s,!0))}))}else if("("===l[0]){if(this.currPreventEval)throw new Error("Eval [(expr)] prevented in JSONPath expression.");p(this._trace(Fe(this._eval(l,t,n[n.length-1],n.slice(0,-1),r,i),h),t,n,r,i,s,a))}else if("@"===l[0]){let e=!1;const a=l.slice(1,-2);switch(a){case"scalar":t&&["object","function"].includes(typeof t)||(e=!0);break;case"boolean":case"string":case"undefined":case"function":typeof t===a&&(e=!0);break;case"integer":!Number.isFinite(t)||t%1||(e=!0);break;case"number":Number.isFinite(t)&&(e=!0);break;case"nonFinite":"number"!=typeof t||Number.isFinite(t)||(e=!0);break;case"object":t&&typeof t===a&&(e=!0);break;case"array":Array.isArray(t)&&(e=!0);break;case"other":e=this.currOtherTypeCallback(t,n,r,i);break;case"null":null===t&&(e=!0);break;default:throw new TypeError("Unknown value type "+a)}if(e)return c={path:n,value:t,parent:r,parentProperty:i},this._handleCallback(c,s,"value"),c}else if("`"===l[0]&&t&&we.call(t,l.slice(1))){const e=l.slice(1);p(this._trace(h,t[e],Be(n,e),t,e,s,a,!0))}else if(l.includes(",")){const e=l.split(",");for(const a of e)p(this._trace(Fe(a,h),t,n,r,i,s,!0))}else!o&&t&&we.call(t,l)&&p(this._trace(h,t[l],Be(n,l),t,l,s,a,!0))}if(this._hasParentSelector)for(let e=0;e{t(e)}))},He.prototype._slice=function(e,t,n,r,i,s,a){if(!Array.isArray(n))return;const o=n.length,c=e.split(":"),l=c[2]&&Number.parseInt(c[2])||1;let h=c[0]&&Number.parseInt(c[0])||0,u=c[1]&&Number.parseInt(c[1])||o;h=h<0?Math.max(0,h+o):Math.min(o,h),u=u<0?Math.max(0,u+o):Math.min(o,u);const p=[];for(let e=h;e{p.push(e)}))}return p},He.prototype._eval=function(e,t,n,r,i,s){this.currSandbox._$_parentProperty=s,this.currSandbox._$_parent=i,this.currSandbox._$_property=n,this.currSandbox._$_root=this.json,this.currSandbox._$_v=t;const a=e.includes("@path");a&&(this.currSandbox._$_path=He.toPathString(r.concat([n])));const o="script:"+e;if(!He.cache[o]){let t=e.replace(/@parentProperty/gu,"_$_parentProperty").replace(/@parent/gu,"_$_parent").replace(/@property/gu,"_$_property").replace(/@root/gu,"_$_root").replace(/@([.\s)[])/gu,"_$_v$1");a&&(t=t.replace(/@path/gu,"_$_path")),He.cache[o]=new this.vm.Script(t)}try{return He.cache[o].runInNewContext(this.currSandbox)}catch(t){throw new Error("jsonPath: "+t.message+": "+e)}},He.cache={},He.toPathString=function(e){const t=e,n=t.length;let r="$";for(let e=1;e":">"},i=/[&"'<>]/g,s=e.exports={};function a(e,t){return n.hasOwnProperty.call(e,t)}function o(e){return r[e]}function c(e,t,n){var r,i,s;if(e instanceof Error&&(e=(i=e).name+": "+i.message),Object.setPrototypeOf?(r=new Error(e),Object.setPrototypeOf(r,c.prototype)):(r=this,Object.defineProperty(r,"message",{enumerable:!1,writable:!0,value:e})),Object.defineProperty(r,"name",{value:"Template render error"}),Error.captureStackTrace&&Error.captureStackTrace(r,this.constructor),i){var a=Object.getOwnPropertyDescriptor(i,"stack");(s=a&&(a.get||function(){return a.value}))||(s=function(){return i.stack})}else{var o=new Error(e).stack;s=function(){return o}}return Object.defineProperty(r,"stack",{get:function(){return s.call(r)}}),Object.defineProperty(r,"cause",{value:i}),r.lineno=t,r.colno=n,r.firstUpdate=!0,r.Update=function(e){var t="("+(e||"unknown path")+")";return this.firstUpdate&&(this.lineno&&this.colno?t+=" [Line "+this.lineno+", Column "+this.colno+"]":this.lineno&&(t+=" [Line "+this.lineno+"]")),t+="\n ",this.firstUpdate&&(t+=" "),this.message=t+(this.message||""),this.firstUpdate=!1,this},r}function l(e){return"[object Function]"===n.toString.call(e)}function h(e){return"[object Array]"===n.toString.call(e)}function u(e){return"[object String]"===n.toString.call(e)}function p(e){return"[object Object]"===n.toString.call(e)}function f(e){var t=function(e){return e?"string"==typeof e?e.split("."):[e]:[]}(e);return function(e){for(var n=e,r=0;r1)for(var n=1;n0&&a.length>i){a.warned=!0;var o=new Error("Possible EventEmitter memory leak detected. "+a.length+" "+t+" listeners added. Use emitter.setMaxListeners() to increase limit");o.name="MaxListenersExceededWarning",o.emitter=e,o.type=t,o.count=a.length,function(e){"function"==typeof console.warn?console.warn(e):console.log(e)}(o)}}else a=s[t]=n,++e._eventsCount;return e}function bt(e,t,n){var r=!1;function i(){e.removeListener(t,i),r||(r=!0,n.apply(e,arguments))}return i.listener=n,i}function Ot(e){var t=this._events;if(t){var n=t[e];if("function"==typeof n)return 1;if(n)return n.length}return 0}function yt(e,t){for(var n=new Array(t);t--;)n[t]=e[t];return n}function Lt(){var e;Tt.call(this),this.__emitError=(e=this,function(t){e.emit("error",t)})}function kt(){return new Lt}mt.prototype=Object.create(null),Tt.EventEmitter=Tt,Tt.usingDomains=!1,Tt.prototype.domain=void 0,Tt.prototype._events=void 0,Tt.prototype._maxListeners=void 0,Tt.defaultMaxListeners=10,Tt.init=function(){this.domain=null,Tt.usingDomains&&undefined.active,this._events&&this._events!==Object.getPrototypeOf(this)._events||(this._events=new mt,this._eventsCount=0),this._maxListeners=this._maxListeners||void 0},Tt.prototype.setMaxListeners=function(e){if("number"!=typeof e||e<0||isNaN(e))throw new TypeError('"n" argument must be a positive number');return this._maxListeners=e,this},Tt.prototype.getMaxListeners=function(){return _t(this)},Tt.prototype.emit=function(e){var t,n,r,i,s,a,o,c="error"===e;if(a=this._events)c=c&&null==a.error;else if(!c)return!1;if(o=this.domain,c){if(t=arguments[1],!o){if(t instanceof Error)throw t;var l=new Error('Uncaught, unspecified "error" event. ('+t+")");throw l.context=t,l}return t||(t=new Error('Uncaught, unspecified "error" event')),t.domainEmitter=this,t.domain=o,t.domainThrown=!1,o.emit("error",t),!1}if(!(n=a[e]))return!1;var h="function"==typeof n;switch(r=arguments.length){case 1:At(n,h,this);break;case 2:gt(n,h,this,arguments[1]);break;case 3:Nt(n,h,this,arguments[1],arguments[2]);break;case 4:Ct(n,h,this,arguments[1],arguments[2],arguments[3]);break;default:for(i=new Array(r-1),s=1;s0;)if(n[s]===t||n[s].listener&&n[s].listener===t){a=n[s].listener,i=s;break}if(i<0)return this;if(1===n.length){if(n[0]=void 0,0==--this._eventsCount)return this._events=new mt,this;delete r[e]}else!function(e,t){for(var n=t,r=n+1,i=e.length;r0?Reflect.ownKeys(this._events):[]},("function"==typeof Object.create?function(e,t){e.super_=t,e.prototype=Object.create(t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}})}:function(e,t){e.super_=t;var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e})(Lt,Tt),Lt.prototype.add=function(e){e.on("error",this.__emitError)},Lt.prototype.remove=function(e){e.removeListener("error",this.__emitError)},Lt.prototype.bind=function(e){var t=this.__emitError;return function(){var n=Array.prototype.slice.call(arguments);try{e.apply(null,n)}catch(e){t(e)}}},Lt.prototype.intercept=function(e){var t=this.__emitError;return function(n){if(n)t(n);else{var r=Array.prototype.slice.call(arguments,1);try{e.apply(null,r)}catch(n){t(n)}}}},Lt.prototype.run=function(e){var t=this.__emitError;try{e()}catch(e){t(e)}return this},Lt.prototype.dispose=function(){return this.removeAllListeners(),this},Lt.prototype.enter=Lt.prototype.exit=function(){return this};var vt,Dt={Domain:Lt,createDomain:kt,create:kt},Rt="function"==typeof setImmediate,Mt=Pt;function Pt(e){xt.length||(Ut(),wt=!0),xt[xt.length]=e}var xt=[],wt=!1,Bt=0;function Ft(){for(;Bt1024){for(var t=0,n=xt.length-Bt;t=!".indexOf(r)){this.forward();var o,c=["==","===","!=","!==","<=",">=","//","**"],l=r+this.current();switch(-1!==je.indexOf(c,l)&&(this.forward(),r=l,-1!==je.indexOf(c,l+this.current())&&(r=l+this.current(),this.forward())),r){case"(":o="left-paren";break;case")":o="right-paren";break;case"[":o="left-bracket";break;case"]":o="right-bracket";break;case"{":o="left-curly";break;case"}":o="right-curly";break;case",":o="comma";break;case":":o="colon";break;case"~":o="tilde";break;case"|":o="pipe";break;default:o="operator"}return Kt(o,r,t,n)}if((e=this._extractUntil(" \n\t\r ()[]{}%*-+~/#,:|.<>=!")).match(/^[-+]?[0-9]+$/))return"."===this.current()?(this.forward(),Kt("float",e+"."+this._extract("0123456789"),t,n)):Kt("int",e,t,n);if(e.match(/^(true|false)$/))return Kt("boolean",e,t,n);if("none"===e)return Kt("none",e,t,n);if("null"===e)return Kt("none",e,t,n);if(e)return Kt("symbol",e,t,n);throw new Error("Unexpected value while parsing: "+e)}var h,u=this.tags.BLOCK_START.charAt(0)+this.tags.VARIABLE_START.charAt(0)+this.tags.COMMENT_START.charAt(0)+this.tags.COMMENT_END.charAt(0);if(this.isFinished())return null;if((e=this._extractString(this.tags.BLOCK_START+"-"))||(e=this._extractString(this.tags.BLOCK_START)))return this.in_code=!0,Kt("block-start",e,t,n);if((e=this._extractString(this.tags.VARIABLE_START+"-"))||(e=this._extractString(this.tags.VARIABLE_START)))return this.in_code=!0,Kt("variable-start",e,t,n);e="";var p=!1;for(this._matches(this.tags.COMMENT_START)&&(p=!0,e=this._extractString(this.tags.COMMENT_START));null!==(h=this._extractUntil(u));){if(e+=h,(this._matches(this.tags.BLOCK_START)||this._matches(this.tags.VARIABLE_START)||this._matches(this.tags.COMMENT_START))&&!p){if(this.lstripBlocks&&this._matches(this.tags.BLOCK_START)&&this.colno>0&&this.colno<=e.length){var f=e.slice(-this.colno);if(/^\s+$/.test(f)&&!(e=e.slice(0,-this.colno)).length)return this.nextToken()}break}if(this._matches(this.tags.COMMENT_END)){if(!p)throw new Error("unexpected end of comment");e+=this._extractString(this.tags.COMMENT_END);break}e+=this.current(),this.forward()}if(null===h&&p)throw new Error("expected end of comment, got end of file");return Kt(p?"comment":"data",e,t,n)},t._parseString=function(e){this.forward();for(var t="";!this.isFinished()&&this.current()!==e;){var n=this.current();if("\\"===n){switch(this.forward(),this.current()){case"n":t+="\n";break;case"t":t+="\t";break;case"r":t+="\r";break;default:t+=this.current()}this.forward()}else t+=n,this.forward()}return this.forward(),t},t._matches=function(e){return this.index+e.length>this.len?null:this.str.slice(this.index,this.index+e.length)===e},t._extractString=function(e){return this._matches(e)?(this.forwardN(e.length),e):null},t._extractUntil=function(e){return this._extractMatching(!0,e||"")},t._extract=function(e){return this._extractMatching(!1,e)},t._extractMatching=function(e,t){if(this.isFinished())return null;var n=t.indexOf(this.current());if(e&&-1===n||!e&&-1!==n){var r=this.current();this.forward();for(var i=t.indexOf(this.current());(e&&-1===i||!e&&-1!==i)&&!this.isFinished();)r+=this.current(),this.forward(),i=t.indexOf(this.current());return r}return""},t._extractRegex=function(e){var t=this.currentStr().match(e);return t?(this.forwardN(t[0].length),t):null},t.isFinished=function(){return this.index>=this.len},t.forwardN=function(e){for(var t=0;t2?i-2:0),a=2;a0||!n)&&Et.stdout.write(" ".repeat(t));var s=i===r.length-1?"":"\n";Et.stdout.write(""+e+s)}))}var hr={Node:on,Root:hn,NodeList:ln,Value:cn,Literal:un,Symbol:pn,Group:fn,Array:dn,Pair:En,Dict:mn,Output:Un,Capture:Hn,TemplateData:Gn,If:_n,IfAsync:An,InlineIf:gn,For:Nn,AsyncEach:Cn,AsyncAll:In,Macro:Sn,Caller:bn,Import:On,FromImport:yn,FunCall:Ln,Filter:kn,FilterAsync:vn,KeywordArgs:Dn,Block:Rn,Super:Mn,Extends:Pn,Include:xn,Set:wn,Switch:Bn,Case:Fn,LookupVal:Tn,BinOp:qn,In:Kn,Is:jn,Or:Vn,And:Wn,Not:Qn,Add:Xn,Concat:$n,Sub:zn,Mul:Jn,Div:Zn,FloorDiv:er,Mod:tr,Pow:nr,Neg:rr,Pos:ir,Compare:sr,CompareOperand:ar,CallExtension:or,CallExtensionAsync:cr,printNodes:function e(t,n){if(n=n||0,lr(t.typename+": ",n),t instanceof ln)lr("\n"),t.children.forEach((function(t){e(t,n+2)}));else if(t instanceof or)lr(t.extName+"."+t.prop+"\n"),t.args&&e(t.args,n+2),t.contentArgs&&t.contentArgs.forEach((function(t){e(t,n+2)}));else{var r=[],i=null;t.iterFields((function(e,t){e instanceof on?r.push([t,e]):(i=i||{})[t]=e})),i?lr(JSON.stringify(i,null,2)+"\n",null,!0):lr("\n"),r.forEach((function(t){var r=t[0],i=t[1];lr("["+r+"] =>",n+2),e(i,n+4)}))}}};function ur(e,t){return ur=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},ur(e,t)}hr.Node,hr.Root,hr.NodeList,hr.Value,hr.Literal,hr.Group,hr.Pair,hr.Dict,hr.Output,hr.Capture,hr.TemplateData,hr.If,hr.IfAsync,hr.InlineIf,hr.For,hr.AsyncEach,hr.AsyncAll,hr.Macro,hr.Caller,hr.Import,hr.FromImport,hr.FunCall,hr.Filter,hr.FilterAsync,hr.KeywordArgs,hr.Block,hr.Super,hr.Extends,hr.Include,hr.Switch,hr.Case,hr.LookupVal,hr.BinOp,hr.In,hr.Is,hr.Or,hr.And,hr.Not,hr.Add,hr.Concat,hr.Sub,hr.Mul,hr.Div,hr.FloorDiv,hr.Mod,hr.Pow,hr.Neg,hr.Pos,hr.Compare,hr.CompareOperand,hr.CallExtension,hr.CallExtensionAsync,hr.printNodes;var pr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,ur(t,n);var i=r.prototype;return i.init=function(e){this.tokens=e,this.peeked=null,this.breakOnBlocks=null,this.dropLeadingWhitespace=!1,this.extensions=[]},i.nextToken=function(e){var t;if(this.peeked){if(e||this.peeked.type!==Vt.TOKEN_WHITESPACE)return t=this.peeked,this.peeked=null,t;this.peeked=null}if(t=this.tokens.nextToken(),!e)for(;t&&t.type===Vt.TOKEN_WHITESPACE;)t=this.tokens.nextToken();return t},i.peekToken=function(){return this.peeked=this.peeked||this.nextToken(),this.peeked},i.pushToken=function(e){if(this.peeked)throw new Error("pushToken: can only push one token on between reads");this.peeked=e},i.error=function(e,t,n){if(void 0===t||void 0===n){var r=this.peekToken()||{};t=r.lineno,n=r.colno}return void 0!==t&&(t+=1),void 0!==n&&(n+=1),new je.TemplateError(e,t,n)},i.fail=function(e,t,n){throw this.error(e,t,n)},i.skip=function(e){var t=this.nextToken();return!(!t||t.type!==e)||(this.pushToken(t),!1)},i.expect=function(e){var t=this.nextToken();return t.type!==e&&this.fail("expected "+e+", got "+t.type,t.lineno,t.colno),t},i.skipValue=function(e,t){var n=this.nextToken();return!(!n||n.type!==e||n.value!==t)||(this.pushToken(n),!1)},i.skipSymbol=function(e){return this.skipValue(Vt.TOKEN_SYMBOL,e)},i.advanceAfterBlockEnd=function(e){var t;return e||((t=this.peekToken())||this.fail("unexpected end of file"),t.type!==Vt.TOKEN_SYMBOL&&this.fail("advanceAfterBlockEnd: expected symbol token or explicit name to be passed"),e=this.nextToken().value),(t=this.nextToken())&&t.type===Vt.TOKEN_BLOCK_END?"-"===t.value.charAt(0)&&(this.dropLeadingWhitespace=!0):this.fail("expected block end in "+e+" statement"),t},i.advanceAfterVariableEnd=function(){var e=this.nextToken();e&&e.type===Vt.TOKEN_VARIABLE_END?this.dropLeadingWhitespace="-"===e.value.charAt(e.value.length-this.tokens.tags.VARIABLE_END.length-1):(this.pushToken(e),this.fail("expected variable end"))},i.parseFor=function(){var e,t,n=this.peekToken();if(this.skipSymbol("for")?(e=new hr.For(n.lineno,n.colno),t="endfor"):this.skipSymbol("asyncEach")?(e=new hr.AsyncEach(n.lineno,n.colno),t="endeach"):this.skipSymbol("asyncAll")?(e=new hr.AsyncAll(n.lineno,n.colno),t="endall"):this.fail("parseFor: expected for{Async}",n.lineno,n.colno),e.name=this.parsePrimary(),e.name instanceof hr.Symbol||this.fail("parseFor: variable name expected for loop"),this.peekToken().type===Vt.TOKEN_COMMA){var r=e.name;for(e.name=new hr.Array(r.lineno,r.colno),e.name.addChild(r);this.skip(Vt.TOKEN_COMMA);){var i=this.parsePrimary();e.name.addChild(i)}}return this.skipSymbol("in")||this.fail('parseFor: expected "in" keyword for loop',n.lineno,n.colno),e.arr=this.parseExpression(),this.advanceAfterBlockEnd(n.value),e.body=this.parseUntilBlocks(t,"else"),this.skipSymbol("else")&&(this.advanceAfterBlockEnd("else"),e.else_=this.parseUntilBlocks(t)),this.advanceAfterBlockEnd(),e},i.parseMacro=function(){var e=this.peekToken();this.skipSymbol("macro")||this.fail("expected macro");var t=this.parsePrimary(!0),n=this.parseSignature(),r=new hr.Macro(e.lineno,e.colno,t,n);return this.advanceAfterBlockEnd(e.value),r.body=this.parseUntilBlocks("endmacro"),this.advanceAfterBlockEnd(),r},i.parseCall=function(){var e=this.peekToken();this.skipSymbol("call")||this.fail("expected call");var t=this.parseSignature(!0)||new hr.NodeList,n=this.parsePrimary();this.advanceAfterBlockEnd(e.value);var r=this.parseUntilBlocks("endcall");this.advanceAfterBlockEnd();var i=new hr.Symbol(e.lineno,e.colno,"caller"),s=new hr.Caller(e.lineno,e.colno,i,t,r),a=n.args.children;return a[a.length-1]instanceof hr.KeywordArgs||a.push(new hr.KeywordArgs),a[a.length-1].addChild(new hr.Pair(e.lineno,e.colno,i,s)),new hr.Output(e.lineno,e.colno,[n])},i.parseWithContext=function(){var e=this.peekToken(),t=null;return this.skipSymbol("with")?t=!0:this.skipSymbol("without")&&(t=!1),null!==t&&(this.skipSymbol("context")||this.fail("parseFrom: expected context after with/without",e.lineno,e.colno)),t},i.parseImport=function(){var e=this.peekToken();this.skipSymbol("import")||this.fail("parseImport: expected import",e.lineno,e.colno);var t=this.parseExpression();this.skipSymbol("as")||this.fail('parseImport: expected "as" keyword',e.lineno,e.colno);var n=this.parseExpression(),r=this.parseWithContext(),i=new hr.Import(e.lineno,e.colno,t,n,r);return this.advanceAfterBlockEnd(e.value),i},i.parseFrom=function(){var e=this.peekToken();this.skipSymbol("from")||this.fail("parseFrom: expected from");var t=this.parseExpression();this.skipSymbol("import")||this.fail("parseFrom: expected import",e.lineno,e.colno);for(var n,r=new hr.NodeList;;){var i=this.peekToken();if(i.type===Vt.TOKEN_BLOCK_END){r.children.length||this.fail("parseFrom: Expected at least one import name",e.lineno,e.colno),"-"===i.value.charAt(0)&&(this.dropLeadingWhitespace=!0),this.nextToken();break}r.children.length>0&&!this.skip(Vt.TOKEN_COMMA)&&this.fail("parseFrom: expected comma",e.lineno,e.colno);var s=this.parsePrimary();if("_"===s.value.charAt(0)&&this.fail("parseFrom: names starting with an underscore cannot be imported",s.lineno,s.colno),this.skipSymbol("as")){var a=this.parsePrimary();r.addChild(new hr.Pair(s.lineno,s.colno,s,a))}else r.addChild(s);n=this.parseWithContext()}return new hr.FromImport(e.lineno,e.colno,t,r,n)},i.parseBlock=function(){var e=this.peekToken();this.skipSymbol("block")||this.fail("parseBlock: expected block",e.lineno,e.colno);var t=new hr.Block(e.lineno,e.colno);t.name=this.parsePrimary(),t.name instanceof hr.Symbol||this.fail("parseBlock: variable name expected",e.lineno,e.colno),this.advanceAfterBlockEnd(e.value),t.body=this.parseUntilBlocks("endblock"),this.skipSymbol("endblock"),this.skipSymbol(t.name.value);var n=this.peekToken();return n||this.fail("parseBlock: expected endblock, got end of file"),this.advanceAfterBlockEnd(n.value),t},i.parseExtends=function(){var e="extends",t=this.peekToken();this.skipSymbol(e)||this.fail("parseTemplateRef: expected extends");var n=new hr.Extends(t.lineno,t.colno);return n.template=this.parseExpression(),this.advanceAfterBlockEnd(t.value),n},i.parseInclude=function(){var e="include",t=this.peekToken();this.skipSymbol(e)||this.fail("parseInclude: expected include");var n=new hr.Include(t.lineno,t.colno);return n.template=this.parseExpression(),this.skipSymbol("ignore")&&this.skipSymbol("missing")&&(n.ignoreMissing=!0),this.advanceAfterBlockEnd(t.value),n},i.parseIf=function(){var e,t=this.peekToken();this.skipSymbol("if")||this.skipSymbol("elif")||this.skipSymbol("elseif")?e=new hr.If(t.lineno,t.colno):this.skipSymbol("ifAsync")?e=new hr.IfAsync(t.lineno,t.colno):this.fail("parseIf: expected if, elif, or elseif",t.lineno,t.colno),e.cond=this.parseExpression(),this.advanceAfterBlockEnd(t.value),e.body=this.parseUntilBlocks("elif","elseif","else","endif");var n=this.peekToken();switch(n&&n.value){case"elseif":case"elif":e.else_=this.parseIf();break;case"else":this.advanceAfterBlockEnd(),e.else_=this.parseUntilBlocks("endif"),this.advanceAfterBlockEnd();break;case"endif":e.else_=null,this.advanceAfterBlockEnd();break;default:this.fail("parseIf: expected elif, else, or endif, got end of file")}return e},i.parseSet=function(){var e=this.peekToken();this.skipSymbol("set")||this.fail("parseSet: expected set",e.lineno,e.colno);for(var t,n=new hr.Set(e.lineno,e.colno,[]);(t=this.parsePrimary())&&(n.targets.push(t),this.skip(Vt.TOKEN_COMMA)););return this.skipValue(Vt.TOKEN_OPERATOR,"=")?(n.value=this.parseExpression(),this.advanceAfterBlockEnd(e.value)):this.skip(Vt.TOKEN_BLOCK_END)?(n.body=new hr.Capture(e.lineno,e.colno,this.parseUntilBlocks("endset")),n.value=null,this.advanceAfterBlockEnd()):this.fail("parseSet: expected = or block end in set tag",e.lineno,e.colno),n},i.parseSwitch=function(){var e="switch",t="endswitch",n="case",r="default",i=this.peekToken();this.skipSymbol(e)||this.skipSymbol(n)||this.skipSymbol(r)||this.fail('parseSwitch: expected "switch," "case" or "default"',i.lineno,i.colno);var s=this.parseExpression();this.advanceAfterBlockEnd(e),this.parseUntilBlocks(n,r,t);var a,o=this.peekToken(),c=[];do{this.skipSymbol(n);var l=this.parseExpression();this.advanceAfterBlockEnd(e);var h=this.parseUntilBlocks(n,r,t);c.push(new hr.Case(o.line,o.col,l,h)),o=this.peekToken()}while(o&&o.value===n);switch(o.value){case r:this.advanceAfterBlockEnd(),a=this.parseUntilBlocks(t),this.advanceAfterBlockEnd();break;case t:this.advanceAfterBlockEnd();break;default:this.fail('parseSwitch: expected "case," "default" or "endswitch," got EOF.')}return new hr.Switch(i.lineno,i.colno,s,c,a)},i.parseStatement=function(){var e=this.peekToken();if(e.type!==Vt.TOKEN_SYMBOL&&this.fail("tag name expected",e.lineno,e.colno),this.breakOnBlocks&&-1!==je.indexOf(this.breakOnBlocks,e.value))return null;switch(e.value){case"raw":return this.parseRaw();case"verbatim":return this.parseRaw("verbatim");case"if":case"ifAsync":return this.parseIf();case"for":case"asyncEach":case"asyncAll":return this.parseFor();case"block":return this.parseBlock();case"extends":return this.parseExtends();case"include":return this.parseInclude();case"set":return this.parseSet();case"macro":return this.parseMacro();case"call":return this.parseCall();case"import":return this.parseImport();case"from":return this.parseFrom();case"filter":return this.parseFilterStatement();case"switch":return this.parseSwitch();default:if(this.extensions.length)for(var t=0;t0;){var o=s[0],c=s[1],l=s[2];l===e?r+=1:l===t&&(r-=1),0===r?(i+=c,this.tokens.backN(o.length-c.length)):i+=o}return new hr.Output(a.lineno,a.colno,[new hr.TemplateData(a.lineno,a.colno,i)])},i.parsePostfix=function(e){for(var t,n=this.peekToken();n;){if(n.type===Vt.TOKEN_LEFT_PAREN)e=new hr.FunCall(n.lineno,n.colno,e,this.parseSignature());else if(n.type===Vt.TOKEN_LEFT_BRACKET)(t=this.parseAggregate()).children.length>1&&this.fail("invalid index"),e=new hr.LookupVal(n.lineno,n.colno,e,t.children[0]);else{if(n.type!==Vt.TOKEN_OPERATOR||"."!==n.value)break;this.nextToken();var r=this.nextToken();r.type!==Vt.TOKEN_SYMBOL&&this.fail("expected name as lookup value, got "+r.value,r.lineno,r.colno),t=new hr.Literal(r.lineno,r.colno,r.value),e=new hr.LookupVal(n.lineno,n.colno,e,t)}n=this.peekToken()}return e},i.parseExpression=function(){return this.parseInlineIf()},i.parseInlineIf=function(){var e=this.parseOr();if(this.skipSymbol("if")){var t=this.parseOr(),n=e;(e=new hr.InlineIf(e.lineno,e.colno)).body=n,e.cond=t,this.skipSymbol("else")?e.else_=this.parseOr():e.else_=null}return e},i.parseOr=function(){for(var e=this.parseAnd();this.skipSymbol("or");){var t=this.parseAnd();e=new hr.Or(e.lineno,e.colno,e,t)}return e},i.parseAnd=function(){for(var e=this.parseNot();this.skipSymbol("and");){var t=this.parseNot();e=new hr.And(e.lineno,e.colno,e,t)}return e},i.parseNot=function(){var e=this.peekToken();return this.skipSymbol("not")?new hr.Not(e.lineno,e.colno,this.parseNot()):this.parseIn()},i.parseIn=function(){for(var e=this.parseIs();;){var t=this.nextToken();if(!t)break;var n=t.type===Vt.TOKEN_SYMBOL&&"not"===t.value;if(n||this.pushToken(t),!this.skipSymbol("in")){n&&this.pushToken(t);break}var r=this.parseIs();e=new hr.In(e.lineno,e.colno,e,r),n&&(e=new hr.Not(e.lineno,e.colno,e))}return e},i.parseIs=function(){var e=this.parseCompare();if(this.skipSymbol("is")){var t=this.skipSymbol("not"),n=this.parseCompare();e=new hr.Is(e.lineno,e.colno,e,n),t&&(e=new hr.Not(e.lineno,e.colno,e))}return e},i.parseCompare=function(){for(var e=["==","===","!=","!==","<",">","<=",">="],t=this.parseConcat(),n=[];;){var r=this.nextToken();if(!r)break;if(-1===e.indexOf(r.value)){this.pushToken(r);break}n.push(new hr.CompareOperand(r.lineno,r.colno,this.parseConcat(),r.value))}return n.length?new hr.Compare(n[0].lineno,n[0].colno,t,n):t},i.parseConcat=function(){for(var e=this.parseAdd();this.skipValue(Vt.TOKEN_TILDE,"~");){var t=this.parseAdd();e=new hr.Concat(e.lineno,e.colno,e,t)}return e},i.parseAdd=function(){for(var e=this.parseSub();this.skipValue(Vt.TOKEN_OPERATOR,"+");){var t=this.parseSub();e=new hr.Add(e.lineno,e.colno,e,t)}return e},i.parseSub=function(){for(var e=this.parseMul();this.skipValue(Vt.TOKEN_OPERATOR,"-");){var t=this.parseMul();e=new hr.Sub(e.lineno,e.colno,e,t)}return e},i.parseMul=function(){for(var e=this.parseDiv();this.skipValue(Vt.TOKEN_OPERATOR,"*");){var t=this.parseDiv();e=new hr.Mul(e.lineno,e.colno,e,t)}return e},i.parseDiv=function(){for(var e=this.parseFloorDiv();this.skipValue(Vt.TOKEN_OPERATOR,"/");){var t=this.parseFloorDiv();e=new hr.Div(e.lineno,e.colno,e,t)}return e},i.parseFloorDiv=function(){for(var e=this.parseMod();this.skipValue(Vt.TOKEN_OPERATOR,"//");){var t=this.parseMod();e=new hr.FloorDiv(e.lineno,e.colno,e,t)}return e},i.parseMod=function(){for(var e=this.parsePow();this.skipValue(Vt.TOKEN_OPERATOR,"%");){var t=this.parsePow();e=new hr.Mod(e.lineno,e.colno,e,t)}return e},i.parsePow=function(){for(var e=this.parseUnary();this.skipValue(Vt.TOKEN_OPERATOR,"**");){var t=this.parseUnary();e=new hr.Pow(e.lineno,e.colno,e,t)}return e},i.parseUnary=function(e){var t,n=this.peekToken();return t=this.skipValue(Vt.TOKEN_OPERATOR,"-")?new hr.Neg(n.lineno,n.colno,this.parseUnary(!0)):this.skipValue(Vt.TOKEN_OPERATOR,"+")?new hr.Pos(n.lineno,n.colno,this.parseUnary(!0)):this.parsePrimary(),e||(t=this.parseFilter(t)),t},i.parsePrimary=function(e){var t,n=this.nextToken(),r=null;if(n?n.type===Vt.TOKEN_STRING?t=n.value:n.type===Vt.TOKEN_INT?t=parseInt(n.value,10):n.type===Vt.TOKEN_FLOAT?t=parseFloat(n.value):n.type===Vt.TOKEN_BOOLEAN?"true"===n.value?t=!0:"false"===n.value?t=!1:this.fail("invalid boolean: "+n.value,n.lineno,n.colno):n.type===Vt.TOKEN_NONE?t=null:n.type===Vt.TOKEN_REGEX&&(t=new RegExp(n.value.body,n.value.flags)):this.fail("expected expression, got end of file"),void 0!==t?r=new hr.Literal(n.lineno,n.colno,t):n.type===Vt.TOKEN_SYMBOL?r=new hr.Symbol(n.lineno,n.colno,n.value):(this.pushToken(n),r=this.parseAggregate()),e||(r=this.parsePostfix(r)),r)return r;throw this.error("unexpected token: "+n.value,n.lineno,n.colno)},i.parseFilterName=function(){for(var e=this.expect(Vt.TOKEN_SYMBOL),t=e.value;this.skipValue(Vt.TOKEN_OPERATOR,".");)t+="."+this.expect(Vt.TOKEN_SYMBOL).value;return new hr.Symbol(e.lineno,e.colno,t)},i.parseFilterArgs=function(e){return this.peekToken().type===Vt.TOKEN_LEFT_PAREN?this.parsePostfix(e).args.children:[]},i.parseFilter=function(e){for(;this.skip(Vt.TOKEN_PIPE);){var t=this.parseFilterName();e=new hr.Filter(t.lineno,t.colno,t,new hr.NodeList(t.lineno,t.colno,[e].concat(this.parseFilterArgs(e))))}return e},i.parseFilterStatement=function(){var e=this.peekToken();this.skipSymbol("filter")||this.fail("parseFilterStatement: expected filter");var t=this.parseFilterName(),n=this.parseFilterArgs(t);this.advanceAfterBlockEnd(e.value);var r=new hr.Capture(t.lineno,t.colno,this.parseUntilBlocks("endfilter"));this.advanceAfterBlockEnd();var i=new hr.Filter(t.lineno,t.colno,t,new hr.NodeList(t.lineno,t.colno,[r].concat(n)));return new hr.Output(t.lineno,t.colno,[i])},i.parseAggregate=function(){var e,t=this.nextToken();switch(t.type){case Vt.TOKEN_LEFT_PAREN:e=new hr.Group(t.lineno,t.colno);break;case Vt.TOKEN_LEFT_BRACKET:e=new hr.Array(t.lineno,t.colno);break;case Vt.TOKEN_LEFT_CURLY:e=new hr.Dict(t.lineno,t.colno);break;default:return null}for(;;){var n=this.peekToken().type;if(n===Vt.TOKEN_RIGHT_PAREN||n===Vt.TOKEN_RIGHT_BRACKET||n===Vt.TOKEN_RIGHT_CURLY){this.nextToken();break}if(e.children.length>0&&(this.skip(Vt.TOKEN_COMMA)||this.fail("parseAggregate: expected comma after expression",t.lineno,t.colno)),e instanceof hr.Dict){var r=this.parsePrimary();this.skip(Vt.TOKEN_COLON)||this.fail("parseAggregate: expected colon after dict key",t.lineno,t.colno);var i=this.parseExpression();e.addChild(new hr.Pair(r.lineno,r.colno,r,i))}else{var s=this.parseExpression();e.addChild(s)}}return e},i.parseSignature=function(e,t){var n=this.peekToken();if(!t&&n.type!==Vt.TOKEN_LEFT_PAREN){if(e)return null;this.fail("expected arguments",n.lineno,n.colno)}n.type===Vt.TOKEN_LEFT_PAREN&&(n=this.nextToken());for(var r=new hr.NodeList(n.lineno,n.colno),i=new hr.KeywordArgs(n.lineno,n.colno),s=!1;;){if(n=this.peekToken(),!t&&n.type===Vt.TOKEN_RIGHT_PAREN){this.nextToken();break}if(t&&n.type===Vt.TOKEN_BLOCK_END)break;if(s&&!this.skip(Vt.TOKEN_COMMA))this.fail("parseSignature: expected comma after expression",n.lineno,n.colno);else{var a=this.parseExpression();this.skipValue(Vt.TOKEN_OPERATOR,"=")?i.addChild(new hr.Pair(a.lineno,a.colno,a,this.parseExpression())):r.addChild(a)}s=!0}return i.children.length&&r.addChild(i),r},i.parseUntilBlocks=function(){for(var e=this.breakOnBlocks,t=arguments.length,n=new Array(t),r=0;re.length)a=i.slice(0,e.length),i.slice(a.length,o).forEach((function(e,n){n":">","<=":"<=",">=":">="},Pr=function(e){var t,n;function r(){return e.apply(this,arguments)||this}n=e,(t=r).prototype=Object.create(n.prototype),t.prototype.constructor=t,vr(t,n);var i=r.prototype;return i.init=function(e,t){this.templateName=e,this.codebuf=[],this.lastId=0,this.buffer=null,this.bufferStack=[],this._scopeClosers="",this.inBlock=!1,this.throwOnUndefined=t},i.fail=function(e,t,n){throw void 0!==t&&(t+=1),void 0!==n&&(n+=1),new Dr(e,t,n)},i._pushBuffer=function(){var e=this._tmpid();return this.bufferStack.push(this.buffer),this.buffer=e,this._emit("var "+this.buffer+' = "";'),e},i._popBuffer=function(){this.buffer=this.bufferStack.pop()},i._emit=function(e){this.codebuf.push(e)},i._emitLine=function(e){this._emit(e+"\n")},i._emitLines=function(){for(var e=this,t=arguments.length,n=new Array(t),r=0;r0&&i._emit(","),i.compile(e,t)})),r&&this._emit(r)},i._compileExpression=function(e,t){this.assertType(e,hr.Literal,hr.Symbol,hr.Group,hr.Array,hr.Dict,hr.FunCall,hr.Caller,hr.Filter,hr.LookupVal,hr.Compare,hr.InlineIf,hr.In,hr.Is,hr.And,hr.Or,hr.Not,hr.Add,hr.Concat,hr.Sub,hr.Mul,hr.Div,hr.FloorDiv,hr.Mod,hr.Pow,hr.Neg,hr.Pos,hr.Compare,hr.NodeList),this.compile(e,t)},i.assertType=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r0&&r._emit(","),e){r._emitLine("function(cb) {"),r._emitLine("if(!cb) { cb = function(err) { if(err) { throw err; }}}");var i=r._pushBuffer();r._withScopedSyntax((function(){r.compile(e,t),r._emitLine("cb(null, "+i+");")})),r._popBuffer(),r._emitLine("return "+i+";"),r._emitLine("}")}else r._emit("null")})),n){var o=this._tmpid();this._emitLine(", "+this._makeCallback(o)),this._emitLine(this.buffer+" += runtime.suppressValue("+o+", "+a+" && env.opts.autoescape);"),this._addScopeLevel()}else this._emit(")"),this._emit(", "+a+" && env.opts.autoescape);\n")},i.compileCallExtensionAsync=function(e,t){this.compileCallExtension(e,t,!0)},i.compileNodeList=function(e,t){this._compileChildren(e,t)},i.compileLiteral=function(e){if("string"==typeof e.value){var t=e.value.replace(/\\/g,"\\\\");t=(t=(t=(t=(t=t.replace(/"/g,'\\"')).replace(/\n/g,"\\n")).replace(/\r/g,"\\r")).replace(/\t/g,"\\t")).replace(/\u2028/g,"\\u2028"),this._emit('"'+t+'"')}else null===e.value?this._emit("null"):this._emit(e.value.toString())},i.compileSymbol=function(e,t){var n=e.value,r=t.lookup(n);r?this._emit(r):this._emit('runtime.contextOrFrameLookup(context, frame, "'+n+'")')},i.compileGroup=function(e,t){this._compileAggregate(e,t,"(",")")},i.compileArray=function(e,t){this._compileAggregate(e,t,"[","]")},i.compileDict=function(e,t){this._compileAggregate(e,t,"{","}")},i.compilePair=function(e,t){var n=e.key,r=e.value;n instanceof hr.Symbol?n=new hr.Literal(n.lineno,n.colno,n.value):n instanceof hr.Literal&&"string"==typeof n.value||this.fail("compilePair: Dict keys must be strings or names",n.lineno,n.colno),this.compile(n,t),this._emit(": "),this._compileExpression(r,t)},i.compileInlineIf=function(e,t){this._emit("("),this.compile(e.cond,t),this._emit("?"),this.compile(e.body,t),this._emit(":"),null!==e.else_?this.compile(e.else_,t):this._emit('""'),this._emit(")")},i.compileIn=function(e,t){this._emit("runtime.inOperator("),this.compile(e.left,t),this._emit(","),this.compile(e.right,t),this._emit(")")},i.compileIs=function(e,t){var n=e.right.name?e.right.name.value:e.right.value;this._emit('env.getTest("'+n+'").call(context, '),this.compile(e.left,t),e.right.args&&(this._emit(","),this.compile(e.right.args,t)),this._emit(") === true")},i._binOpEmitter=function(e,t,n){this.compile(e.left,t),this._emit(n),this.compile(e.right,t)},i.compileOr=function(e,t){return this._binOpEmitter(e,t," || ")},i.compileAnd=function(e,t){return this._binOpEmitter(e,t," && ")},i.compileAdd=function(e,t){return this._binOpEmitter(e,t," + ")},i.compileConcat=function(e,t){return this._binOpEmitter(e,t,' + "" + ')},i.compileSub=function(e,t){return this._binOpEmitter(e,t," - ")},i.compileMul=function(e,t){return this._binOpEmitter(e,t," * ")},i.compileDiv=function(e,t){return this._binOpEmitter(e,t," / ")},i.compileMod=function(e,t){return this._binOpEmitter(e,t," % ")},i.compileNot=function(e,t){this._emit("!"),this.compile(e.target,t)},i.compileFloorDiv=function(e,t){this._emit("Math.floor("),this.compile(e.left,t),this._emit(" / "),this.compile(e.right,t),this._emit(")")},i.compilePow=function(e,t){this._emit("Math.pow("),this.compile(e.left,t),this._emit(", "),this.compile(e.right,t),this._emit(")")},i.compileNeg=function(e,t){this._emit("-"),this.compile(e.target,t)},i.compilePos=function(e,t){this._emit("+"),this.compile(e.target,t)},i.compileCompare=function(e,t){var n=this;this.compile(e.expr,t),e.ops.forEach((function(e){n._emit(" "+Mr[e.type]+" "),n.compile(e.expr,t)}))},i.compileLookupVal=function(e,t){this._emit("runtime.memberLookup(("),this._compileExpression(e.target,t),this._emit("),"),this._compileExpression(e.val,t),this._emit(")")},i._getNodeName=function(e){switch(e.typename){case"Symbol":return e.value;case"FunCall":return"the return value of ("+this._getNodeName(e.name)+")";case"LookupVal":return this._getNodeName(e.target)+'["'+this._getNodeName(e.val)+'"]';case"Literal":return e.value.toString();default:return"--expression--"}},i.compileFunCall=function(e,t){this._emit("(lineno = "+e.lineno+", colno = "+e.colno+", "),this._emit("runtime.callWrap("),this._compileExpression(e.name,t),this._emit(', "'+this._getNodeName(e.name).replace(/"/g,'\\"')+'", context, '),this._compileAggregate(e.args,t,"[","])"),this._emit(")")},i.compileFilter=function(e,t){var n=e.name;this.assertType(n,hr.Symbol),this._emit('env.getFilter("'+n.value+'").call(context, '),this._compileAggregate(e.args,t),this._emit(")")},i.compileFilterAsync=function(e,t){var n=e.name,r=e.symbol.value;this.assertType(n,hr.Symbol),t.set(r,r),this._emit('env.getFilter("'+n.value+'").call(context, '),this._compileAggregate(e.args,t),this._emitLine(", "+this._makeCallback(r)),this._addScopeLevel()},i.compileKeywordArgs=function(e,t){this._emit("runtime.makeKeywordArgs("),this.compileDict(e,t),this._emit(")")},i.compileSet=function(e,t){var n=this,r=[];e.targets.forEach((function(e){var i=e.value,s=t.lookup(i);null==s&&(s=n._tmpid(),n._emitLine("var "+s+";")),r.push(s)})),e.value?(this._emit(r.join(" = ")+" = "),this._compileExpression(e.value,t),this._emitLine(";")):(this._emit(r.join(" = ")+" = "),this.compile(e.body,t),this._emitLine(";")),e.targets.forEach((function(e,t){var i=r[t],s=e.value;n._emitLine('frame.set("'+s+'", '+i+", true);"),n._emitLine("if(frame.topLevel) {"),n._emitLine('context.setVariable("'+s+'", '+i+");"),n._emitLine("}"),"_"!==s.charAt(0)&&(n._emitLine("if(frame.topLevel) {"),n._emitLine('context.addExport("'+s+'", '+i+");"),n._emitLine("}"))}))},i.compileSwitch=function(e,t){var n=this;this._emit("switch ("),this.compile(e.expr,t),this._emit(") {"),e.cases.forEach((function(e,r){n._emit("case "),n.compile(e.cond,t),n._emit(": "),n.compile(e.body,t),e.body.children.length&&n._emitLine("break;")})),e.default&&(this._emit("default:"),this.compile(e.default,t)),this._emit("}")},i.compileIf=function(e,t,n){var r=this;this._emit("if("),this._compileExpression(e.cond,t),this._emitLine(") {"),this._withScopedSyntax((function(){r.compile(e.body,t),n&&r._emit("cb()")})),e.else_?(this._emitLine("}\nelse {"),this._withScopedSyntax((function(){r.compile(e.else_,t),n&&r._emit("cb()")}))):n&&(this._emitLine("}\nelse {"),this._emit("cb()")),this._emitLine("}")},i.compileIfAsync=function(e,t){this._emit("(function(cb) {"),this.compileIf(e,t,!0),this._emit("})("+this._makeCallback()),this._addScopeLevel()},i._emitLoopBindings=function(e,t,n,r){var i=this;[{name:"index",val:n+" + 1"},{name:"index0",val:n},{name:"revindex",val:r+" - "+n},{name:"revindex0",val:r+" - "+n+" - 1"},{name:"first",val:n+" === 0"},{name:"last",val:n+" === "+r+" - 1"},{name:"length",val:r}].forEach((function(e){i._emitLine('frame.set("loop.'+e.name+'", '+e.val+");")}))},i.compileFor=function(e,t){var n=this,r=this._tmpid(),i=this._tmpid(),s=this._tmpid();if(t=t.push(),this._emitLine("frame = frame.push();"),this._emit("var "+s+" = "),this._compileExpression(e.arr,t),this._emitLine(";"),this._emit("if("+s+") {"),this._emitLine(s+" = runtime.fromIterator("+s+");"),e.name instanceof hr.Array){this._emitLine("var "+r+";"),this._emitLine("if(runtime.isArray("+s+")) {"),this._emitLine("var "+i+" = "+s+".length;"),this._emitLine("for("+r+"=0; "+r+" < "+s+".length; "+r+"++) {"),e.name.children.forEach((function(i,a){var o=n._tmpid();n._emitLine("var "+o+" = "+s+"["+r+"]["+a+"];"),n._emitLine('frame.set("'+i+'", '+s+"["+r+"]["+a+"]);"),t.set(e.name.children[a].value,o)})),this._emitLoopBindings(e,s,r,i),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("}"),this._emitLine("} else {");var a=e.name.children,o=a[0],c=a[1],l=this._tmpid(),h=this._tmpid();t.set(o.value,l),t.set(c.value,h),this._emitLine(r+" = -1;"),this._emitLine("var "+i+" = runtime.keys("+s+").length;"),this._emitLine("for(var "+l+" in "+s+") {"),this._emitLine(r+"++;"),this._emitLine("var "+h+" = "+s+"["+l+"];"),this._emitLine('frame.set("'+o.value+'", '+l+");"),this._emitLine('frame.set("'+c.value+'", '+h+");"),this._emitLoopBindings(e,s,r,i),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("}"),this._emitLine("}")}else{var u=this._tmpid();t.set(e.name.value,u),this._emitLine("var "+i+" = "+s+".length;"),this._emitLine("for(var "+r+"=0; "+r+" < "+s+".length; "+r+"++) {"),this._emitLine("var "+u+" = "+s+"["+r+"];"),this._emitLine('frame.set("'+e.name.value+'", '+u+");"),this._emitLoopBindings(e,s,r,i),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("}")}this._emitLine("}"),e.else_&&(this._emitLine("if (!"+i+") {"),this.compile(e.else_,t),this._emitLine("}")),this._emitLine("frame = frame.pop();")},i._compileAsyncLoop=function(e,t,n){var r=this,i=this._tmpid(),s=this._tmpid(),a=this._tmpid(),o=n?"asyncAll":"asyncEach";if(t=t.push(),this._emitLine("frame = frame.push();"),this._emit("var "+a+" = runtime.fromIterator("),this._compileExpression(e.arr,t),this._emitLine(");"),e.name instanceof hr.Array){var c=e.name.children.length;this._emit("runtime."+o+"("+a+", "+c+", function("),e.name.children.forEach((function(e){r._emit(e.value+",")})),this._emit(i+","+s+",next) {"),e.name.children.forEach((function(e){var n=e.value;t.set(n,n),r._emitLine('frame.set("'+n+'", '+n+");")}))}else{var l=e.name.value;this._emitLine("runtime."+o+"("+a+", 1, function("+l+", "+i+", "+s+",next) {"),this._emitLine('frame.set("'+l+'", '+l+");"),t.set(l,l)}this._emitLoopBindings(e,a,i,s),this._withScopedSyntax((function(){var s;n&&(s=r._pushBuffer()),r.compile(e.body,t),r._emitLine("next("+i+(s?","+s:"")+");"),n&&r._popBuffer()}));var h=this._tmpid();this._emitLine("}, "+this._makeCallback(h)),this._addScopeLevel(),n&&this._emitLine(this.buffer+" += "+h+";"),e.else_&&(this._emitLine("if (!"+a+".length) {"),this.compile(e.else_,t),this._emitLine("}")),this._emitLine("frame = frame.pop();")},i.compileAsyncEach=function(e,t){this._compileAsyncLoop(e,t)},i.compileAsyncAll=function(e,t){this._compileAsyncLoop(e,t,!0)},i._compileMacro=function(e,t){var n=this,r=[],i=null,s="macro_"+this._tmpid(),a=void 0!==t;e.args.children.forEach((function(t,s){s===e.args.children.length-1&&t instanceof hr.Dict?i=t:(n.assertType(t,hr.Symbol),r.push(t))}));var o,c=[].concat(r.map((function(e){return"l_"+e.value})),["kwargs"]),l=r.map((function(e){return'"'+e.value+'"'})),h=(i&&i.children||[]).map((function(e){return'"'+e.key.value+'"'}));o=a?t.push(!0):new Rr,this._emitLines("var "+s+" = runtime.makeMacro(","["+l.join(", ")+"], ","["+h.join(", ")+"], ","function ("+c.join(", ")+") {","var callerFrame = frame;","frame = "+(a?"frame.push(true);":"new runtime.Frame();"),"kwargs = kwargs || {};",'if (Object.prototype.hasOwnProperty.call(kwargs, "caller")) {','frame.set("caller", kwargs.caller); }'),r.forEach((function(e){n._emitLine('frame.set("'+e.value+'", l_'+e.value+");"),o.set(e.value,"l_"+e.value)})),i&&i.children.forEach((function(e){var t=e.key.value;n._emit('frame.set("'+t+'", '),n._emit('Object.prototype.hasOwnProperty.call(kwargs, "'+t+'")'),n._emit(' ? kwargs["'+t+'"] : '),n._compileExpression(e.value,o),n._emit(");")}));var u=this._pushBuffer();return this._withScopedSyntax((function(){n.compile(e.body,o)})),this._emitLine("frame = "+(a?"frame.pop();":"callerFrame;")),this._emitLine("return new runtime.SafeString("+u+");"),this._emitLine("});"),this._popBuffer(),s},i.compileMacro=function(e,t){var n=this._compileMacro(e),r=e.name.value;t.set(r,n),t.parent?this._emitLine('frame.set("'+r+'", '+n+");"):("_"!==e.name.value.charAt(0)&&this._emitLine('context.addExport("'+r+'");'),this._emitLine('context.setVariable("'+r+'", '+n+");"))},i.compileCaller=function(e,t){this._emit("(function (){");var n=this._compileMacro(e,t);this._emit("return "+n+";})()")},i._compileGetTemplate=function(e,t,n,r){var i=this._tmpid(),s=this._templateName(),a=this._makeCallback(i),o=n?"true":"false",c=r?"true":"false";return this._emit("env.getTemplate("),this._compileExpression(e.template,t),this._emitLine(", "+o+", "+s+", "+c+", "+a),i},i.compileImport=function(e,t){var n=e.target.value,r=this._compileGetTemplate(e,t,!1,!1);this._addScopeLevel(),this._emitLine(r+".getExported("+(e.withContext?"context.getVariables(), frame, ":"")+this._makeCallback(r)),this._addScopeLevel(),t.set(n,r),t.parent?this._emitLine('frame.set("'+n+'", '+r+");"):this._emitLine('context.setVariable("'+n+'", '+r+");")},i.compileFromImport=function(e,t){var n=this,r=this._compileGetTemplate(e,t,!1,!1);this._addScopeLevel(),this._emitLine(r+".getExported("+(e.withContext?"context.getVariables(), frame, ":"")+this._makeCallback(r)),this._addScopeLevel(),e.names.children.forEach((function(e){var i,s,a=n._tmpid();e instanceof hr.Pair?(i=e.key.value,s=e.value.value):s=i=e.value,n._emitLine("if(Object.prototype.hasOwnProperty.call("+r+', "'+i+'")) {'),n._emitLine("var "+a+" = "+r+"."+i+";"),n._emitLine("} else {"),n._emitLine("cb(new Error(\"cannot import '"+i+"'\")); return;"),n._emitLine("}"),t.set(s,a),t.parent?n._emitLine('frame.set("'+s+'", '+a+");"):n._emitLine('context.setVariable("'+s+'", '+a+");")}))},i.compileBlock=function(e){var t=this._tmpid();this.inBlock||this._emit('(parentTemplate ? function(e, c, f, r, cb) { cb(""); } : '),this._emit('context.getBlock("'+e.name.value+'")'),this.inBlock||this._emit(")"),this._emitLine("(env, context, frame, runtime, "+this._makeCallback(t)),this._emitLine(this.buffer+" += "+t+";"),this._addScopeLevel()},i.compileSuper=function(e,t){var n=e.blockName.value,r=e.symbol.value,i=this._makeCallback(r);this._emitLine('context.getSuper(env, "'+n+'", b_'+n+", frame, runtime, "+i),this._emitLine(r+" = runtime.markSafe("+r+");"),this._addScopeLevel(),t.set(r,r)},i.compileExtends=function(e,t){var n=this._tmpid(),r=this._compileGetTemplate(e,t,!0,!1);this._emitLine("parentTemplate = "+r),this._emitLine("for(var "+n+" in parentTemplate.blocks) {"),this._emitLine("context.addBlock("+n+", parentTemplate.blocks["+n+"]);"),this._emitLine("}"),this._addScopeLevel()},i.compileInclude=function(e,t){this._emitLine("var tasks = [];"),this._emitLine("tasks.push("),this._emitLine("function(callback) {");var n=this._compileGetTemplate(e,t,!1,e.ignoreMissing);this._emitLine("callback(null,"+n+");});"),this._emitLine("});");var r=this._tmpid();this._emitLine("tasks.push("),this._emitLine("function(template, callback){"),this._emitLine("template.render(context.getVariables(), frame, "+this._makeCallback(r)),this._emitLine("callback(null,"+r+");});"),this._emitLine("});"),this._emitLine("tasks.push("),this._emitLine("function(result, callback){"),this._emitLine(this.buffer+" += result;"),this._emitLine("callback(null);"),this._emitLine("});"),this._emitLine("env.waterfall(tasks, function(){"),this._addScopeLevel()},i.compileTemplateData=function(e,t){this.compileLiteral(e,t)},i.compileCapture=function(e,t){var n=this,r=this.buffer;this.buffer="output",this._emitLine("(function() {"),this._emitLine('var output = "";'),this._withScopedSyntax((function(){n.compile(e.body,t)})),this._emitLine("return output;"),this._emitLine("})()"),this.buffer=r},i.compileOutput=function(e,t){var n=this;e.children.forEach((function(r){r instanceof hr.TemplateData?r.value&&(n._emit(n.buffer+" += "),n.compileLiteral(r,t),n._emitLine(";")):(n._emit(n.buffer+" += runtime.suppressValue("),n.throwOnUndefined&&n._emit("runtime.ensureDefined("),n.compile(r,t),n.throwOnUndefined&&n._emit(","+e.lineno+","+e.colno+")"),n._emit(", env.opts.autoescape);\n"))}))},i.compileRoot=function(e,t){var n=this;t&&this.fail("compileRoot: root node can't have frame"),t=new Rr,this._emitFuncBegin(e,"root"),this._emitLine("var parentTemplate = null;"),this._compileChildren(e,t),this._emitLine("if(parentTemplate) {"),this._emitLine("parentTemplate.rootRenderFunc(env, context, frame, runtime, cb);"),this._emitLine("} else {"),this._emitLine("cb(null, "+this.buffer+");"),this._emitLine("}"),this._emitFuncEnd(!0),this.inBlock=!0;var r=[],i=e.findAll(hr.Block);i.forEach((function(e,t){var i=e.name.value;if(-1!==r.indexOf(i))throw new Error('Block "'+i+'" defined more than once.');r.push(i),n._emitFuncBegin(e,"b_"+i);var s=new Rr;n._emitLine("var frame = frame.push(true);"),n.compile(e.body,s),n._emitFuncEnd()})),this._emitLine("return {"),i.forEach((function(e,t){var r="b_"+e.name.value;n._emitLine(r+": "+r+",")})),this._emitLine("root: root\n};")},i.compile=function(e,t){var n=this["compile"+e.typename];n?n.call(this,e,t):this.fail("compile: Cannot compile node: "+e.typename,e.lineno,e.colno)},i.getCode=function(){return this.codebuf.join("")},r}(en.Obj),xr={compile:function(e,t,n,r,i){void 0===i&&(i={});var s=new Pr(r,i.throwOnUndefined),a=(n||[]).map((function(e){return e.preprocess})).filter((function(e){return!!e})).reduce((function(e,t){return t(e)}),e);return s.compile(Nr.transform(fr.parse(a,n,i),t,r)),s.getCode()},Compiler:Pr},wr=Ke((function(e){var t=e.exports={};function n(e,t){return null==e||!1===e?t:e}function r(e){return e!=e}function i(e){var t=(e=n(e,"")).toLowerCase();return kr.copySafeness(e,t.charAt(0).toUpperCase()+t.slice(1))}function s(e){if(je.isString(e))return e.split("");if(je.isObject(e))return je._entries(e||{}).map((function(e){return{key:e[0],value:e[1]}}));if(je.isArray(e))return e;throw new je.TemplateError("list filter: type not iterable")}function a(e){return function(t,n,r){void 0===n&&(n="truthy");var i=this,s=i.env.getTest(n);return je.toArray(t).filter((function(t){return s.call(i,t,r)===e}))}}function o(e){return kr.copySafeness(e,e.replace(/^\s*|\s*$/g,""))}t.abs=Math.abs,t.batch=function(e,t,n){var r,i=[],s=[];for(r=0;r=t)return e;var r=t-e.length,i=je.repeat(" ",r/2-r%2),s=je.repeat(" ",r/2);return kr.copySafeness(e,i+e+s)},t.default=function(e,t,n){return n?e||t:void 0!==e?e:t},t.dictsort=function(e,t,n){if(!je.isObject(e))throw new je.TemplateError("dictsort filter: val must be an object");var r,i=[];for(var s in e)i.push([s,e[s]]);if(void 0===n||"key"===n)r=0;else{if("value"!==n)throw new je.TemplateError("dictsort filter: You can only sort by either key or value");r=1}return i.sort((function(e,n){var i=e[r],s=n[r];return t||(je.isString(i)&&(i=i.toUpperCase()),je.isString(s)&&(s=s.toUpperCase())),i>s?1:i===s?0:-1})),i},t.dump=function(e,t){return JSON.stringify(e,null,t)},t.escape=function(e){return e instanceof kr.SafeString?e:(e=null==e?"":e,kr.markSafe(je.escape(e.toString())))},t.safe=function(e){return e instanceof kr.SafeString?e:(e=null==e?"":e,kr.markSafe(e.toString()))},t.first=function(e){return e[0]},t.forceescape=function(e){return e=null==e?"":e,kr.markSafe(je.escape(e.toString()))},t.groupby=function(e,t){return je.groupBy(e,t,this.env.opts.throwOnUndefined)},t.indent=function(e,t,r){if(""===(e=n(e,"")))return"";t=t||4;var i=e.split("\n"),s=je.repeat(" ",t),a=i.map((function(e,t){return 0!==t||r?""+s+e:e})).join("\n");return kr.copySafeness(e,a)},t.join=function(e,t,n){return t=t||"",n&&(e=je.map(e,(function(e){return e[n]}))),e.join(t)},t.last=function(e){return e[e.length-1]},t.length=function(e){var t=n(e,"");return void 0!==t?"function"==typeof Map&&t instanceof Map||"function"==typeof Set&&t instanceof Set?t.size:!je.isObject(t)||t instanceof kr.SafeString?t.length:je.keys(t).length:0},t.list=s,t.lower=function(e){return(e=n(e,"")).toLowerCase()},t.nl2br=function(e){return null==e?"":kr.copySafeness(e,e.replace(/\r\n|\n/g,"
\n"))},t.random=function(e){return e[Math.floor(Math.random()*e.length)]},t.reject=a(!1),t.rejectattr=function(e,t){return e.filter((function(e){return!e[t]}))},t.select=a(!0),t.selectattr=function(e,t){return e.filter((function(e){return!!e[t]}))},t.replace=function(e,t,n,r){var i=e;if(t instanceof RegExp)return e.replace(t,n);void 0===r&&(r=-1);var s="";if("number"==typeof t)t=""+t;else if("string"!=typeof t)return e;if("number"==typeof e&&(e=""+e),"string"!=typeof e&&!(e instanceof kr.SafeString))return e;if(""===t)return s=n+e.split("").join(n)+n,kr.copySafeness(e,s);var a=e.indexOf(t);if(0===r||-1===a)return e;for(var o=0,c=0;a>-1&&(-1===r||c=i&&h.push(n),s.push(h)}return s},t.sum=function(e,t,n){return void 0===n&&(n=0),t&&(e=je.map(e,(function(e){return e[t]}))),n+e.reduce((function(e,t){return e+t}),0)},t.sort=kr.makeMacro(["value","reverse","case_sensitive","attribute"],[],(function(e,t,n,r){var i=this,s=je.map(e,(function(e){return e})),a=je.getAttrGetter(r);return s.sort((function(e,s){var o=r?a(e):e,c=r?a(s):s;if(i.env.opts.throwOnUndefined&&r&&(void 0===o||void 0===c))throw new TypeError('sort: attribute "'+r+'" resolved to undefined');return!n&&je.isString(o)&&je.isString(c)&&(o=o.toLowerCase(),c=c.toLowerCase()),oc?t?-1:1:0})),s})),t.string=function(e){return kr.copySafeness(e,e)},t.striptags=function(e,t){var r=o((e=n(e,"")).replace(/<\/?([a-z][a-z0-9]*)\b[^>]*>|/gi,"")),i="";return i=t?r.replace(/^ +| +$/gm,"").replace(/ +/g," ").replace(/(\r\n)/g,"\n").replace(/\n\n\n+/g,"\n\n"):r.replace(/\s+/gi," "),kr.copySafeness(e,i)},t.title=function(e){var t=(e=n(e,"")).split(" ").map((function(e){return i(e)}));return kr.copySafeness(e,t.join(" "))},t.trim=o,t.truncate=function(e,t,r,i){var s=e;if(t=t||255,(e=n(e,"")).length<=t)return e;if(r)e=e.substring(0,t);else{var a=e.lastIndexOf(" ",t);-1===a&&(a=t),e=e.substring(0,a)}return e+=null!=i?i:"...",kr.copySafeness(s,e)},t.upper=function(e){return(e=n(e,"")).toUpperCase()},t.urlencode=function(e){var t=encodeURIComponent;return je.isString(e)?t(e):(je.isArray(e)?e:je._entries(e)).map((function(e){var n=e[0],r=e[1];return t(n)+"="+t(r)})).join("&")};var c=/^(?:\(|<|<)?(.*?)(?:\.|,|\)|\n|>)?$/,l=/^[\w.!#$%&'*+\-\/=?\^`{|}~]+@[a-z\d\-]+(\.[a-z\d\-]+)+$/i,h=/^https?:\/\/.*$/,u=/^www\./,p=/\.(?:org|net|com)(?:\:|\/|$)/;t.urlize=function(e,t,n){r(t)&&(t=1/0);var i=!0===n?' rel="nofollow"':"";return e.split(/(\s+)/).filter((function(e){return e&&e.length})).map((function(e){var n=e.match(c),r=n?n[1]:e,s=r.substr(0,t);return h.test(r)?'"+s+"":u.test(r)?'"+s+"":l.test(r)?''+r+"":p.test(r)?'"+s+"":e})).join("")},t.wordcount=function(e){var t=(e=n(e,""))?e.match(/\w+/g):null;return t?t.length:null},t.float=function(e,t){var n=parseFloat(e);return r(n)?t:n};var f=kr.makeMacro(["value","default","base"],[],(function(e,t,n){void 0===n&&(n=10);var i=parseInt(e,n);return r(i)?t:i}));t.int=f,t.d=t.default,t.e=t.escape})),Br={};function Fr(e,t){for(var n=0,r=e.length-1;r>=0;r--){var i=e[r];"."===i?e.splice(r,1):".."===i?(e.splice(r,1),n++):n&&(e.splice(r,1),n--)}if(t)for(;n--;n)e.unshift("..");return e}var Ur=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/,Hr=function(e){return Ur.exec(e).slice(1)};function Gr(){for(var e="",t=!1,n=arguments.length-1;n>=-1&&!t;n--){var r=n>=0?arguments[n]:"/";if("string"!=typeof r)throw new TypeError("Arguments to path.resolve must be strings");r&&(e=r+"/"+e,t="/"===r.charAt(0))}return(t?"/":"")+(e=Fr(jr(e.split("/"),(function(e){return!!e})),!t).join("/"))||"."}function Yr(e){var t=qr(e),n="/"===Vr(e,-1);return(e=Fr(jr(e.split("/"),(function(e){return!!e})),!t).join("/"))||t||(e="."),e&&n&&(e+="/"),(t?"/":"")+e}function qr(e){return"/"===e.charAt(0)}var Kr={extname:function(e){return Hr(e)[3]},basename:function(e,t){var n=Hr(e)[2];return t&&n.substr(-1*t.length)===t&&(n=n.substr(0,n.length-t.length)),n},dirname:function(e){var t=Hr(e),n=t[0],r=t[1];return n||r?(r&&(r=r.substr(0,r.length-1)),n+r):"."},sep:"/",delimiter:":",relative:function(e,t){function n(e){for(var t=0;t=0&&""===e[n];n--);return t>n?[]:e.slice(t,n-t+1)}e=Gr(e).substr(1),t=Gr(t).substr(1);for(var r=n(e.split("/")),i=n(t.split("/")),s=Math.min(r.length,i.length),a=s,o=0;o=t},t.greaterthan=function(e,t){return e>t},t.gt=t.greaterthan,t.le=function(e,t){return e<=t},t.lessthan=function(e,t){return e=e.length&&(t=0),this.current=e[t],this.current}}}ni.callable,ni.defined,ni.divisibleby,ni.escaped,ni.equalto,ni.eq,ni.sameas,ni.even,ni.falsy,ni.ge,ni.greaterthan,ni.gt,ni.le,ni.lessthan,ni.lt,ni.lower,ni.ne,ni.number,ni.odd,ni.string,ni.truthy,ni.undefined,ni.upper,ni.iterable,ni.mapping;var ii=function(){return{range:function(e,t,n){void 0===t?(t=e,e=0,n=1):n||(n=1);var r=[];if(n>0)for(var i=e;it;s+=n)r.push(s);return r},cycler:function(){return ri(Array.prototype.slice.call(arguments))},joiner:function(e){return function(e){e=e||",";var t=!0;return function(){var n=t?"":e;return t=!1,n}}(e)}}};function si(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,ai(e,t)}function ai(e,t){return ai=Object.setPrototypeOf||function(e,t){return e.__proto__=t,e},ai(e,t)}var oi=ti.FileSystemLoader,ci=ti.WebLoader,li=ti.PrecompiledLoader,hi=en.Obj,ui=en.EmitterObj,pi=kr.handleError,fi=kr.Frame;function di(e,t,n){Gt((function(){e(t,n)}))}var Ei={type:"code",obj:{root:function(e,t,n,r,i){try{i(null,"")}catch(e){i(pi(e,null,null))}}}},mi=function(e){function t(){return e.apply(this,arguments)||this}si(t,e);var n=t.prototype;return n.init=function(e,t){var n=this;t=this.opts=t||{},this.opts.dev=!!t.dev,this.opts.autoescape=null==t.autoescape||t.autoescape,this.opts.throwOnUndefined=!!t.throwOnUndefined,this.opts.trimBlocks=!!t.trimBlocks,this.opts.lstripBlocks=!!t.lstripBlocks,this.loaders=[],e?this.loaders=je.isArray(e)?e:[e]:oi?this.loaders=[new oi("views")]:ci&&(this.loaders=[new ci("/views")]),"undefined"!=typeof window&&window.jinja2Precompiled&&this.loaders.unshift(new li(window.jinja2Precompiled)),this._initLoaders(),this.globals=ii(),this.filters={},this.tests={},this.asyncFilters=[],this.extensions={},this.extensionsList=[],je._entries(wr).forEach((function(e){var t=e[0],r=e[1];return n.addFilter(t,r)})),je._entries(ni).forEach((function(e){var t=e[0],r=e[1];return n.addTest(t,r)}))},n._initLoaders=function(){var e=this;this.loaders.forEach((function(t){t.cache={},"function"==typeof t.on&&(t.on("update",(function(n,r){t.cache[n]=null,e.emit("update",n,r,t)})),t.on("load",(function(n,r){e.emit("load",n,r,t)})))}))},n.invalidateCache=function(){this.loaders.forEach((function(e){e.cache={}}))},n.addExtension=function(e,t){return t.__name=e,this.extensions[e]=t,this.extensionsList.push(t),this},n.removeExtension=function(e){var t=this.getExtension(e);t&&(this.extensionsList=je.without(this.extensionsList,t),delete this.extensions[e])},n.getExtension=function(e){return this.extensions[e]},n.hasExtension=function(e){return!!this.extensions[e]},n.addGlobal=function(e,t){return this.globals[e]=t,this},n.getGlobal=function(e){if(void 0===this.globals[e])throw new Error("global not found: "+e);return this.globals[e]},n.addFilter=function(e,t,n){var r=t;return n&&this.asyncFilters.push(e),this.filters[e]=r,this},n.getFilter=function(e){if(!this.filters[e])throw new Error("filter not found: "+e);return this.filters[e]},n.addTest=function(e,t){return this.tests[e]=t,this},n.getTest=function(e){if(!this.tests[e])throw new Error("test not found: "+e);return this.tests[e]},n.resolveTemplate=function(e,t,n){return!(!e.isRelative||!t)&&e.isRelative(n)&&e.resolve?e.resolve(t,n):n},n.getTemplate=function(e,t,n,r,i){var s,a=this,o=this,c=null;if(e&&e.raw&&(e=e.raw),je.isFunction(n)&&(i=n,n=null,t=t||!1),je.isFunction(t)&&(i=t,t=!1),e instanceof _i)c=e;else{if("string"!=typeof e)throw new Error("template names must be a string: "+e);for(var l=0;le.length-3)return!1;var n=e.charCodeAt(t+1);return(n>=Pi.LowerA&&n<=Pi.LowerZ||n>=Pi.UpperA&&n<=Pi.UpperZ||n===Pi.Exclamation)&&e.includes(">",t+2)}!function(e){e[e.LowerA=97]="LowerA",e[e.LowerZ=122]="LowerZ",e[e.UpperA=65]="UpperA",e[e.UpperZ=90]="UpperZ",e[e.Exclamation=33]="Exclamation"}(Pi||(Pi={}));var Hi=Object.prototype.hasOwnProperty,Gi=/\s+/,Yi={null:null,true:!0,false:!1},qi=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,Ki=/^{[^]*}$|^\[[^]*]$/;function ji(e,t,n){var r;if(e&&I(e))return null!==(r=e.attribs)&&void 0!==r||(e.attribs={}),t?Hi.call(e.attribs,t)?!n&&qi.test(t)?t:e.attribs[t]:"option"===e.name&&"value"===t?Li(e.children):"input"!==e.name||"radio"!==e.attribs.type&&"checkbox"!==e.attribs.type||"value"!==t?void 0:"on":e.attribs}function Vi(e,t,n){null===n?zi(e,t):e.attribs[t]="".concat(n)}function Wi(e,t,n){return t in e?e[t]:!n&&qi.test(t)?void 0!==ji(e,t,!1):ji(e,t,n)}function Qi(e,t,n,r){t in e?e[t]=n:Vi(e,t,!r&&qi.test(t)?n?"":null:"".concat(n))}function Xi(e,t,n){var r,i=e;null!==(r=i.data)&&void 0!==r||(i.data={}),"object"==typeof t?Object.assign(i.data,t):"string"==typeof t&&void 0!==n&&(i.data[t]=n)}function $i(e,t){var n,r,i,s;null==t?r=(n=Object.keys(e.attribs).filter((function(e){return e.startsWith("data-")}))).map((function(e){return e.slice("data-".length).replace(/[_.-](\w|$)/g,(function(e,t){return t.toUpperCase()}))})):(n=["data-"+(s=t,s.replace(/[A-Z]/g,"-$&").toLowerCase())],r=[t]);for(var a=0;a1?this:ji(this[0],e,this.options.xmlMode)},prop:function(e,t){var n,r=this;if("string"==typeof e&&void 0===t){var i=this[0];if(!i||!I(i))return;switch(e){case"style":var s=this.css(),a=Object.keys(s);return a.forEach((function(e,t){s[t]=e})),s.length=a.length,s;case"tagName":case"nodeName":return i.name.toUpperCase();case"href":case"src":var o=null===(n=i.attribs)||void 0===n?void 0:n[e];return"undefined"==typeof URL||("href"!==e||"a"!==i.tagName&&"link"!==i.name)&&("src"!==e||"img"!==i.tagName&&"iframe"!==i.tagName&&"audio"!==i.tagName&&"video"!==i.tagName&&"source"!==i.tagName)||void 0===o||!this.options.baseURI?o:new URL(o,this.options.baseURI).href;case"innerText":return J(i);case"textContent":return z(i);case"outerHTML":return this.clone().wrap("").parent().html();case"innerHTML":return this.html();default:return Wi(i,e,this.options.xmlMode)}}if("object"==typeof e||void 0!==t){if("function"==typeof t){if("object"==typeof e)throw new Error("Bad combination of arguments.");return Bi(this,(function(n,i){I(n)&&Qi(n,e,t.call(n,i,Wi(n,e,r.options.xmlMode)),r.options.xmlMode)}))}return Bi(this,(function(n){I(n)&&("object"==typeof e?Object.keys(e).forEach((function(t){var i=e[t];Qi(n,t,i,r.options.xmlMode)})):Qi(n,e,t,r.options.xmlMode))}))}},data:function(e,t){var n,r=this[0];if(r&&I(r)){var i=r;return null!==(n=i.data)&&void 0!==n||(i.data={}),e?"object"==typeof e||void 0!==t?(Bi(this,(function(n){I(n)&&("object"==typeof e?Xi(n,e):Xi(n,e,t))})),this):Hi.call(i.data,e)?i.data[e]:$i(i,e):$i(i)}},val:function(e){var t=0===arguments.length,n=this[0];if(!n||!I(n))return t?void 0:this;switch(n.name){case"textarea":return this.text(e);case"select":var r=this.find("option:selected");if(!t){if(null==this.attr("multiple")&&"object"==typeof e)return this;this.find("option").removeAttr("selected");for(var i="object"!=typeof e?[e]:e,s=0;s-1;){var i=r+e.length;if((0===r||Gi.test(n[r-1]))&&(i===n.length||Gi.test(n[i])))return!0}return!1}))},addClass:function e(t){if("function"==typeof t)return Bi(this,(function(n,r){if(I(n)){var i=n.attribs.class||"";e.call([n],t.call(n,r,i))}}));if(!t||"string"!=typeof t)return this;for(var n=t.split(Gi),r=this.length,i=0;i=0&&(t.splice(o,1),s=!0,a--)}s&&(e.attribs.class=t.join(" "))}}))},toggleClass:function e(t,n){if("function"==typeof t)return Bi(this,(function(r,i){I(r)&&e.call([r],t.call(r,i,r.attribs.class||"",n),n)}));if(!t||"string"!=typeof t)return this;for(var r=t.split(Gi),i=r.length,s="boolean"==typeof n?n?1:-1:0,a=this.length,o=0;o=0&&u<0?l.push(r[h]):s<=0&&u>=0&&l.splice(u,1)}c.attribs.class=l.join(" ")}}return this}});!function(e){e.Attribute="attribute",e.Pseudo="pseudo",e.PseudoElement="pseudo-element",e.Tag="tag",e.Universal="universal",e.Adjacent="adjacent",e.Child="child",e.Descendant="descendant",e.Parent="parent",e.Sibling="sibling",e.ColumnCombinator="column-combinator"}(Zi||(Zi={})),function(e){e.Any="any",e.Element="element",e.End="end",e.Equals="equals",e.Exists="exists",e.Hyphen="hyphen",e.Not="not",e.Start="start"}(es||(es={}));const ns=/^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/,rs=/\\([\da-f]{1,6}\s?|(\s)|.)/gi,is=new Map([[126,es.Element],[94,es.Start],[36,es.End],[42,es.Any],[33,es.Not],[124,es.Hyphen]]),ss=new Set(["has","not","matches","is","where","host","host-context"]);function as(e){switch(e.type){case Zi.Adjacent:case Zi.Child:case Zi.Descendant:case Zi.Parent:case Zi.Sibling:case Zi.ColumnCombinator:return!0;default:return!1}}const os=new Set(["contains","icontains"]);function cs(e,t,n){const r=parseInt(t,16)-65536;return r!=r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)}function ls(e){return e.replace(rs,cs)}function hs(e){return 39===e||34===e}function us(e){return 32===e||9===e||10===e||12===e||13===e}function ps(e){const t=[],n=fs(t,`${e}`,0);if(n0&&n0&&as(r[r.length-1]))throw new Error("Did not expect successive traversals.")}function l(e){r.length>0&&r[r.length-1].type===Zi.Descendant?r[r.length-1].type=e:(c(),r.push({type:e}))}function h(e,t){r.push({type:Zi.Attribute,name:e,action:t,value:i(1),namespace:null,ignoreCase:"quirks"})}function u(){if(r.length&&r[r.length-1].type===Zi.Descendant&&r.pop(),0===r.length)throw new Error("Empty sub-selector");e.push(r)}if(s(0),t.length===n)return n;e:for(;n=0&&r>=1)):e.type===Zi.Pseudo&&(e.data?"has"===e.name||"contains"===e.name?r=0:Array.isArray(e.data)?(r=Math.min(...e.data.map((e=>Math.min(...e.map(gs))))),r<0&&(r=0)):r=2:r=3),r}const Ns=/[-[\]{}()*+?.,\\^$|#\s]/g;function Cs(e){return e.replace(Ns,"\\$&")}const Is=new Set(["accept","accept-charset","align","alink","axis","bgcolor","charset","checked","clear","codetype","color","compact","declare","defer","dir","direction","disabled","enctype","face","frame","hreflang","http-equiv","lang","language","link","media","method","multiple","nohref","noresize","noshade","nowrap","readonly","rel","rev","rules","scope","scrolling","selected","shape","target","text","type","valign","valuetype","vlink"]);function Ss(e,t){return"boolean"==typeof e.ignoreCase?e.ignoreCase:"quirks"===e.ignoreCase?!!t.quirksMode:!t.xmlMode&&Is.has(e.name)}const bs={equals(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;return Ss(t,n)?(s=s.toLowerCase(),t=>{const n=r.getAttributeValue(t,i);return null!=n&&n.length===s.length&&n.toLowerCase()===s&&e(t)}):t=>r.getAttributeValue(t,i)===s&&e(t)},hyphen(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;const a=s.length;return Ss(t,n)?(s=s.toLowerCase(),function(t){const n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a).toLowerCase()===s&&e(t)}):function(t){const n=r.getAttributeValue(t,i);return null!=n&&(n.length===a||"-"===n.charAt(a))&&n.substr(0,a)===s&&e(t)}},element(e,t,n){const{adapter:r}=n,{name:i,value:s}=t;if(/\s/.test(s))return ds.falseFunc;const a=new RegExp(`(?:^|\\s)${Cs(s)}(?:$|\\s)`,Ss(t,n)?"i":"");return function(t){const n=r.getAttributeValue(t,i);return null!=n&&n.length>=s.length&&a.test(n)&&e(t)}},exists:(e,{name:t},{adapter:n})=>r=>n.hasAttrib(r,t)&&e(r),start(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;const a=s.length;return 0===a?ds.falseFunc:Ss(t,n)?(s=s.toLowerCase(),t=>{const n=r.getAttributeValue(t,i);return null!=n&&n.length>=a&&n.substr(0,a).toLowerCase()===s&&e(t)}):t=>{var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.startsWith(s))&&e(t)}},end(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;const a=-s.length;return 0===a?ds.falseFunc:Ss(t,n)?(s=s.toLowerCase(),t=>{var n;return(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.substr(a).toLowerCase())===s&&e(t)}):t=>{var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.endsWith(s))&&e(t)}},any(e,t,n){const{adapter:r}=n,{name:i,value:s}=t;if(""===s)return ds.falseFunc;if(Ss(t,n)){const t=new RegExp(Cs(s),"i");return function(n){const a=r.getAttributeValue(n,i);return null!=a&&a.length>=s.length&&t.test(a)&&e(n)}}return t=>{var n;return!!(null===(n=r.getAttributeValue(t,i))||void 0===n?void 0:n.includes(s))&&e(t)}},not(e,t,n){const{adapter:r}=n,{name:i}=t;let{value:s}=t;return""===s?t=>!!r.getAttributeValue(t,i)&&e(t):Ss(t,n)?(s=s.toLowerCase(),t=>{const n=r.getAttributeValue(t,i);return(null==n||n.length!==s.length||n.toLowerCase()!==s)&&e(t)}):t=>r.getAttributeValue(t,i)!==s&&e(t)}};var Os=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.parse=void 0;var n=new Set([9,10,12,13,32]),r="0".charCodeAt(0),i="9".charCodeAt(0);t.parse=function(e){if("even"===(e=e.trim().toLowerCase()))return[2,0];if("odd"===e)return[2,1];var t=0,s=0,a=c(),o=l();if(t=r&&e.charCodeAt(t)<=i;)s=10*s+(e.charCodeAt(t)-r),t++;return t===n?null:s}function h(){for(;t=n};var r=Math.abs(t),i=(n%r+r)%r;return t>1?function(e){return e>=n&&e%r===i}:function(e){return e<=n&&e%r===i}}}));qe(ys),ys.compile;var Ls=Os,ks=ys,vs=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.compile=t.parse=void 0,Object.defineProperty(t,"parse",{enumerable:!0,get:function(){return Ls.parse}}),Object.defineProperty(t,"compile",{enumerable:!0,get:function(){return ks.compile}}),t.default=function(e){return(0,ks.compile)((0,Ls.parse)(e))}})),Ds=qe(vs);function Rs(e,t){return n=>{const r=t.getParent(n);return null!=r&&t.isTag(r)&&e(n)}}vs.compile,vs.parse;const Ms={contains:(e,t,{adapter:n})=>function(r){return e(r)&&n.getText(r).includes(t)},icontains(e,t,{adapter:n}){const r=t.toLowerCase();return function(t){return e(t)&&n.getText(t).toLowerCase().includes(r)}},"nth-child"(e,t,{adapter:n,equals:r}){const i=Ds(t);return i===ds.falseFunc?ds.falseFunc:i===ds.trueFunc?Rs(e,n):function(t){const s=n.getSiblings(t);let a=0;for(let e=0;e=0&&!r(t,s[e]);e--)n.isTag(s[e])&&a++;return i(a)&&e(t)}},"nth-of-type"(e,t,{adapter:n,equals:r}){const i=Ds(t);return i===ds.falseFunc?ds.falseFunc:i===ds.trueFunc?Rs(e,n):function(t){const s=n.getSiblings(t);let a=0;for(let e=0;e=0;e--){const i=s[e];if(r(t,i))break;n.isTag(i)&&n.getName(i)===n.getName(t)&&a++}return i(a)&&e(t)}},root:(e,t,{adapter:n})=>t=>{const r=n.getParent(t);return(null==r||!n.isTag(r))&&e(t)},scope(e,t,n,r){const{equals:i}=n;return r&&0!==r.length?1===r.length?t=>i(r[0],t)&&e(t):t=>r.includes(t)&&e(t):Ms.root(e,t,n)},hover:Ps("isHovered"),visited:Ps("isVisited"),active:Ps("isActive")};function Ps(e){return function(t,n,{adapter:r}){const i=r[e];return"function"!=typeof i?ds.falseFunc:function(e){return i(e)&&t(e)}}}const xs={empty:(e,{adapter:t})=>!t.getChildren(e).some((e=>t.isTag(e)||""!==t.getText(e))),"first-child"(e,{adapter:t,equals:n}){if(t.prevElementSibling)return null==t.prevElementSibling(e);const r=t.getSiblings(e).find((e=>t.isTag(e)));return null!=r&&n(e,r)},"last-child"(e,{adapter:t,equals:n}){const r=t.getSiblings(e);for(let i=r.length-1;i>=0;i--){if(n(e,r[i]))return!0;if(t.isTag(r[i]))break}return!1},"first-of-type"(e,{adapter:t,equals:n}){const r=t.getSiblings(e),i=t.getName(e);for(let s=0;s=0;s--){const a=r[s];if(n(e,a))return!0;if(t.isTag(a)&&t.getName(a)===i)break}return!1},"only-of-type"(e,{adapter:t,equals:n}){const r=t.getName(e);return t.getSiblings(e).every((i=>n(e,i)||!t.isTag(i)||t.getName(i)!==r))},"only-child":(e,{adapter:t,equals:n})=>t.getSiblings(e).every((r=>n(e,r)||!t.isTag(r)))};function ws(e,t,n,r){if(null===n){if(e.length>r)throw new Error(`Pseudo-class :${t} requires an argument`)}else if(e.length===r)throw new Error(`Pseudo-class :${t} doesn't have any arguments`)}const Bs={"any-link":":is(a, area, link)[href]",link:":any-link:not(:visited)",disabled:":is(\n :is(button, input, select, textarea, optgroup, option)[disabled],\n optgroup[disabled] > option,\n fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *)\n )",enabled:":not(:disabled)",checked:":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",required:":is(input, select, textarea)[required]",optional:":is(input, select, textarea):not([required])",selected:"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",checkbox:"[type=checkbox]",file:"[type=file]",password:"[type=password]",radio:"[type=radio]",reset:"[type=reset]",image:"[type=image]",submit:"[type=submit]",parent:":not(:empty)",header:":is(h1, h2, h3, h4, h5, h6)",button:":is(button, input[type=button])",input:":is(input, textarea, select, button)",text:"input:is(:not([type!='']), [type=text])"},Fs={};function Us(e,t){const n=t.getSiblings(e);if(n.length<=1)return[];const r=n.indexOf(e);return r<0||r===n.length-1?[]:n.slice(r+1).filter(t.isTag)}function Hs(e){return{xmlMode:!!e.xmlMode,lowerCaseAttributeNames:!!e.lowerCaseAttributeNames,lowerCaseTags:!!e.lowerCaseTags,quirksMode:!!e.quirksMode,cacheResults:!!e.cacheResults,pseudos:e.pseudos,adapter:e.adapter,equals:e.equals}}const Gs=(e,t,n,r,i)=>{const s=i(t,Hs(n),r);return s===ds.trueFunc?e:s===ds.falseFunc?ds.falseFunc:t=>s(t)&&e(t)},Ys={is:Gs,matches:Gs,where:Gs,not(e,t,n,r,i){const s=i(t,Hs(n),r);return s===ds.falseFunc?e:s===ds.trueFunc?ds.falseFunc:t=>!s(t)&&e(t)},has(e,t,n,r,i){const{adapter:s}=n,a=Hs(n);a.relativeSelector=!0;const o=t.some((e=>e.some(Ts)))?[Fs]:void 0,c=i(t,a,o);if(c===ds.falseFunc)return ds.falseFunc;const l=function(e,t){return e===ds.falseFunc?ds.falseFunc:n=>t.isTag(n)&&e(n)}(c,s);if(o&&c!==ds.trueFunc){const{shouldTestNextSiblings:t=!1}=c;return n=>{if(!e(n))return!1;o[0]=n;const r=s.getChildren(n),i=t?[...r,...Us(n,s)]:r;return s.existsOne(l,i)}}return t=>e(t)&&s.existsOne(l,s.getChildren(t))}};function qs(e,t){const n=t.getParent(e);return n&&t.isTag(n)?n:null}function Ks(e,t,n,r,i){const{adapter:s,equals:a}=n;switch(t.type){case Zi.PseudoElement:throw new Error("Pseudo-elements are not supported by css-select");case Zi.ColumnCombinator:throw new Error("Column combinators are not yet supported by css-select");case Zi.Attribute:if(null!=t.namespace)throw new Error("Namespaced attributes are not yet supported by css-select");return n.xmlMode&&!n.lowerCaseAttributeNames||(t.name=t.name.toLowerCase()),bs[t.action](e,t,n);case Zi.Pseudo:return function(e,t,n,r,i){var s;const{name:a,data:o}=t;if(Array.isArray(o)){if(!(a in Ys))throw new Error(`Unknown pseudo-class :${a}(${o})`);return Ys[a](e,o,n,r,i)}const c=null===(s=n.pseudos)||void 0===s?void 0:s[a],l="string"==typeof c?c:Bs[a];if("string"==typeof l){if(null!=o)throw new Error(`Pseudo ${a} doesn't have any arguments`);const t=ps(l);return Ys.is(e,t,n,r,i)}if("function"==typeof c)return ws(c,a,o,1),t=>c(t,o)&&e(t);if(a in Ms)return Ms[a](e,o,n,r);if(a in xs){const t=xs[a];return ws(t,a,o,2),r=>t(r,n,o)&&e(r)}throw new Error(`Unknown pseudo-class :${a}`)}(e,t,n,r,i);case Zi.Tag:{if(null!=t.namespace)throw new Error("Namespaced tag names are not yet supported by css-select");let{name:r}=t;return n.xmlMode&&!n.lowerCaseTags||(r=r.toLowerCase()),function(t){return s.getName(t)===r&&e(t)}}case Zi.Descendant:{if(!1===n.cacheResults||"undefined"==typeof WeakSet)return function(t){let n=t;for(;n=qs(n,s);)if(e(n))return!0;return!1};const t=new WeakSet;return function(n){let r=n;for(;r=qs(r,s);)if(!t.has(r)){if(s.isTag(r)&&e(r))return!0;t.add(r)}return!1}}case"_flexibleDescendant":return function(t){let n=t;do{if(e(n))return!0}while(n=qs(n,s));return!1};case Zi.Parent:return function(t){return s.getChildren(t).some((t=>s.isTag(t)&&e(t)))};case Zi.Child:return function(t){const n=s.getParent(t);return null!=n&&s.isTag(n)&&e(n)};case Zi.Sibling:return function(t){const n=s.getSiblings(t);for(let r=0;re.some(js))))}const Vs={type:Zi.Descendant},Ws={type:"_flexibleDescendant"},Qs={type:Zi.Pseudo,name:"scope",data:null};function Xs(e,t,n){var r;e.forEach(As),n=null!==(r=t.context)&&void 0!==r?r:n;const i=Array.isArray(n),s=n&&(Array.isArray(n)?n:[n]);if(!1!==t.relativeSelector)!function(e,{adapter:t},n){const r=!!(null==n?void 0:n.every((e=>{const n=t.isTag(e)&&t.getParent(e);return e===Fs||n&&t.isTag(n)})));for(const t of e){if(t.length>0&&Ts(t[0])&&t[0].type!==Zi.Descendant);else{if(!r||t.some(js))continue;t.unshift(Vs)}t.unshift(Qs)}}(e,t,s);else if(e.some((e=>e.length>0&&Ts(e[0]))))throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");let a=!1;const o=e.map((e=>{if(e.length>=2){const[t,n]=e;t.type!==Zi.Pseudo||"scope"!==t.name||(i&&n.type===Zi.Descendant?e[1]=Ws:n.type!==Zi.Adjacent&&n.type!==Zi.Sibling||(a=!0))}return function(e,t,n){var r;return e.reduce(((e,r)=>e===ds.falseFunc?ds.falseFunc:Ks(e,r,t,n,Xs)),null!==(r=t.rootFunc)&&void 0!==r?r:ds.trueFunc)}(e,t,s)})).reduce($s,ds.falseFunc);return o.shouldTestNextSiblings=a,o}function $s(e,t){return t===ds.falseFunc||e===ds.trueFunc?e:e===ds.falseFunc||t===ds.trueFunc?t:function(n){return e(n)||t(n)}}const zs=(e,t)=>e===t,Js={adapter:Se,equals:zs};const Zs=(ea=Xs,function(e,t,n){const r=function(e){var t,n,r,i;const s=null!=e?e:Js;return null!==(t=s.adapter)&&void 0!==t||(s.adapter=Se),null!==(n=s.equals)&&void 0!==n||(s.equals=null!==(i=null===(r=s.adapter)||void 0===r?void 0:r.equals)&&void 0!==i?i:zs),s}(t);return ea(e,r,n)});var ea;function ta(e,t,n=!1){return n&&(e=function(e,t){const n=Array.isArray(e)?e.slice(0):[e],r=n.length;for(let e=0;ee.some(ra))))}function ia(e){const t=[],n=[];for(const r of e)r.some(ra)?t.push(r):n.push(r);return[n,t]}const sa={type:Zi.Universal,namespace:null},aa={type:Zi.Pseudo,name:"scope",data:null};function oa(e,t,n={}){return ca([e],t,n)}function ca(e,t,n={}){if("function"==typeof t)return e.some(t);const[r,i]=ia(ps(t));return r.length>0&&e.some(Zs(r,n))||i.some((t=>ua(t,e,n).length>0))}function la(e,t,n={}){return ha(ps(e),t,n)}function ha(e,t,n){if(0===t.length)return[];const[r,i]=ia(e);let s;if(r.length){const e=Ea(t,r,n);if(0===i.length)return e;e.length&&(s=new Set(e))}for(let e=0;eI(e)&&!s.has(e))):t;if(0===a.length)break;const o=ua(r,t,n);if(o.length)if(s)o.forEach((e=>s.add(e)));else{if(e===i.length-1)return o;s=new Set(o)}}return void 0!==s?s.size===t.length?t:t.filter((e=>s.has(e))):[]}function ua(e,t,n){var r;if(e.some(as)){const i=null!==(r=n.root)&&void 0!==r?r:function(e){for(;e.parent;)e=e.parent;return e}(t[0]),s={...n,context:t,relativeSelector:!1};return e.push(aa),pa(i,e,s,!0,t.length)}return pa(t,e,n,!1,t.length)}function pa(e,t,n,r,i){const s=t.findIndex(ra),a=t.slice(0,s),o=t[s],c=t.length-1===s?i:1/0,l=function(e,t,n){const r=null!=t?parseInt(t,10):NaN;switch(e){case"first":return 1;case"nth":case"eq":return isFinite(r)?r>=0?r+1:1/0:0;case"lt":return isFinite(r)?r>=0?Math.min(r,n):1/0:0;case"gt":return isFinite(r)?1/0:0;case"odd":return 2*n;case"even":return 2*n-1;case"last":case"not":return 1/0}}(o.name,o.data,c);if(0===l)return[];const h=(0!==a.length||Array.isArray(e)?0===a.length?(Array.isArray(e)?e:[e]).filter(I):r||a.some(as)?fa(e,[a],n,l):Ea(e,[a],n):Z(e).filter(I)).slice(0,l);let u=function(e,t,n,r){const i="string"==typeof n?parseInt(n,10):NaN;switch(e){case"first":case"lt":return t;case"last":return t.length>0?[t[t.length-1]]:t;case"nth":case"eq":return isFinite(i)&&Math.abs(i)t%2==0));case"odd":return t.filter(((e,t)=>t%2==1));case"not":{const e=new Set(ha(n,t,r));return t.filter((t=>!e.has(t)))}}}(o.name,h,o.data,n);if(0===u.length||t.length===s+1)return u;const p=t.slice(s+1),f=p.some(as);if(f){if(as(p[0])){const{type:e}=p[0];e!==Zi.Sibling&&e!==Zi.Adjacent||(u=ta(u,Se,!0)),p.unshift(sa)}n={...n,relativeSelector:!1,rootFunc:e=>u.includes(e)}}else n.rootFunc&&n.rootFunc!==Es&&(n={...n,rootFunc:Es});return p.some(ra)?pa(u,p,n,!1,i):f?fa(u,[p],n,i):Ea(u,[p],n)}function fa(e,t,n,r){return da(e,Zs(t,n,e),r)}function da(e,t,n=1/0){return ae((e=>I(e)&&t(e)),ta(e,Se,t.shouldTestNextSiblings),!0,n)}function Ea(e,t,n){const r=(Array.isArray(e)?e:[e]).filter(I);if(0===r.length)return r;const i=Zs(t,n);return i===Es?r:r.filter(i)}var ma=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;i1&&s.length>1?n.reduce((function(e,t){return t(e)}),s):s)}}}var Aa=_a((function(e,t){for(var n,r=[],i=0;ipa(t,e,n,!0,r)));return i.length&&a.push(fa(t,i,n,r)),0===a.length?[]:1===a.length?a[0]:Ee(a.reduce(((e,t)=>[...e,...t])))}(e,i,s))},parent:Ia,parents:Sa,parentsUntil:ba,closest:function(e){var t,n=[];if(!e)return this._make(n);var r={xmlMode:this.options.xmlMode,root:null===(t=this._root)||void 0===t?void 0:t[0]},i="string"==typeof e?function(t){return oa(t,e,r)}:Pa(e);return Bi(this,(function(e){for(;e&&I(e);){if(i(e,0)){n.includes(e)||n.push(e);break}e=e.parent}})),this._make(n)},next:Oa,nextAll:ya,nextUntil:La,prev:ka,prevAll:va,prevUntil:Da,siblings:Ra,children:Ma,contents:function(){var e=this.toArray().reduce((function(e,t){return k(t)?e.concat(t.children):e}),[]);return this._make(e)},each:function(e){for(var t=0,n=this.length;t0})},first:function(){return this.length>1?this._make(this[0]):this},last:function(){return this.length>0?this._make(this[this.length-1]):this},eq:function(e){var t;return 0===(e=+e)&&this.length<=1?this:(e<0&&(e=this.length+e),this._make(null!==(t=this[e])&&void 0!==t?t:[]))},get:function(e){return null==e?this.toArray():this[e<0?this.length+e:e]},toArray:function(){return Array.prototype.slice.call(this)},index:function(e){var t,n;return null==e?(t=this.parent().children(),n=this[0]):"string"==typeof e?(t=this._make(e),n=this[0]):(t=this,n=wi(e)?e[0]:e),Array.prototype.indexOf.call(t,n)},slice:function(e,t){return this._make(Array.prototype.slice.call(this,e,t))},end:function(){var e;return null!==(e=this.prevObject)&&void 0!==e?e:this._make([])},add:function(e,t){var n=this._make(e,t),r=Ee(ma(ma([],this.get(),!0),n.get(),!0));return this._make(r)},addBack:function(e){return this.prevObject?this.add(e?this.prevObject.filter(e):this.prevObject):this}});function Ba(e,t){var n=Array.isArray(e)?e:[e];t?t.children=n:t=null;for(var r=0;r=e.length?null:e[t+n],h=0;h-1&&(p.children.splice(f,1),i===p&&t>f&&o[0]--)}u.parent=i,u.prev&&(u.prev.next=null!==(s=u.next)&&void 0!==s?s:null),u.next&&(u.next.prev=null!==(a=u.prev)&&void 0!==a?a:null),u.prev=0===h?c:r[h-1],u.next=h===r.length-1?l:r[h+1]}return c&&(c.next=r[0]),l&&(l.prev=r[r.length-1]),e.splice.apply(e,o)}var Ga=Ua((function(e,t,n){Ha(t,t.length,0,e,n)})),Ya=Ua((function(e,t,n){Ha(t,0,0,e,n)}));function qa(e){return function(t){for(var n=this.length-1,r=this.parents().last(),i=0;i0&&void 0!==t&&(n[t]+=";".concat(o))}else n[t=s.slice(0,a).trim()]=s.slice(a+1).trim()}return n}(e.attribs.style);if("string"==typeof t)return n[t];if(Array.isArray(t)){var r={};return t.forEach((function(e){null!=n[e]&&(r[e]=n[e])})),r}return n}}var Xa=Object.freeze({__proto__:null,css:function(e,t){return null!=e&&null!=t||"object"==typeof e&&!Array.isArray(e)?Bi(this,(function(n,r){I(n)&&Wa(n,e,t,r)})):0!==this.length?Qa(this[0],e):void 0}}),$a=/%20/g,za=/\r?\n/g;var Ja=Object.freeze({__proto__:null,serialize:function(){var e=this.serializeArray().map((function(e){return"".concat(encodeURIComponent(e.name),"=").concat(encodeURIComponent(e.value))}));return e.join("&").replace($a,"+")},serializeArray:function(){var e=this;return this.map((function(t,n){var r=e._make(n);return I(n)&&"form"===n.name?r.find("input,select,textarea,keygen").toArray():r.filter("input,select,textarea,keygen").toArray()})).filter('[name!=""]:enabled:not(:submit, :button, :image, :reset, :file):matches([checked], :not(:checkbox, :radio))').map((function(t,n){var r,i=e._make(n),s=i.attr("name"),a=null!==(r=i.val())&&void 0!==r?r:"";return Array.isArray(a)?a.map((function(e){return{name:s,value:e.replace(za,"\r\n")}})):{name:s,value:a.replace(za,"\r\n")}})).toArray()}}),Za=function(e,t,n){if(this.length=0,this.options=n,this._root=t,e){for(var r=0;r=55296&&e<=57343}function po(e){return 32!==e&&10!==e&&13!==e&&9!==e&&12!==e&&e>=1&&e<=31||e>=127&&e<=159}function fo(e){return e>=64976&&e<=65007||ro.has(e)}var Eo;!function(e){e.controlCharacterInInputStream="control-character-in-input-stream",e.noncharacterInInputStream="noncharacter-in-input-stream",e.surrogateInInputStream="surrogate-in-input-stream",e.nonVoidHtmlElementStartTagWithTrailingSolidus="non-void-html-element-start-tag-with-trailing-solidus",e.endTagWithAttributes="end-tag-with-attributes",e.endTagWithTrailingSolidus="end-tag-with-trailing-solidus",e.unexpectedSolidusInTag="unexpected-solidus-in-tag",e.unexpectedNullCharacter="unexpected-null-character",e.unexpectedQuestionMarkInsteadOfTagName="unexpected-question-mark-instead-of-tag-name",e.invalidFirstCharacterOfTagName="invalid-first-character-of-tag-name",e.unexpectedEqualsSignBeforeAttributeName="unexpected-equals-sign-before-attribute-name",e.missingEndTagName="missing-end-tag-name",e.unexpectedCharacterInAttributeName="unexpected-character-in-attribute-name",e.unknownNamedCharacterReference="unknown-named-character-reference",e.missingSemicolonAfterCharacterReference="missing-semicolon-after-character-reference",e.unexpectedCharacterAfterDoctypeSystemIdentifier="unexpected-character-after-doctype-system-identifier",e.unexpectedCharacterInUnquotedAttributeValue="unexpected-character-in-unquoted-attribute-value",e.eofBeforeTagName="eof-before-tag-name",e.eofInTag="eof-in-tag",e.missingAttributeValue="missing-attribute-value",e.missingWhitespaceBetweenAttributes="missing-whitespace-between-attributes",e.missingWhitespaceAfterDoctypePublicKeyword="missing-whitespace-after-doctype-public-keyword",e.missingWhitespaceBetweenDoctypePublicAndSystemIdentifiers="missing-whitespace-between-doctype-public-and-system-identifiers",e.missingWhitespaceAfterDoctypeSystemKeyword="missing-whitespace-after-doctype-system-keyword",e.missingQuoteBeforeDoctypePublicIdentifier="missing-quote-before-doctype-public-identifier",e.missingQuoteBeforeDoctypeSystemIdentifier="missing-quote-before-doctype-system-identifier",e.missingDoctypePublicIdentifier="missing-doctype-public-identifier",e.missingDoctypeSystemIdentifier="missing-doctype-system-identifier",e.abruptDoctypePublicIdentifier="abrupt-doctype-public-identifier",e.abruptDoctypeSystemIdentifier="abrupt-doctype-system-identifier",e.cdataInHtmlContent="cdata-in-html-content",e.incorrectlyOpenedComment="incorrectly-opened-comment",e.eofInScriptHtmlCommentLikeText="eof-in-script-html-comment-like-text",e.eofInDoctype="eof-in-doctype",e.nestedComment="nested-comment",e.abruptClosingOfEmptyComment="abrupt-closing-of-empty-comment",e.eofInComment="eof-in-comment",e.incorrectlyClosedComment="incorrectly-closed-comment",e.eofInCdata="eof-in-cdata",e.absenceOfDigitsInNumericCharacterReference="absence-of-digits-in-numeric-character-reference",e.nullCharacterReference="null-character-reference",e.surrogateCharacterReference="surrogate-character-reference",e.characterReferenceOutsideUnicodeRange="character-reference-outside-unicode-range",e.controlCharacterReference="control-character-reference",e.noncharacterCharacterReference="noncharacter-character-reference",e.missingWhitespaceBeforeDoctypeName="missing-whitespace-before-doctype-name",e.missingDoctypeName="missing-doctype-name",e.invalidCharacterSequenceAfterDoctypeName="invalid-character-sequence-after-doctype-name",e.duplicateAttribute="duplicate-attribute",e.nonConformingDoctype="non-conforming-doctype",e.missingDoctype="missing-doctype",e.misplacedDoctype="misplaced-doctype",e.endTagWithoutMatchingOpenElement="end-tag-without-matching-open-element",e.closingOfElementWithOpenChildElements="closing-of-element-with-open-child-elements",e.disallowedContentInNoscriptInHead="disallowed-content-in-noscript-in-head",e.openElementsLeftAfterEof="open-elements-left-after-eof",e.abandonedHeadElementChild="abandoned-head-element-child",e.misplacedStartTagForHeadElement="misplaced-start-tag-for-head-element",e.nestedNoscriptInHead="nested-noscript-in-head",e.eofInElementThatCanContainOnlyText="eof-in-element-that-can-contain-only-text"}(Eo=Eo||(Eo={}));class mo{constructor(e){this.handler=e,this.html="",this.pos=-1,this.lastGapPos=-2,this.gapStack=[],this.skipNextNewLine=!1,this.lastChunkWritten=!1,this.endOfChunkHit=!1,this.bufferWaterline=65536,this.isEol=!1,this.lineStartPos=0,this.droppedBufferSize=0,this.line=1,this.lastErrOffset=-1}get col(){return this.pos-this.lineStartPos+Number(this.lastGapPos!==this.pos)}get offset(){return this.droppedBufferSize+this.pos}getError(e){const{line:t,col:n,offset:r}=this;return{code:e,startLine:t,endLine:t,startCol:n,endCol:n,startOffset:r,endOffset:r}}_err(e){this.handler.onParseError&&this.lastErrOffset!==this.offset&&(this.lastErrOffset=this.offset,this.handler.onParseError(this.getError(e)))}_addGap(){this.gapStack.push(this.lastGapPos),this.lastGapPos=this.pos}_processSurrogate(e){if(this.pos!==this.html.length-1){const t=this.html.charCodeAt(this.pos+1);if(function(e){return e>=56320&&e<=57343}(t))return this.pos++,this._addGap(),1024*(e-55296)+9216+t}else if(!this.lastChunkWritten)return this.endOfChunkHit=!0,io.EOF;return this._err(Eo.surrogateInInputStream),e}willDropParsedChunk(){return this.pos>this.bufferWaterline}dropParsedChunk(){this.willDropParsedChunk()&&(this.html=this.html.substring(this.pos),this.lineStartPos-=this.pos,this.droppedBufferSize+=this.pos,this.pos=0,this.lastGapPos=-2,this.gapStack.length=0)}write(e,t){this.html.length>0?this.html+=e:this.html=e,this.endOfChunkHit=!1,this.lastChunkWritten=t}insertHtmlAtCurrentPos(e){this.html=this.html.substring(0,this.pos+1)+e+this.html.substring(this.pos+1),this.endOfChunkHit=!1}startsWith(e,t){if(this.pos+e.length>this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,!1;if(t)return this.html.startsWith(e,this.pos);for(let t=0;t=this.html.length?(this.endOfChunkHit=!this.lastChunkWritten,io.EOF):this.html.charCodeAt(t)}advance(){if(this.pos++,this.isEol&&(this.isEol=!1,this.line++,this.lineStartPos=this.pos),this.pos>=this.html.length)return this.endOfChunkHit=!this.lastChunkWritten,io.EOF;let e=this.html.charCodeAt(this.pos);if(e===io.CARRIAGE_RETURN)return this.isEol=!0,this.skipNextNewLine=!0,io.LINE_FEED;if(e===io.LINE_FEED&&(this.isEol=!0,this.skipNextNewLine))return this.line--,this.skipNextNewLine=!1,this._addGap(),this.advance();this.skipNextNewLine=!1,uo(e)&&(e=this._processSurrogate(e));return null===this.handler.onParseError||e>31&&e<127||e===io.LINE_FEED||e===io.CARRIAGE_RETURN||e>159&&e<64976||this._checkForProblematicCharacters(e),e}_checkForProblematicCharacters(e){po(e)?this._err(Eo.controlCharacterInInputStream):fo(e)&&this._err(Eo.noncharacterInInputStream)}retreat(e){for(this.pos-=e;this.pos=0;n--)if(e.attrs[n].name===t)return e.attrs[n].value;return null}!function(e){e[e.CHARACTER=0]="CHARACTER",e[e.NULL_CHARACTER=1]="NULL_CHARACTER",e[e.WHITESPACE_CHARACTER=2]="WHITESPACE_CHARACTER",e[e.START_TAG=3]="START_TAG",e[e.END_TAG=4]="END_TAG",e[e.COMMENT=5]="COMMENT",e[e.DOCTYPE=6]="DOCTYPE",e[e.EOF=7]="EOF",e[e.HIBERNATION=8]="HIBERNATION"}(To=To||(To={}));var Ao=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array('ᵁ<Õıʊҝջאٵ۞ޢߖࠏ੊ઑඡ๭༉༦჊ረዡᐕᒝᓃᓟᔥ\0\0\0\0\0\0ᕫᛍᦍᰒᷝ὾⁠↰⊍⏀⏻⑂⠤⤒ⴈ⹈⿎〖㊺㘹㞬㣾㨨㩱㫠㬮ࠀEMabcfglmnoprstu\\bfms„‹•˜¦³¹ÈÏlig耻Æ䃆P耻&䀦cute耻Á䃁reve;䄂Āiyx}rc耻Â䃂;䐐r;쀀𝔄rave耻À䃀pha;䎑acr;䄀d;橓Āgp¡on;䄄f;쀀𝔸plyFunction;恡ing耻Å䃅Ācs¾Ãr;쀀𝒜ign;扔ilde耻Ã䃃ml耻Ä䃄ЀaceforsuåûþėĜĢħĪĀcrêòkslash;或Ŷöø;櫧ed;挆y;䐑ƀcrtąċĔause;戵noullis;愬a;䎒r;쀀𝔅pf;쀀𝔹eve;䋘còēmpeq;扎܀HOacdefhilorsuōőŖƀƞƢƵƷƺǜȕɳɸɾcy;䐧PY耻©䂩ƀcpyŝŢźute;䄆Ā;iŧŨ拒talDifferentialD;慅leys;愭ȀaeioƉƎƔƘron;䄌dil耻Ç䃇rc;䄈nint;戰ot;䄊ĀdnƧƭilla;䂸terDot;䂷òſi;䎧rcleȀDMPTLJNjǑǖot;抙inus;抖lus;投imes;抗oĀcsǢǸkwiseContourIntegral;戲eCurlyĀDQȃȏoubleQuote;思uote;怙ȀlnpuȞȨɇɕonĀ;eȥȦ户;橴ƀgitȯȶȺruent;扡nt;戯ourIntegral;戮ĀfrɌɎ;愂oduct;成nterClockwiseContourIntegral;戳oss;樯cr;쀀𝒞pĀ;Cʄʅ拓ap;才րDJSZacefiosʠʬʰʴʸˋ˗ˡ˦̳ҍĀ;oŹʥtrahd;椑cy;䐂cy;䐅cy;䐏ƀgrsʿ˄ˇger;怡r;憡hv;櫤Āayː˕ron;䄎;䐔lĀ;t˝˞戇a;䎔r;쀀𝔇Āaf˫̧Ācm˰̢riticalȀADGT̖̜̀̆cute;䂴oŴ̋̍;䋙bleAcute;䋝rave;䁠ilde;䋜ond;拄ferentialD;慆Ѱ̽\0\0\0͔͂\0Ѕf;쀀𝔻ƀ;DE͈͉͍䂨ot;惜qual;扐blèCDLRUVͣͲ΂ϏϢϸontourIntegraìȹoɴ͹\0\0ͻ»͉nArrow;懓Āeo·ΤftƀARTΐΖΡrrow;懐ightArrow;懔eåˊngĀLRΫτeftĀARγιrrow;柸ightArrow;柺ightArrow;柹ightĀATϘϞrrow;懒ee;抨pɁϩ\0\0ϯrrow;懑ownArrow;懕erticalBar;戥ǹABLRTaВЪаўѿͼrrowƀ;BUНОТ憓ar;椓pArrow;懵reve;䌑eft˒к\0ц\0ѐightVector;楐eeVector;楞ectorĀ;Bљњ憽ar;楖ightǔѧ\0ѱeeVector;楟ectorĀ;BѺѻ懁ar;楗eeĀ;A҆҇护rrow;憧ĀctҒҗr;쀀𝒟rok;䄐ࠀNTacdfglmopqstuxҽӀӄӋӞӢӧӮӵԡԯԶՒ՝ՠեG;䅊H耻Ð䃐cute耻É䃉ƀaiyӒӗӜron;䄚rc耻Ê䃊;䐭ot;䄖r;쀀𝔈rave耻È䃈ement;戈ĀapӺӾcr;䄒tyɓԆ\0\0ԒmallSquare;旻erySmallSquare;斫ĀgpԦԪon;䄘f;쀀𝔼silon;䎕uĀaiԼՉlĀ;TՂՃ橵ilde;扂librium;懌Āci՗՚r;愰m;橳a;䎗ml耻Ë䃋Āipժկsts;戃onentialE;慇ʀcfiosօֈ֍ֲ׌y;䐤r;쀀𝔉lledɓ֗\0\0֣mallSquare;旼erySmallSquare;斪Ͱֺ\0ֿ\0\0ׄf;쀀𝔽All;戀riertrf;愱cò׋؀JTabcdfgorstר׬ׯ׺؀ؒؖ؛؝أ٬ٲcy;䐃耻>䀾mmaĀ;d׷׸䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇܎ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ޷޼߂ߐĀiyޱ޵rc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄΀HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶߻dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣ঳সে্਷ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗ࡜ࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४Ānrࢃ࢏gleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpw৔ਖਛgȀLRlr৞৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼੝੠੷੼અઋ઎p;椅y;䐜Ādl੥੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑ඗ඞcy;䐊cute;䅃ƀaey઴હાron;䅇dil;䅅;䐝ƀgswે૰଎ativeƀMTV૓૟૨ediumSpace;怋hiĀcn૦૘ë૙eryThiî૙tedĀGL૸ଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷ଺reak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪୼஡௫ఄ౞಄ದ೘ൡඅ櫬Āou୛୤ngruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊ஛ement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater΀;EFGLSTஶஷ஽௉௓௘௥扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲௽ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ೒拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨೹setĀ;E೰ೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂ෉෕ෛ෠෧෼ขภยา฿ไlig;䅒cute耻Ó䃓Āiy෎ීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲ෶cr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬื฼de耻Õ䃕es;樷ml耻Ö䃖erĀBP๋๠Āar๐๓r;怾acĀek๚๜;揞et;掴arenthesis;揜Ҁacfhilors๿ງຊຏຒດຝະ໼rtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ໠໤檻cedesȀ;EST່້໏໚扺qual;檯lantEqual;扼ilde;找me;怳Ādp໩໮uct;戏ortionĀ;aȥ໹l;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬؀BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁࿫࿳ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL࿜࿝࿡憒ar;懥eftArrow;懄eiling;按oǵ࿹\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»࿝pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄ቉ቕ቞ቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHc቎ቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗Āeiቻ኉Dzኀ\0ኇefore;戴a;䎘Ācn኎ኘkSpace;쀀  Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0᏿ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0጖y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻፿on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtè૙a;䎖r;愨pf;愤cr;쀀𝒵௡ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚΀;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒΀;Eaeiop዁ᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;e዁ᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;e዁ᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰ᜼ᝃᝈ᝸᝽០៦ᠹᡐᜍ᤽᥈ᥰot;櫭Ācrᛶ᜞kȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e᜚᜛戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;t፜᜷brk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓ᝛ᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯ᝱ᝳ;䎲;愶een;扬r;쀀𝔟g΀costuvwឍឝឳេ៕៛៞ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀako៭ᠦᠵĀcn៲ᠣkƀlst៺֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘᠝斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈؀DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬ᣿ᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教΀;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ᣷᣹᣻᣽;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ᤟;敛;敘;攘;攔΀;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģ᥂bar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;e᜚᜜lƀ;bhᥨᥩᥫ䁜;槅sub;柈Ŭᥴ᥾lĀ;e᥹᥺怢t»᥺pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭍᭒\0᯽\0ᰌƀcpr᦭ᦲ᧝ute;䄇̀;abcdsᦿᧀᧄ᧊᧕᧙戩nd;橄rcup;橉Āau᧏᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r΀;Ecefms᩟᩠ᩢᩫ᪤᪪᪮旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖᪚᪟»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇᫔᫺\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ᫙\0\0᫢aĀ;t᫞᫟䀬;䁀ƀ;fl᫨᫩᫫戁îᅠeĀmx᫱᫶ent»᫩eóɍǧ᫾\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯΀delprvw᭠᭬᭷ᮂᮬᯔ᯹arrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;p᭿ᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰸᰻᰿ᱝᱩᱵᲊᲞᲬᲷ᳻᳿ᴍᵻᶑᶫᶻ᷆᷍rò΁ar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂͸᳖᳜᳠mƀ;oș᳊᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄ὎὚ĀDoḆᴴoôᲉĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»Ṻƀaeiἒ἖Ἒls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙ῜ῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao‌⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧‪‬\0‮耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₟₥₰₴⃰⃵⃺⃿℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕ₝ute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽⃉ƀ;qsؾٌ⃄lanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqr׮ⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0↎proø₞r;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢␧␭␱␵␻ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀஀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼rò৆òΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonó྘quigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d྘➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ᠛旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐௏쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop඄⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roø඄urĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓΀;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨í஘istĀ;s஠டr;쀀𝔫ȀEest௅⩦⩹⩼ƀ;qs஼⩭௡ƀ;qs஼௅⩴lanô௢ií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚΀AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs఻⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs఻⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast୻⭕⭚⭟lleì୻l;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖ΀chimpqu⮽⯍⯙⬄୸⯤⯯Ȁ;cerല⯆ഷ⯉uå൅;쀀𝓃ortɭ⬅\0\0⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭å೸åഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñ೗Ā;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰⳴ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\0\0\0\0\0\0\0\0\0\0\0\0\0ⴭ\0ⴸⵈⵠⵥ⵲ⶄᬇ\0\0ⶍⶫ\0ⷈⷎ\0ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;c᪞ⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācr⵩⵭ir;榿;쀀𝔬ͯ⵹\0\0⵼\0ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕ⶘ⶥⶨrò᪀Āir⶝ⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀𝕠ƀaelⷔ⷗ǒr;榷rp;榹΀;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ⹞\0⹽\0⺀⺝\0⺢⺹\0\0⻋ຜ\0⼓\0\0⼫⾼\0⿈rȀ;astЃ⹧⹲຅脀¶;l⹭⹮䂶leìЃɩ⹸\0\0⹻m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀𝔭ƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳⻴ᤈ⻹⻽⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀𝕡nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp໬⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t໻⾴ï໻rel;抰Āci⿀⿅r;쀀𝓅;䏈ncsp;怈̀fiopsu⿚⋢⿟⿥⿫⿱r;쀀𝔮pf;쀀𝕢rime;恗cr;쀀𝓆ƀaeo⿸〉〓tĀei⿾々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔઀ABHabcdefhilmnoprstux぀けさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤΀cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstw࿜ガクシスゼゾダッデナp;極Ā;f࿠ゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes㄂㄄;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ì࿲âヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀𝔯ĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘㇤㇮rrowĀ;t࿜ㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowó࿪arpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓rò࿪aòՑ;怏oustĀ;a㈞㈟掱che»㈟mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀𝕣us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀𝓇Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\0㍺㎤\0\0㏬㏰\0㐨㑈㑚㒭㒱㓊㓱\0㘖\0\0㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\0㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦΀Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼਴t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀𝔰Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\0\0㎜iäᑤaraì⹯耻­䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;q኱ኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀𝕤aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀𝓈tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫ਩war;椪lig耻ß䃟௡㙑㙝㙠ዎ㙳㙹\0㙾㛂\0\0\0\0\0㛛㜃\0㜉㝬\0\0\0㞇ɲ㙖\0\0㙛get;挖;䏄rë๟ƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀𝔱Ȁeiko㚆㚝㚵㚼Dz㚋\0㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproø዁im»ኬsðኞĀas㚺㚮ð዁rn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀𝕥rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈ΀adempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀𝓉;䑆cy;䑛rok;䅧Āio㞋㞎xô᝷headĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\0㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀𝔲rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\0\0㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀𝕦̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\0\0㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀𝓊ƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜΀eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀𝔳tré㦮suĀbp㧯㧱»ജ»൙pf;쀀𝕧roð໻tré㦴Ācu㨆㨋r;쀀𝓋Ābp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚΀cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀𝔴pf;쀀𝕨Ā;eᑹ㩦atèᑹcr;쀀𝓌ૣណ㪇\0㪋\0㪐㪛\0\0㪝㪨㪫㪯\0\0㫃㫎\0㫘ៜ៟tré៑r;쀀𝔵ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀𝕩imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀𝓍Āpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀𝔶cy;䑗pf;쀀𝕪cr;쀀𝓎Ācm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀𝔷cy;䐶grarr;懝pf;쀀𝕫cr;쀀𝓏Ājn㮅㮇;怍j;怌'.split("").map((function(e){return e.charCodeAt(0)})))}));qe(Ao);var go=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.default=new Uint16Array("Ȁaglq\tɭ\0\0p;䀦os;䀧t;䀾t;䀼uot;䀢".split("").map((function(e){return e.charCodeAt(0)})))}));qe(go);var No=Ke((function(e,t){var n;Object.defineProperty(t,"__esModule",{value:!0}),t.replaceCodePoint=t.fromCodePoint=void 0;var r=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);function i(e){var t;return e>=55296&&e<=57343||e>1114111?65533:null!==(t=r.get(e))&&void 0!==t?t:e}t.fromCodePoint=null!==(n=String.fromCodePoint)&&void 0!==n?n:function(e){var t="";return e>65535&&(e-=65536,t+=String.fromCharCode(e>>>10&1023|55296),e=56320|1023&e),t+=String.fromCharCode(e)},t.replaceCodePoint=i,t.default=function(e){return(0,t.fromCodePoint)(i(e))}}));qe(No),No.replaceCodePoint,No.fromCodePoint;var Co=Ao,Io=go,So=No,bo=Ke((function(e,t){var n=Ge&&Ge.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.decodeXML=t.decodeHTMLStrict=t.decodeHTML=t.determineBranch=t.BinTrieFlags=t.fromCodePoint=t.replaceCodePoint=t.decodeCodePoint=t.xmlDecodeTree=t.htmlDecodeTree=void 0;var r=n(Co);t.htmlDecodeTree=r.default;var i=n(Io);t.xmlDecodeTree=i.default;var s=n(So);t.decodeCodePoint=s.default;var a,o,c=So;function l(e){return function(t,n){for(var r="",i=0,c=0;(c=t.indexOf("&",c))>=0;)if(r+=t.slice(i,c),i=c,c+=1,t.charCodeAt(c)!==a.NUM){for(var l=0,u=1,p=0,f=e[p];c>14)-1))break;p+=E}}if(0!==l)r+=1===(E=(e[l]&o.VALUE_LENGTH)>>14)?String.fromCharCode(e[l]&~o.VALUE_LENGTH):2===E?String.fromCharCode(e[l+1]):String.fromCharCode(e[l+1],e[l+2]),i=c-u+1}else{var m=c+1,T=10,_=t.charCodeAt(m);(_|a.To_LOWER_BIT)===a.LOWER_X&&(T=16,c+=1,m+=1);do{_=t.charCodeAt(++c)}while(_>=a.ZERO&&_<=a.NINE||16===T&&(_|a.To_LOWER_BIT)>=a.LOWER_A&&(_|a.To_LOWER_BIT)<=a.LOWER_F);if(m!==c){var A=t.substring(m,c),g=parseInt(A,T);if(t.charCodeAt(c)===a.SEMI)c+=1;else if(n)continue;r+=(0,s.default)(g),i=c}}return r+t.slice(i)}}function h(e,t,n,r){var i=(t&o.BRANCH_LENGTH)>>7,s=t&o.JUMP_TABLE;if(0===i)return 0!==s&&r===s?n:-1;if(s){var a=r-s;return a<0||a>=i?-1:e[n+a]-1}for(var c=n,l=c+i-1;c<=l;){var h=c+l>>>1,u=e[h];if(ur))return e[h+i];l=h-1}}return-1}Object.defineProperty(t,"replaceCodePoint",{enumerable:!0,get:function(){return c.replaceCodePoint}}),Object.defineProperty(t,"fromCodePoint",{enumerable:!0,get:function(){return c.fromCodePoint}}),function(e){e[e.NUM=35]="NUM",e[e.SEMI=59]="SEMI",e[e.ZERO=48]="ZERO",e[e.NINE=57]="NINE",e[e.LOWER_A=97]="LOWER_A",e[e.LOWER_F=102]="LOWER_F",e[e.LOWER_X=120]="LOWER_X",e[e.To_LOWER_BIT=32]="To_LOWER_BIT"}(a||(a={})),function(e){e[e.VALUE_LENGTH=49152]="VALUE_LENGTH",e[e.BRANCH_LENGTH=16256]="BRANCH_LENGTH",e[e.JUMP_TABLE=127]="JUMP_TABLE"}(o=t.BinTrieFlags||(t.BinTrieFlags={})),t.determineBranch=h;var u=l(r.default),p=l(i.default);t.decodeHTML=function(e){return u(e,!1)},t.decodeHTMLStrict=function(e){return u(e,!0)},t.decodeXML=function(e){return p(e,!0)}}));qe(bo),bo.decodeXML,bo.decodeHTMLStrict,bo.decodeHTML;var Oo=bo.determineBranch,yo=bo.BinTrieFlags,Lo=bo.fromCodePoint,ko=bo.replaceCodePoint;bo.decodeCodePoint;var vo,Do,Ro,Mo,Po,xo=bo.xmlDecodeTree,wo=bo.htmlDecodeTree;!function(e){e.HTML="http://www.w3.org/1999/xhtml",e.MATHML="http://www.w3.org/1998/Math/MathML",e.SVG="http://www.w3.org/2000/svg",e.XLINK="http://www.w3.org/1999/xlink",e.XML="http://www.w3.org/XML/1998/namespace",e.XMLNS="http://www.w3.org/2000/xmlns/"}(vo=vo||(vo={})),function(e){e.TYPE="type",e.ACTION="action",e.ENCODING="encoding",e.PROMPT="prompt",e.NAME="name",e.COLOR="color",e.FACE="face",e.SIZE="size"}(Do=Do||(Do={})),function(e){e.NO_QUIRKS="no-quirks",e.QUIRKS="quirks",e.LIMITED_QUIRKS="limited-quirks"}(Ro=Ro||(Ro={})),function(e){e.A="a",e.ADDRESS="address",e.ANNOTATION_XML="annotation-xml",e.APPLET="applet",e.AREA="area",e.ARTICLE="article",e.ASIDE="aside",e.B="b",e.BASE="base",e.BASEFONT="basefont",e.BGSOUND="bgsound",e.BIG="big",e.BLOCKQUOTE="blockquote",e.BODY="body",e.BR="br",e.BUTTON="button",e.CAPTION="caption",e.CENTER="center",e.CODE="code",e.COL="col",e.COLGROUP="colgroup",e.DD="dd",e.DESC="desc",e.DETAILS="details",e.DIALOG="dialog",e.DIR="dir",e.DIV="div",e.DL="dl",e.DT="dt",e.EM="em",e.EMBED="embed",e.FIELDSET="fieldset",e.FIGCAPTION="figcaption",e.FIGURE="figure",e.FONT="font",e.FOOTER="footer",e.FOREIGN_OBJECT="foreignObject",e.FORM="form",e.FRAME="frame",e.FRAMESET="frameset",e.H1="h1",e.H2="h2",e.H3="h3",e.H4="h4",e.H5="h5",e.H6="h6",e.HEAD="head",e.HEADER="header",e.HGROUP="hgroup",e.HR="hr",e.HTML="html",e.I="i",e.IMG="img",e.IMAGE="image",e.INPUT="input",e.IFRAME="iframe",e.KEYGEN="keygen",e.LABEL="label",e.LI="li",e.LINK="link",e.LISTING="listing",e.MAIN="main",e.MALIGNMARK="malignmark",e.MARQUEE="marquee",e.MATH="math",e.MENU="menu",e.META="meta",e.MGLYPH="mglyph",e.MI="mi",e.MO="mo",e.MN="mn",e.MS="ms",e.MTEXT="mtext",e.NAV="nav",e.NOBR="nobr",e.NOFRAMES="noframes",e.NOEMBED="noembed",e.NOSCRIPT="noscript",e.OBJECT="object",e.OL="ol",e.OPTGROUP="optgroup",e.OPTION="option",e.P="p",e.PARAM="param",e.PLAINTEXT="plaintext",e.PRE="pre",e.RB="rb",e.RP="rp",e.RT="rt",e.RTC="rtc",e.RUBY="ruby",e.S="s",e.SCRIPT="script",e.SECTION="section",e.SELECT="select",e.SOURCE="source",e.SMALL="small",e.SPAN="span",e.STRIKE="strike",e.STRONG="strong",e.STYLE="style",e.SUB="sub",e.SUMMARY="summary",e.SUP="sup",e.TABLE="table",e.TBODY="tbody",e.TEMPLATE="template",e.TEXTAREA="textarea",e.TFOOT="tfoot",e.TD="td",e.TH="th",e.THEAD="thead",e.TITLE="title",e.TR="tr",e.TRACK="track",e.TT="tt",e.U="u",e.UL="ul",e.SVG="svg",e.VAR="var",e.WBR="wbr",e.XMP="xmp"}(Mo=Mo||(Mo={})),function(e){e[e.UNKNOWN=0]="UNKNOWN",e[e.A=1]="A",e[e.ADDRESS=2]="ADDRESS",e[e.ANNOTATION_XML=3]="ANNOTATION_XML",e[e.APPLET=4]="APPLET",e[e.AREA=5]="AREA",e[e.ARTICLE=6]="ARTICLE",e[e.ASIDE=7]="ASIDE",e[e.B=8]="B",e[e.BASE=9]="BASE",e[e.BASEFONT=10]="BASEFONT",e[e.BGSOUND=11]="BGSOUND",e[e.BIG=12]="BIG",e[e.BLOCKQUOTE=13]="BLOCKQUOTE",e[e.BODY=14]="BODY",e[e.BR=15]="BR",e[e.BUTTON=16]="BUTTON",e[e.CAPTION=17]="CAPTION",e[e.CENTER=18]="CENTER",e[e.CODE=19]="CODE",e[e.COL=20]="COL",e[e.COLGROUP=21]="COLGROUP",e[e.DD=22]="DD",e[e.DESC=23]="DESC",e[e.DETAILS=24]="DETAILS",e[e.DIALOG=25]="DIALOG",e[e.DIR=26]="DIR",e[e.DIV=27]="DIV",e[e.DL=28]="DL",e[e.DT=29]="DT",e[e.EM=30]="EM",e[e.EMBED=31]="EMBED",e[e.FIELDSET=32]="FIELDSET",e[e.FIGCAPTION=33]="FIGCAPTION",e[e.FIGURE=34]="FIGURE",e[e.FONT=35]="FONT",e[e.FOOTER=36]="FOOTER",e[e.FOREIGN_OBJECT=37]="FOREIGN_OBJECT",e[e.FORM=38]="FORM",e[e.FRAME=39]="FRAME",e[e.FRAMESET=40]="FRAMESET",e[e.H1=41]="H1",e[e.H2=42]="H2",e[e.H3=43]="H3",e[e.H4=44]="H4",e[e.H5=45]="H5",e[e.H6=46]="H6",e[e.HEAD=47]="HEAD",e[e.HEADER=48]="HEADER",e[e.HGROUP=49]="HGROUP",e[e.HR=50]="HR",e[e.HTML=51]="HTML",e[e.I=52]="I",e[e.IMG=53]="IMG",e[e.IMAGE=54]="IMAGE",e[e.INPUT=55]="INPUT",e[e.IFRAME=56]="IFRAME",e[e.KEYGEN=57]="KEYGEN",e[e.LABEL=58]="LABEL",e[e.LI=59]="LI",e[e.LINK=60]="LINK",e[e.LISTING=61]="LISTING",e[e.MAIN=62]="MAIN",e[e.MALIGNMARK=63]="MALIGNMARK",e[e.MARQUEE=64]="MARQUEE",e[e.MATH=65]="MATH",e[e.MENU=66]="MENU",e[e.META=67]="META",e[e.MGLYPH=68]="MGLYPH",e[e.MI=69]="MI",e[e.MO=70]="MO",e[e.MN=71]="MN",e[e.MS=72]="MS",e[e.MTEXT=73]="MTEXT",e[e.NAV=74]="NAV",e[e.NOBR=75]="NOBR",e[e.NOFRAMES=76]="NOFRAMES",e[e.NOEMBED=77]="NOEMBED",e[e.NOSCRIPT=78]="NOSCRIPT",e[e.OBJECT=79]="OBJECT",e[e.OL=80]="OL",e[e.OPTGROUP=81]="OPTGROUP",e[e.OPTION=82]="OPTION",e[e.P=83]="P",e[e.PARAM=84]="PARAM",e[e.PLAINTEXT=85]="PLAINTEXT",e[e.PRE=86]="PRE",e[e.RB=87]="RB",e[e.RP=88]="RP",e[e.RT=89]="RT",e[e.RTC=90]="RTC",e[e.RUBY=91]="RUBY",e[e.S=92]="S",e[e.SCRIPT=93]="SCRIPT",e[e.SECTION=94]="SECTION",e[e.SELECT=95]="SELECT",e[e.SOURCE=96]="SOURCE",e[e.SMALL=97]="SMALL",e[e.SPAN=98]="SPAN",e[e.STRIKE=99]="STRIKE",e[e.STRONG=100]="STRONG",e[e.STYLE=101]="STYLE",e[e.SUB=102]="SUB",e[e.SUMMARY=103]="SUMMARY",e[e.SUP=104]="SUP",e[e.TABLE=105]="TABLE",e[e.TBODY=106]="TBODY",e[e.TEMPLATE=107]="TEMPLATE",e[e.TEXTAREA=108]="TEXTAREA",e[e.TFOOT=109]="TFOOT",e[e.TD=110]="TD",e[e.TH=111]="TH",e[e.THEAD=112]="THEAD",e[e.TITLE=113]="TITLE",e[e.TR=114]="TR",e[e.TRACK=115]="TRACK",e[e.TT=116]="TT",e[e.U=117]="U",e[e.UL=118]="UL",e[e.SVG=119]="SVG",e[e.VAR=120]="VAR",e[e.WBR=121]="WBR",e[e.XMP=122]="XMP"}(Po=Po||(Po={}));const Bo=new Map([[Mo.A,Po.A],[Mo.ADDRESS,Po.ADDRESS],[Mo.ANNOTATION_XML,Po.ANNOTATION_XML],[Mo.APPLET,Po.APPLET],[Mo.AREA,Po.AREA],[Mo.ARTICLE,Po.ARTICLE],[Mo.ASIDE,Po.ASIDE],[Mo.B,Po.B],[Mo.BASE,Po.BASE],[Mo.BASEFONT,Po.BASEFONT],[Mo.BGSOUND,Po.BGSOUND],[Mo.BIG,Po.BIG],[Mo.BLOCKQUOTE,Po.BLOCKQUOTE],[Mo.BODY,Po.BODY],[Mo.BR,Po.BR],[Mo.BUTTON,Po.BUTTON],[Mo.CAPTION,Po.CAPTION],[Mo.CENTER,Po.CENTER],[Mo.CODE,Po.CODE],[Mo.COL,Po.COL],[Mo.COLGROUP,Po.COLGROUP],[Mo.DD,Po.DD],[Mo.DESC,Po.DESC],[Mo.DETAILS,Po.DETAILS],[Mo.DIALOG,Po.DIALOG],[Mo.DIR,Po.DIR],[Mo.DIV,Po.DIV],[Mo.DL,Po.DL],[Mo.DT,Po.DT],[Mo.EM,Po.EM],[Mo.EMBED,Po.EMBED],[Mo.FIELDSET,Po.FIELDSET],[Mo.FIGCAPTION,Po.FIGCAPTION],[Mo.FIGURE,Po.FIGURE],[Mo.FONT,Po.FONT],[Mo.FOOTER,Po.FOOTER],[Mo.FOREIGN_OBJECT,Po.FOREIGN_OBJECT],[Mo.FORM,Po.FORM],[Mo.FRAME,Po.FRAME],[Mo.FRAMESET,Po.FRAMESET],[Mo.H1,Po.H1],[Mo.H2,Po.H2],[Mo.H3,Po.H3],[Mo.H4,Po.H4],[Mo.H5,Po.H5],[Mo.H6,Po.H6],[Mo.HEAD,Po.HEAD],[Mo.HEADER,Po.HEADER],[Mo.HGROUP,Po.HGROUP],[Mo.HR,Po.HR],[Mo.HTML,Po.HTML],[Mo.I,Po.I],[Mo.IMG,Po.IMG],[Mo.IMAGE,Po.IMAGE],[Mo.INPUT,Po.INPUT],[Mo.IFRAME,Po.IFRAME],[Mo.KEYGEN,Po.KEYGEN],[Mo.LABEL,Po.LABEL],[Mo.LI,Po.LI],[Mo.LINK,Po.LINK],[Mo.LISTING,Po.LISTING],[Mo.MAIN,Po.MAIN],[Mo.MALIGNMARK,Po.MALIGNMARK],[Mo.MARQUEE,Po.MARQUEE],[Mo.MATH,Po.MATH],[Mo.MENU,Po.MENU],[Mo.META,Po.META],[Mo.MGLYPH,Po.MGLYPH],[Mo.MI,Po.MI],[Mo.MO,Po.MO],[Mo.MN,Po.MN],[Mo.MS,Po.MS],[Mo.MTEXT,Po.MTEXT],[Mo.NAV,Po.NAV],[Mo.NOBR,Po.NOBR],[Mo.NOFRAMES,Po.NOFRAMES],[Mo.NOEMBED,Po.NOEMBED],[Mo.NOSCRIPT,Po.NOSCRIPT],[Mo.OBJECT,Po.OBJECT],[Mo.OL,Po.OL],[Mo.OPTGROUP,Po.OPTGROUP],[Mo.OPTION,Po.OPTION],[Mo.P,Po.P],[Mo.PARAM,Po.PARAM],[Mo.PLAINTEXT,Po.PLAINTEXT],[Mo.PRE,Po.PRE],[Mo.RB,Po.RB],[Mo.RP,Po.RP],[Mo.RT,Po.RT],[Mo.RTC,Po.RTC],[Mo.RUBY,Po.RUBY],[Mo.S,Po.S],[Mo.SCRIPT,Po.SCRIPT],[Mo.SECTION,Po.SECTION],[Mo.SELECT,Po.SELECT],[Mo.SOURCE,Po.SOURCE],[Mo.SMALL,Po.SMALL],[Mo.SPAN,Po.SPAN],[Mo.STRIKE,Po.STRIKE],[Mo.STRONG,Po.STRONG],[Mo.STYLE,Po.STYLE],[Mo.SUB,Po.SUB],[Mo.SUMMARY,Po.SUMMARY],[Mo.SUP,Po.SUP],[Mo.TABLE,Po.TABLE],[Mo.TBODY,Po.TBODY],[Mo.TEMPLATE,Po.TEMPLATE],[Mo.TEXTAREA,Po.TEXTAREA],[Mo.TFOOT,Po.TFOOT],[Mo.TD,Po.TD],[Mo.TH,Po.TH],[Mo.THEAD,Po.THEAD],[Mo.TITLE,Po.TITLE],[Mo.TR,Po.TR],[Mo.TRACK,Po.TRACK],[Mo.TT,Po.TT],[Mo.U,Po.U],[Mo.UL,Po.UL],[Mo.SVG,Po.SVG],[Mo.VAR,Po.VAR],[Mo.WBR,Po.WBR],[Mo.XMP,Po.XMP]]);function Fo(e){var t;return null!==(t=Bo.get(e))&&void 0!==t?t:Po.UNKNOWN}const Uo=Po,Ho={[vo.HTML]:new Set([Uo.ADDRESS,Uo.APPLET,Uo.AREA,Uo.ARTICLE,Uo.ASIDE,Uo.BASE,Uo.BASEFONT,Uo.BGSOUND,Uo.BLOCKQUOTE,Uo.BODY,Uo.BR,Uo.BUTTON,Uo.CAPTION,Uo.CENTER,Uo.COL,Uo.COLGROUP,Uo.DD,Uo.DETAILS,Uo.DIR,Uo.DIV,Uo.DL,Uo.DT,Uo.EMBED,Uo.FIELDSET,Uo.FIGCAPTION,Uo.FIGURE,Uo.FOOTER,Uo.FORM,Uo.FRAME,Uo.FRAMESET,Uo.H1,Uo.H2,Uo.H3,Uo.H4,Uo.H5,Uo.H6,Uo.HEAD,Uo.HEADER,Uo.HGROUP,Uo.HR,Uo.HTML,Uo.IFRAME,Uo.IMG,Uo.INPUT,Uo.LI,Uo.LINK,Uo.LISTING,Uo.MAIN,Uo.MARQUEE,Uo.MENU,Uo.META,Uo.NAV,Uo.NOEMBED,Uo.NOFRAMES,Uo.NOSCRIPT,Uo.OBJECT,Uo.OL,Uo.P,Uo.PARAM,Uo.PLAINTEXT,Uo.PRE,Uo.SCRIPT,Uo.SECTION,Uo.SELECT,Uo.SOURCE,Uo.STYLE,Uo.SUMMARY,Uo.TABLE,Uo.TBODY,Uo.TD,Uo.TEMPLATE,Uo.TEXTAREA,Uo.TFOOT,Uo.TH,Uo.THEAD,Uo.TITLE,Uo.TR,Uo.TRACK,Uo.UL,Uo.WBR,Uo.XMP]),[vo.MATHML]:new Set([Uo.MI,Uo.MO,Uo.MN,Uo.MS,Uo.MTEXT,Uo.ANNOTATION_XML]),[vo.SVG]:new Set([Uo.TITLE,Uo.FOREIGN_OBJECT,Uo.DESC]),[vo.XLINK]:new Set,[vo.XML]:new Set,[vo.XMLNS]:new Set};function Go(e){return e===Uo.H1||e===Uo.H2||e===Uo.H3||e===Uo.H4||e===Uo.H5||e===Uo.H6}const Yo=new Set([Mo.STYLE,Mo.SCRIPT,Mo.XMP,Mo.IFRAME,Mo.NOEMBED,Mo.NOFRAMES,Mo.PLAINTEXT]);const qo=new Map([[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]);var Ko;!function(e){e[e.DATA=0]="DATA",e[e.RCDATA=1]="RCDATA",e[e.RAWTEXT=2]="RAWTEXT",e[e.SCRIPT_DATA=3]="SCRIPT_DATA",e[e.PLAINTEXT=4]="PLAINTEXT",e[e.TAG_OPEN=5]="TAG_OPEN",e[e.END_TAG_OPEN=6]="END_TAG_OPEN",e[e.TAG_NAME=7]="TAG_NAME",e[e.RCDATA_LESS_THAN_SIGN=8]="RCDATA_LESS_THAN_SIGN",e[e.RCDATA_END_TAG_OPEN=9]="RCDATA_END_TAG_OPEN",e[e.RCDATA_END_TAG_NAME=10]="RCDATA_END_TAG_NAME",e[e.RAWTEXT_LESS_THAN_SIGN=11]="RAWTEXT_LESS_THAN_SIGN",e[e.RAWTEXT_END_TAG_OPEN=12]="RAWTEXT_END_TAG_OPEN",e[e.RAWTEXT_END_TAG_NAME=13]="RAWTEXT_END_TAG_NAME",e[e.SCRIPT_DATA_LESS_THAN_SIGN=14]="SCRIPT_DATA_LESS_THAN_SIGN",e[e.SCRIPT_DATA_END_TAG_OPEN=15]="SCRIPT_DATA_END_TAG_OPEN",e[e.SCRIPT_DATA_END_TAG_NAME=16]="SCRIPT_DATA_END_TAG_NAME",e[e.SCRIPT_DATA_ESCAPE_START=17]="SCRIPT_DATA_ESCAPE_START",e[e.SCRIPT_DATA_ESCAPE_START_DASH=18]="SCRIPT_DATA_ESCAPE_START_DASH",e[e.SCRIPT_DATA_ESCAPED=19]="SCRIPT_DATA_ESCAPED",e[e.SCRIPT_DATA_ESCAPED_DASH=20]="SCRIPT_DATA_ESCAPED_DASH",e[e.SCRIPT_DATA_ESCAPED_DASH_DASH=21]="SCRIPT_DATA_ESCAPED_DASH_DASH",e[e.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN=22]="SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN",e[e.SCRIPT_DATA_ESCAPED_END_TAG_OPEN=23]="SCRIPT_DATA_ESCAPED_END_TAG_OPEN",e[e.SCRIPT_DATA_ESCAPED_END_TAG_NAME=24]="SCRIPT_DATA_ESCAPED_END_TAG_NAME",e[e.SCRIPT_DATA_DOUBLE_ESCAPE_START=25]="SCRIPT_DATA_DOUBLE_ESCAPE_START",e[e.SCRIPT_DATA_DOUBLE_ESCAPED=26]="SCRIPT_DATA_DOUBLE_ESCAPED",e[e.SCRIPT_DATA_DOUBLE_ESCAPED_DASH=27]="SCRIPT_DATA_DOUBLE_ESCAPED_DASH",e[e.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH=28]="SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH",e[e.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN=29]="SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN",e[e.SCRIPT_DATA_DOUBLE_ESCAPE_END=30]="SCRIPT_DATA_DOUBLE_ESCAPE_END",e[e.BEFORE_ATTRIBUTE_NAME=31]="BEFORE_ATTRIBUTE_NAME",e[e.ATTRIBUTE_NAME=32]="ATTRIBUTE_NAME",e[e.AFTER_ATTRIBUTE_NAME=33]="AFTER_ATTRIBUTE_NAME",e[e.BEFORE_ATTRIBUTE_VALUE=34]="BEFORE_ATTRIBUTE_VALUE",e[e.ATTRIBUTE_VALUE_DOUBLE_QUOTED=35]="ATTRIBUTE_VALUE_DOUBLE_QUOTED",e[e.ATTRIBUTE_VALUE_SINGLE_QUOTED=36]="ATTRIBUTE_VALUE_SINGLE_QUOTED",e[e.ATTRIBUTE_VALUE_UNQUOTED=37]="ATTRIBUTE_VALUE_UNQUOTED",e[e.AFTER_ATTRIBUTE_VALUE_QUOTED=38]="AFTER_ATTRIBUTE_VALUE_QUOTED",e[e.SELF_CLOSING_START_TAG=39]="SELF_CLOSING_START_TAG",e[e.BOGUS_COMMENT=40]="BOGUS_COMMENT",e[e.MARKUP_DECLARATION_OPEN=41]="MARKUP_DECLARATION_OPEN",e[e.COMMENT_START=42]="COMMENT_START",e[e.COMMENT_START_DASH=43]="COMMENT_START_DASH",e[e.COMMENT=44]="COMMENT",e[e.COMMENT_LESS_THAN_SIGN=45]="COMMENT_LESS_THAN_SIGN",e[e.COMMENT_LESS_THAN_SIGN_BANG=46]="COMMENT_LESS_THAN_SIGN_BANG",e[e.COMMENT_LESS_THAN_SIGN_BANG_DASH=47]="COMMENT_LESS_THAN_SIGN_BANG_DASH",e[e.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH=48]="COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH",e[e.COMMENT_END_DASH=49]="COMMENT_END_DASH",e[e.COMMENT_END=50]="COMMENT_END",e[e.COMMENT_END_BANG=51]="COMMENT_END_BANG",e[e.DOCTYPE=52]="DOCTYPE",e[e.BEFORE_DOCTYPE_NAME=53]="BEFORE_DOCTYPE_NAME",e[e.DOCTYPE_NAME=54]="DOCTYPE_NAME",e[e.AFTER_DOCTYPE_NAME=55]="AFTER_DOCTYPE_NAME",e[e.AFTER_DOCTYPE_PUBLIC_KEYWORD=56]="AFTER_DOCTYPE_PUBLIC_KEYWORD",e[e.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER=57]="BEFORE_DOCTYPE_PUBLIC_IDENTIFIER",e[e.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED=58]="DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED",e[e.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED=59]="DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED",e[e.AFTER_DOCTYPE_PUBLIC_IDENTIFIER=60]="AFTER_DOCTYPE_PUBLIC_IDENTIFIER",e[e.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS=61]="BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS",e[e.AFTER_DOCTYPE_SYSTEM_KEYWORD=62]="AFTER_DOCTYPE_SYSTEM_KEYWORD",e[e.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER=63]="BEFORE_DOCTYPE_SYSTEM_IDENTIFIER",e[e.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED=64]="DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED",e[e.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED=65]="DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED",e[e.AFTER_DOCTYPE_SYSTEM_IDENTIFIER=66]="AFTER_DOCTYPE_SYSTEM_IDENTIFIER",e[e.BOGUS_DOCTYPE=67]="BOGUS_DOCTYPE",e[e.CDATA_SECTION=68]="CDATA_SECTION",e[e.CDATA_SECTION_BRACKET=69]="CDATA_SECTION_BRACKET",e[e.CDATA_SECTION_END=70]="CDATA_SECTION_END",e[e.CHARACTER_REFERENCE=71]="CHARACTER_REFERENCE",e[e.NAMED_CHARACTER_REFERENCE=72]="NAMED_CHARACTER_REFERENCE",e[e.AMBIGUOUS_AMPERSAND=73]="AMBIGUOUS_AMPERSAND",e[e.NUMERIC_CHARACTER_REFERENCE=74]="NUMERIC_CHARACTER_REFERENCE",e[e.HEXADEMICAL_CHARACTER_REFERENCE_START=75]="HEXADEMICAL_CHARACTER_REFERENCE_START",e[e.HEXADEMICAL_CHARACTER_REFERENCE=76]="HEXADEMICAL_CHARACTER_REFERENCE",e[e.DECIMAL_CHARACTER_REFERENCE=77]="DECIMAL_CHARACTER_REFERENCE",e[e.NUMERIC_CHARACTER_REFERENCE_END=78]="NUMERIC_CHARACTER_REFERENCE_END"}(Ko||(Ko={}));const jo={DATA:Ko.DATA,RCDATA:Ko.RCDATA,RAWTEXT:Ko.RAWTEXT,SCRIPT_DATA:Ko.SCRIPT_DATA,PLAINTEXT:Ko.PLAINTEXT,CDATA_SECTION:Ko.CDATA_SECTION};function Vo(e){return e>=io.DIGIT_0&&e<=io.DIGIT_9}function Wo(e){return e>=io.LATIN_CAPITAL_A&&e<=io.LATIN_CAPITAL_Z}function Qo(e){return function(e){return e>=io.LATIN_SMALL_A&&e<=io.LATIN_SMALL_Z}(e)||Wo(e)}function Xo(e){return Qo(e)||Vo(e)}function $o(e){return e>=io.LATIN_CAPITAL_A&&e<=io.LATIN_CAPITAL_F}function zo(e){return e>=io.LATIN_SMALL_A&&e<=io.LATIN_SMALL_F}function Jo(e){return e+32}function Zo(e){return e===io.SPACE||e===io.LINE_FEED||e===io.TABULATION||e===io.FORM_FEED}function ec(e){return Zo(e)||e===io.SOLIDUS||e===io.GREATER_THAN_SIGN}class tc{constructor(e,t){this.options=e,this.handler=t,this.paused=!1,this.inLoop=!1,this.inForeignNode=!1,this.lastStartTagName="",this.active=!1,this.state=Ko.DATA,this.returnState=Ko.DATA,this.charRefCode=-1,this.consumedAfterSnapshot=-1,this.currentCharacterToken=null,this.currentToken=null,this.currentAttr={name:"",value:""},this.preprocessor=new mo(t),this.currentLocation=this.getCurrentLocation(-1)}_err(e){var t,n;null===(n=(t=this.handler).onParseError)||void 0===n||n.call(t,this.preprocessor.getError(e))}getCurrentLocation(e){return this.options.sourceCodeLocationInfo?{startLine:this.preprocessor.line,startCol:this.preprocessor.col-e,startOffset:this.preprocessor.offset-e,endLine:-1,endCol:-1,endOffset:-1}:null}_runParsingLoop(){if(!this.inLoop){for(this.inLoop=!0;this.active&&!this.paused;){this.consumedAfterSnapshot=0;const e=this._consume();this._ensureHibernation()||this._callState(e)}this.inLoop=!1}}pause(){this.paused=!0}resume(e){if(!this.paused)throw new Error("Parser was already resumed");this.paused=!1,this.inLoop||(this._runParsingLoop(),this.paused||null==e||e())}write(e,t,n){this.active=!0,this.preprocessor.write(e,t),this._runParsingLoop(),this.paused||null==n||n()}insertHtmlAtCurrentPos(e){this.active=!0,this.preprocessor.insertHtmlAtCurrentPos(e),this._runParsingLoop()}_ensureHibernation(){return!!this.preprocessor.endOfChunkHit&&(this._unconsume(this.consumedAfterSnapshot),this.active=!1,!0)}_consume(){return this.consumedAfterSnapshot++,this.preprocessor.advance()}_unconsume(e){this.consumedAfterSnapshot-=e,this.preprocessor.retreat(e)}_reconsumeInState(e,t){this.state=e,this._callState(t)}_advanceBy(e){this.consumedAfterSnapshot+=e;for(let t=0;t0&&this._err(Eo.endTagWithAttributes),e.selfClosing&&this._err(Eo.endTagWithTrailingSolidus),this.handler.onEndTag(e)),this.preprocessor.dropParsedChunk()}emitCurrentComment(e){this.prepareToken(e),this.handler.onComment(e),this.preprocessor.dropParsedChunk()}emitCurrentDoctype(e){this.prepareToken(e),this.handler.onDoctype(e),this.preprocessor.dropParsedChunk()}_emitCurrentCharacterToken(e){if(this.currentCharacterToken){switch(e&&this.currentCharacterToken.location&&(this.currentCharacterToken.location.endLine=e.startLine,this.currentCharacterToken.location.endCol=e.startCol,this.currentCharacterToken.location.endOffset=e.startOffset),this.currentCharacterToken.type){case To.CHARACTER:this.handler.onCharacter(this.currentCharacterToken);break;case To.NULL_CHARACTER:this.handler.onNullCharacter(this.currentCharacterToken);break;case To.WHITESPACE_CHARACTER:this.handler.onWhitespaceCharacter(this.currentCharacterToken)}this.currentCharacterToken=null}}_emitEOFToken(){const e=this.getCurrentLocation(0);e&&(e.endLine=e.startLine,e.endCol=e.startCol,e.endOffset=e.startOffset),this._emitCurrentCharacterToken(e),this.handler.onEof({type:To.EOF,location:e}),this.active=!1}_appendCharToCurrentCharacterToken(e,t){if(this.currentCharacterToken){if(this.currentCharacterToken.type===e)return void(this.currentCharacterToken.chars+=t);this.currentLocation=this.getCurrentLocation(0),this._emitCurrentCharacterToken(this.currentLocation),this.preprocessor.dropParsedChunk()}this._createCharacterToken(e,t)}_emitCodePoint(e){const t=Zo(e)?To.WHITESPACE_CHARACTER:e===io.NULL?To.NULL_CHARACTER:To.CHARACTER;this._appendCharToCurrentCharacterToken(t,String.fromCodePoint(e))}_emitChars(e){this._appendCharToCurrentCharacterToken(To.CHARACTER,e)}_matchNamedCharacterReference(e){let t=null,n=0,r=!1;for(let s=0,a=wo[0];s>=0&&(s=Oo(wo,a,s+1,e),!(s<0));e=this._consume()){n+=1,a=wo[s];const o=a&yo.VALUE_LENGTH;if(o){const a=(o>>14)-1;if(e!==io.SEMICOLON&&this._isCharacterReferenceInAttribute()&&((i=this.preprocessor.peek(1))===io.EQUALS_SIGN||Xo(i))?(t=[io.AMPERSAND],s+=a):(t=0===a?[wo[s]&~yo.VALUE_LENGTH]:1===a?[wo[++s]]:[wo[++s],wo[++s]],n=0,r=e!==io.SEMICOLON),0===a){this._consume();break}}}var i;return this._unconsume(n),r&&!this.preprocessor.endOfChunkHit&&this._err(Eo.missingSemicolonAfterCharacterReference),this._unconsume(1),t}_isCharacterReferenceInAttribute(){return this.returnState===Ko.ATTRIBUTE_VALUE_DOUBLE_QUOTED||this.returnState===Ko.ATTRIBUTE_VALUE_SINGLE_QUOTED||this.returnState===Ko.ATTRIBUTE_VALUE_UNQUOTED}_flushCodePointConsumedAsCharacterReference(e){this._isCharacterReferenceInAttribute()?this.currentAttr.value+=String.fromCodePoint(e):this._emitCodePoint(e)}_callState(e){switch(this.state){case Ko.DATA:this._stateData(e);break;case Ko.RCDATA:this._stateRcdata(e);break;case Ko.RAWTEXT:this._stateRawtext(e);break;case Ko.SCRIPT_DATA:this._stateScriptData(e);break;case Ko.PLAINTEXT:this._statePlaintext(e);break;case Ko.TAG_OPEN:this._stateTagOpen(e);break;case Ko.END_TAG_OPEN:this._stateEndTagOpen(e);break;case Ko.TAG_NAME:this._stateTagName(e);break;case Ko.RCDATA_LESS_THAN_SIGN:this._stateRcdataLessThanSign(e);break;case Ko.RCDATA_END_TAG_OPEN:this._stateRcdataEndTagOpen(e);break;case Ko.RCDATA_END_TAG_NAME:this._stateRcdataEndTagName(e);break;case Ko.RAWTEXT_LESS_THAN_SIGN:this._stateRawtextLessThanSign(e);break;case Ko.RAWTEXT_END_TAG_OPEN:this._stateRawtextEndTagOpen(e);break;case Ko.RAWTEXT_END_TAG_NAME:this._stateRawtextEndTagName(e);break;case Ko.SCRIPT_DATA_LESS_THAN_SIGN:this._stateScriptDataLessThanSign(e);break;case Ko.SCRIPT_DATA_END_TAG_OPEN:this._stateScriptDataEndTagOpen(e);break;case Ko.SCRIPT_DATA_END_TAG_NAME:this._stateScriptDataEndTagName(e);break;case Ko.SCRIPT_DATA_ESCAPE_START:this._stateScriptDataEscapeStart(e);break;case Ko.SCRIPT_DATA_ESCAPE_START_DASH:this._stateScriptDataEscapeStartDash(e);break;case Ko.SCRIPT_DATA_ESCAPED:this._stateScriptDataEscaped(e);break;case Ko.SCRIPT_DATA_ESCAPED_DASH:this._stateScriptDataEscapedDash(e);break;case Ko.SCRIPT_DATA_ESCAPED_DASH_DASH:this._stateScriptDataEscapedDashDash(e);break;case Ko.SCRIPT_DATA_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataEscapedLessThanSign(e);break;case Ko.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:this._stateScriptDataEscapedEndTagOpen(e);break;case Ko.SCRIPT_DATA_ESCAPED_END_TAG_NAME:this._stateScriptDataEscapedEndTagName(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPE_START:this._stateScriptDataDoubleEscapeStart(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED:this._stateScriptDataDoubleEscaped(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED_DASH:this._stateScriptDataDoubleEscapedDash(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED_DASH_DASH:this._stateScriptDataDoubleEscapedDashDash(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPED_LESS_THAN_SIGN:this._stateScriptDataDoubleEscapedLessThanSign(e);break;case Ko.SCRIPT_DATA_DOUBLE_ESCAPE_END:this._stateScriptDataDoubleEscapeEnd(e);break;case Ko.BEFORE_ATTRIBUTE_NAME:this._stateBeforeAttributeName(e);break;case Ko.ATTRIBUTE_NAME:this._stateAttributeName(e);break;case Ko.AFTER_ATTRIBUTE_NAME:this._stateAfterAttributeName(e);break;case Ko.BEFORE_ATTRIBUTE_VALUE:this._stateBeforeAttributeValue(e);break;case Ko.ATTRIBUTE_VALUE_DOUBLE_QUOTED:this._stateAttributeValueDoubleQuoted(e);break;case Ko.ATTRIBUTE_VALUE_SINGLE_QUOTED:this._stateAttributeValueSingleQuoted(e);break;case Ko.ATTRIBUTE_VALUE_UNQUOTED:this._stateAttributeValueUnquoted(e);break;case Ko.AFTER_ATTRIBUTE_VALUE_QUOTED:this._stateAfterAttributeValueQuoted(e);break;case Ko.SELF_CLOSING_START_TAG:this._stateSelfClosingStartTag(e);break;case Ko.BOGUS_COMMENT:this._stateBogusComment(e);break;case Ko.MARKUP_DECLARATION_OPEN:this._stateMarkupDeclarationOpen(e);break;case Ko.COMMENT_START:this._stateCommentStart(e);break;case Ko.COMMENT_START_DASH:this._stateCommentStartDash(e);break;case Ko.COMMENT:this._stateComment(e);break;case Ko.COMMENT_LESS_THAN_SIGN:this._stateCommentLessThanSign(e);break;case Ko.COMMENT_LESS_THAN_SIGN_BANG:this._stateCommentLessThanSignBang(e);break;case Ko.COMMENT_LESS_THAN_SIGN_BANG_DASH:this._stateCommentLessThanSignBangDash(e);break;case Ko.COMMENT_LESS_THAN_SIGN_BANG_DASH_DASH:this._stateCommentLessThanSignBangDashDash(e);break;case Ko.COMMENT_END_DASH:this._stateCommentEndDash(e);break;case Ko.COMMENT_END:this._stateCommentEnd(e);break;case Ko.COMMENT_END_BANG:this._stateCommentEndBang(e);break;case Ko.DOCTYPE:this._stateDoctype(e);break;case Ko.BEFORE_DOCTYPE_NAME:this._stateBeforeDoctypeName(e);break;case Ko.DOCTYPE_NAME:this._stateDoctypeName(e);break;case Ko.AFTER_DOCTYPE_NAME:this._stateAfterDoctypeName(e);break;case Ko.AFTER_DOCTYPE_PUBLIC_KEYWORD:this._stateAfterDoctypePublicKeyword(e);break;case Ko.BEFORE_DOCTYPE_PUBLIC_IDENTIFIER:this._stateBeforeDoctypePublicIdentifier(e);break;case Ko.DOCTYPE_PUBLIC_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypePublicIdentifierDoubleQuoted(e);break;case Ko.DOCTYPE_PUBLIC_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypePublicIdentifierSingleQuoted(e);break;case Ko.AFTER_DOCTYPE_PUBLIC_IDENTIFIER:this._stateAfterDoctypePublicIdentifier(e);break;case Ko.BETWEEN_DOCTYPE_PUBLIC_AND_SYSTEM_IDENTIFIERS:this._stateBetweenDoctypePublicAndSystemIdentifiers(e);break;case Ko.AFTER_DOCTYPE_SYSTEM_KEYWORD:this._stateAfterDoctypeSystemKeyword(e);break;case Ko.BEFORE_DOCTYPE_SYSTEM_IDENTIFIER:this._stateBeforeDoctypeSystemIdentifier(e);break;case Ko.DOCTYPE_SYSTEM_IDENTIFIER_DOUBLE_QUOTED:this._stateDoctypeSystemIdentifierDoubleQuoted(e);break;case Ko.DOCTYPE_SYSTEM_IDENTIFIER_SINGLE_QUOTED:this._stateDoctypeSystemIdentifierSingleQuoted(e);break;case Ko.AFTER_DOCTYPE_SYSTEM_IDENTIFIER:this._stateAfterDoctypeSystemIdentifier(e);break;case Ko.BOGUS_DOCTYPE:this._stateBogusDoctype(e);break;case Ko.CDATA_SECTION:this._stateCdataSection(e);break;case Ko.CDATA_SECTION_BRACKET:this._stateCdataSectionBracket(e);break;case Ko.CDATA_SECTION_END:this._stateCdataSectionEnd(e);break;case Ko.CHARACTER_REFERENCE:this._stateCharacterReference(e);break;case Ko.NAMED_CHARACTER_REFERENCE:this._stateNamedCharacterReference(e);break;case Ko.AMBIGUOUS_AMPERSAND:this._stateAmbiguousAmpersand(e);break;case Ko.NUMERIC_CHARACTER_REFERENCE:this._stateNumericCharacterReference(e);break;case Ko.HEXADEMICAL_CHARACTER_REFERENCE_START:this._stateHexademicalCharacterReferenceStart(e);break;case Ko.HEXADEMICAL_CHARACTER_REFERENCE:this._stateHexademicalCharacterReference(e);break;case Ko.DECIMAL_CHARACTER_REFERENCE:this._stateDecimalCharacterReference(e);break;case Ko.NUMERIC_CHARACTER_REFERENCE_END:this._stateNumericCharacterReferenceEnd(e);break;default:throw new Error("Unknown state")}}_stateData(e){switch(e){case io.LESS_THAN_SIGN:this.state=Ko.TAG_OPEN;break;case io.AMPERSAND:this.returnState=Ko.DATA,this.state=Ko.CHARACTER_REFERENCE;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitCodePoint(e);break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRcdata(e){switch(e){case io.AMPERSAND:this.returnState=Ko.RCDATA,this.state=Ko.CHARACTER_REFERENCE;break;case io.LESS_THAN_SIGN:this.state=Ko.RCDATA_LESS_THAN_SIGN;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateRawtext(e){switch(e){case io.LESS_THAN_SIGN:this.state=Ko.RAWTEXT_LESS_THAN_SIGN;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateScriptData(e){switch(e){case io.LESS_THAN_SIGN:this.state=Ko.SCRIPT_DATA_LESS_THAN_SIGN;break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_statePlaintext(e){switch(e){case io.NULL:this._err(Eo.unexpectedNullCharacter),this._emitChars("�");break;case io.EOF:this._emitEOFToken();break;default:this._emitCodePoint(e)}}_stateTagOpen(e){if(Qo(e))this._createStartTagToken(),this.state=Ko.TAG_NAME,this._stateTagName(e);else switch(e){case io.EXCLAMATION_MARK:this.state=Ko.MARKUP_DECLARATION_OPEN;break;case io.SOLIDUS:this.state=Ko.END_TAG_OPEN;break;case io.QUESTION_MARK:this._err(Eo.unexpectedQuestionMarkInsteadOfTagName),this._createCommentToken(1),this.state=Ko.BOGUS_COMMENT,this._stateBogusComment(e);break;case io.EOF:this._err(Eo.eofBeforeTagName),this._emitChars("<"),this._emitEOFToken();break;default:this._err(Eo.invalidFirstCharacterOfTagName),this._emitChars("<"),this.state=Ko.DATA,this._stateData(e)}}_stateEndTagOpen(e){if(Qo(e))this._createEndTagToken(),this.state=Ko.TAG_NAME,this._stateTagName(e);else switch(e){case io.GREATER_THAN_SIGN:this._err(Eo.missingEndTagName),this.state=Ko.DATA;break;case io.EOF:this._err(Eo.eofBeforeTagName),this._emitChars("");break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this.state=Ko.SCRIPT_DATA_ESCAPED,this._emitChars("�");break;case io.EOF:this._err(Eo.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Ko.SCRIPT_DATA_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataEscapedLessThanSign(e){e===io.SOLIDUS?this.state=Ko.SCRIPT_DATA_ESCAPED_END_TAG_OPEN:Qo(e)?(this._emitChars("<"),this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPE_START,this._stateScriptDataDoubleEscapeStart(e)):(this._emitChars("<"),this.state=Ko.SCRIPT_DATA_ESCAPED,this._stateScriptDataEscaped(e))}_stateScriptDataEscapedEndTagOpen(e){Qo(e)?(this.state=Ko.SCRIPT_DATA_ESCAPED_END_TAG_NAME,this._stateScriptDataEscapedEndTagName(e)):(this._emitChars("");break;case io.NULL:this._err(Eo.unexpectedNullCharacter),this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitChars("�");break;case io.EOF:this._err(Eo.eofInScriptHtmlCommentLikeText),this._emitEOFToken();break;default:this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPED,this._emitCodePoint(e)}}_stateScriptDataDoubleEscapedLessThanSign(e){e===io.SOLIDUS?(this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPE_END,this._emitChars("/")):(this.state=Ko.SCRIPT_DATA_DOUBLE_ESCAPED,this._stateScriptDataDoubleEscaped(e))}_stateScriptDataDoubleEscapeEnd(e){if(this.preprocessor.startsWith(co,!1)&&ec(this.preprocessor.peek(co.length))){this._emitCodePoint(e);for(let e=0;e1114111)this._err(Eo.characterReferenceOutsideUnicodeRange),this.charRefCode=io.REPLACEMENT_CHARACTER;else if(uo(this.charRefCode))this._err(Eo.surrogateCharacterReference),this.charRefCode=io.REPLACEMENT_CHARACTER;else if(fo(this.charRefCode))this._err(Eo.noncharacterCharacterReference);else if(po(this.charRefCode)||this.charRefCode===io.CARRIAGE_RETURN){this._err(Eo.controlCharacterReference);const e=qo.get(this.charRefCode);void 0!==e&&(this.charRefCode=e)}this._flushCodePointConsumedAsCharacterReference(this.charRefCode),this._reconsumeInState(this.returnState,e)}}const nc=new Set([Po.DD,Po.DT,Po.LI,Po.OPTGROUP,Po.OPTION,Po.P,Po.RB,Po.RP,Po.RT,Po.RTC]),rc=new Set([...nc,Po.CAPTION,Po.COLGROUP,Po.TBODY,Po.TD,Po.TFOOT,Po.TH,Po.THEAD,Po.TR]),ic=new Map([[Po.APPLET,vo.HTML],[Po.CAPTION,vo.HTML],[Po.HTML,vo.HTML],[Po.MARQUEE,vo.HTML],[Po.OBJECT,vo.HTML],[Po.TABLE,vo.HTML],[Po.TD,vo.HTML],[Po.TEMPLATE,vo.HTML],[Po.TH,vo.HTML],[Po.ANNOTATION_XML,vo.MATHML],[Po.MI,vo.MATHML],[Po.MN,vo.MATHML],[Po.MO,vo.MATHML],[Po.MS,vo.MATHML],[Po.MTEXT,vo.MATHML],[Po.DESC,vo.SVG],[Po.FOREIGN_OBJECT,vo.SVG],[Po.TITLE,vo.SVG]]),sc=[Po.H1,Po.H2,Po.H3,Po.H4,Po.H5,Po.H6],ac=[Po.TR,Po.TEMPLATE,Po.HTML],oc=[Po.TBODY,Po.TFOOT,Po.THEAD,Po.TEMPLATE,Po.HTML],cc=[Po.TABLE,Po.TEMPLATE,Po.HTML],lc=[Po.TD,Po.TH];class hc{constructor(e,t,n){this.treeAdapter=t,this.handler=n,this.items=[],this.tagIDs=[],this.stackTop=-1,this.tmplCount=0,this.currentTagId=Po.UNKNOWN,this.current=e}get currentTmplContentOrNode(){return this._isInTemplate()?this.treeAdapter.getTemplateContent(this.current):this.current}_indexOf(e){return this.items.lastIndexOf(e,this.stackTop)}_isInTemplate(){return this.currentTagId===Po.TEMPLATE&&this.treeAdapter.getNamespaceURI(this.current)===vo.HTML}_updateCurrentElement(){this.current=this.items[this.stackTop],this.currentTagId=this.tagIDs[this.stackTop]}push(e,t){this.stackTop++,this.items[this.stackTop]=e,this.current=e,this.tagIDs[this.stackTop]=t,this.currentTagId=t,this._isInTemplate()&&this.tmplCount++,this.handler.onItemPush(e,t,!0)}pop(){const e=this.current;this.tmplCount>0&&this._isInTemplate()&&this.tmplCount--,this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(e,!0)}replace(e,t){const n=this._indexOf(e);this.items[n]=t,n===this.stackTop&&(this.current=t)}insertAfter(e,t,n){const r=this._indexOf(e)+1;this.items.splice(r,0,t),this.tagIDs.splice(r,0,n),this.stackTop++,r===this.stackTop&&this._updateCurrentElement(),this.handler.onItemPush(this.current,this.currentTagId,r===this.stackTop)}popUntilTagNamePopped(e){let t=this.stackTop+1;do{t=this.tagIDs.lastIndexOf(e,t-1)}while(t>0&&this.treeAdapter.getNamespaceURI(this.items[t])!==vo.HTML);this.shortenToLength(t<0?0:t)}shortenToLength(e){for(;this.stackTop>=e;){const t=this.current;this.tmplCount>0&&this._isInTemplate()&&(this.tmplCount-=1),this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(t,this.stackTop=0;n--)if(e.includes(this.tagIDs[n])&&this.treeAdapter.getNamespaceURI(this.items[n])===t)return n;return-1}clearBackTo(e,t){const n=this._indexOfTagNames(e,t);this.shortenToLength(n+1)}clearBackToTableContext(){this.clearBackTo(cc,vo.HTML)}clearBackToTableBodyContext(){this.clearBackTo(oc,vo.HTML)}clearBackToTableRowContext(){this.clearBackTo(ac,vo.HTML)}remove(e){const t=this._indexOf(e);t>=0&&(t===this.stackTop?this.pop():(this.items.splice(t,1),this.tagIDs.splice(t,1),this.stackTop--,this._updateCurrentElement(),this.handler.onItemPop(e,!1)))}tryPeekProperlyNestedBodyElement(){return this.stackTop>=1&&this.tagIDs[1]===Po.BODY?this.items[1]:null}contains(e){return this._indexOf(e)>-1}getCommonAncestor(e){const t=this._indexOf(e)-1;return t>=0?this.items[t]:null}isRootHtmlElementCurrent(){return 0===this.stackTop&&this.tagIDs[0]===Po.HTML}hasInScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t],r=this.treeAdapter.getNamespaceURI(this.items[t]);if(n===e&&r===vo.HTML)return!0;if(ic.get(n)===r)return!1}return!0}hasNumberedHeaderInScope(){for(let e=this.stackTop;e>=0;e--){const t=this.tagIDs[e],n=this.treeAdapter.getNamespaceURI(this.items[e]);if(Go(t)&&n===vo.HTML)return!0;if(ic.get(t)===n)return!1}return!0}hasInListItemScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t],r=this.treeAdapter.getNamespaceURI(this.items[t]);if(n===e&&r===vo.HTML)return!0;if((n===Po.UL||n===Po.OL)&&r===vo.HTML||ic.get(n)===r)return!1}return!0}hasInButtonScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t],r=this.treeAdapter.getNamespaceURI(this.items[t]);if(n===e&&r===vo.HTML)return!0;if(n===Po.BUTTON&&r===vo.HTML||ic.get(n)===r)return!1}return!0}hasInTableScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t];if(this.treeAdapter.getNamespaceURI(this.items[t])===vo.HTML){if(n===e)return!0;if(n===Po.TABLE||n===Po.TEMPLATE||n===Po.HTML)return!1}}return!0}hasTableBodyContextInTableScope(){for(let e=this.stackTop;e>=0;e--){const t=this.tagIDs[e];if(this.treeAdapter.getNamespaceURI(this.items[e])===vo.HTML){if(t===Po.TBODY||t===Po.THEAD||t===Po.TFOOT)return!0;if(t===Po.TABLE||t===Po.HTML)return!1}}return!0}hasInSelectScope(e){for(let t=this.stackTop;t>=0;t--){const n=this.tagIDs[t];if(this.treeAdapter.getNamespaceURI(this.items[t])===vo.HTML){if(n===e)return!0;if(n!==Po.OPTION&&n!==Po.OPTGROUP)return!1}}return!0}generateImpliedEndTags(){for(;nc.has(this.currentTagId);)this.pop()}generateImpliedEndTagsThoroughly(){for(;rc.has(this.currentTagId);)this.pop()}generateImpliedEndTagsWithExclusion(e){for(;this.currentTagId!==e&&rc.has(this.currentTagId);)this.pop()}}var uc;!function(e){e[e.Marker=0]="Marker",e[e.Element=1]="Element"}(uc=uc||(uc={}));const pc={type:uc.Marker};class fc{constructor(e){this.treeAdapter=e,this.entries=[],this.bookmark=null}_getNoahArkConditionCandidates(e,t){const n=[],r=t.length,i=this.treeAdapter.getTagName(e),s=this.treeAdapter.getNamespaceURI(e);for(let e=0;e[e.name,e.value])));let i=0;for(let e=0;er.get(e.name)===e.value))&&(i+=1,i>=3&&this.entries.splice(t.idx,1))}}insertMarker(){this.entries.unshift(pc)}pushElement(e,t){this._ensureNoahArkCondition(e),this.entries.unshift({type:uc.Element,element:e,token:t})}insertElementAfterBookmark(e,t){const n=this.entries.indexOf(this.bookmark);this.entries.splice(n,0,{type:uc.Element,element:e,token:t})}removeEntry(e){const t=this.entries.indexOf(e);t>=0&&this.entries.splice(t,1)}clearToLastMarker(){const e=this.entries.indexOf(pc);e>=0?this.entries.splice(0,e+1):this.entries.length=0}getElementEntryInScopeWithTagName(e){const t=this.entries.find((t=>t.type===uc.Marker||this.treeAdapter.getTagName(t.element)===e));return t&&t.type===uc.Element?t:null}getElementEntry(e){return this.entries.find((t=>t.type===uc.Element&&t.element===e))}}function dc(e){return{nodeName:"#text",value:e,parentNode:null}}const Ec={createDocument:()=>({nodeName:"#document",mode:Ro.NO_QUIRKS,childNodes:[]}),createDocumentFragment:()=>({nodeName:"#document-fragment",childNodes:[]}),createElement:(e,t,n)=>({nodeName:e,tagName:e,attrs:n,namespaceURI:t,childNodes:[],parentNode:null}),createCommentNode:e=>({nodeName:"#comment",data:e,parentNode:null}),appendChild(e,t){e.childNodes.push(t),t.parentNode=e},insertBefore(e,t,n){const r=e.childNodes.indexOf(n);e.childNodes.splice(r,0,t),t.parentNode=e},setTemplateContent(e,t){e.content=t},getTemplateContent:e=>e.content,setDocumentType(e,t,n,r){const i=e.childNodes.find((e=>"#documentType"===e.nodeName));if(i)i.name=t,i.publicId=n,i.systemId=r;else{const i={nodeName:"#documentType",name:t,publicId:n,systemId:r,parentNode:null};Ec.appendChild(e,i)}},setDocumentMode(e,t){e.mode=t},getDocumentMode:e=>e.mode,detachNode(e){if(e.parentNode){const t=e.parentNode.childNodes.indexOf(e);e.parentNode.childNodes.splice(t,1),e.parentNode=null}},insertText(e,t){if(e.childNodes.length>0){const n=e.childNodes[e.childNodes.length-1];if(Ec.isTextNode(n))return void(n.value+=t)}Ec.appendChild(e,dc(t))},insertTextBefore(e,t,n){const r=e.childNodes[e.childNodes.indexOf(n)-1];r&&Ec.isTextNode(r)?r.value+=t:Ec.insertBefore(e,dc(t),n)},adoptAttributes(e,t){const n=new Set(e.attrs.map((e=>e.name)));for(let r=0;re.childNodes[0],getChildNodes:e=>e.childNodes,getParentNode:e=>e.parentNode,getAttrList:e=>e.attrs,getTagName:e=>e.tagName,getNamespaceURI:e=>e.namespaceURI,getTextNodeContent:e=>e.value,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName:e=>e.name,getDocumentTypeNodePublicId:e=>e.publicId,getDocumentTypeNodeSystemId:e=>e.systemId,isTextNode:e=>"#text"===e.nodeName,isCommentNode:e=>"#comment"===e.nodeName,isDocumentTypeNode:e=>"#documentType"===e.nodeName,isElementNode:e=>Object.prototype.hasOwnProperty.call(e,"tagName"),setNodeSourceCodeLocation(e,t){e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){e.sourceCodeLocation={...e.sourceCodeLocation,...t}}},mc=["+//silmaril//dtd html pro v0r11 19970101//","-//as//dtd html 3.0 aswedit + extensions//","-//advasoft ltd//dtd html 3.0 aswedit + extensions//","-//ietf//dtd html 2.0 level 1//","-//ietf//dtd html 2.0 level 2//","-//ietf//dtd html 2.0 strict level 1//","-//ietf//dtd html 2.0 strict level 2//","-//ietf//dtd html 2.0 strict//","-//ietf//dtd html 2.0//","-//ietf//dtd html 2.1e//","-//ietf//dtd html 3.0//","-//ietf//dtd html 3.2 final//","-//ietf//dtd html 3.2//","-//ietf//dtd html 3//","-//ietf//dtd html level 0//","-//ietf//dtd html level 1//","-//ietf//dtd html level 2//","-//ietf//dtd html level 3//","-//ietf//dtd html strict level 0//","-//ietf//dtd html strict level 1//","-//ietf//dtd html strict level 2//","-//ietf//dtd html strict level 3//","-//ietf//dtd html strict//","-//ietf//dtd html//","-//metrius//dtd metrius presentational//","-//microsoft//dtd internet explorer 2.0 html strict//","-//microsoft//dtd internet explorer 2.0 html//","-//microsoft//dtd internet explorer 2.0 tables//","-//microsoft//dtd internet explorer 3.0 html strict//","-//microsoft//dtd internet explorer 3.0 html//","-//microsoft//dtd internet explorer 3.0 tables//","-//netscape comm. corp.//dtd html//","-//netscape comm. corp.//dtd strict html//","-//o'reilly and associates//dtd html 2.0//","-//o'reilly and associates//dtd html extended 1.0//","-//o'reilly and associates//dtd html extended relaxed 1.0//","-//sq//dtd html 2.0 hotmetal + extensions//","-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//","-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//","-//spyglass//dtd html 2.0 extended//","-//sun microsystems corp.//dtd hotjava html//","-//sun microsystems corp.//dtd hotjava strict html//","-//w3c//dtd html 3 1995-03-24//","-//w3c//dtd html 3.2 draft//","-//w3c//dtd html 3.2 final//","-//w3c//dtd html 3.2//","-//w3c//dtd html 3.2s draft//","-//w3c//dtd html 4.0 frameset//","-//w3c//dtd html 4.0 transitional//","-//w3c//dtd html experimental 19960712//","-//w3c//dtd html experimental 970421//","-//w3c//dtd w3 html//","-//w3o//dtd w3 html 3.0//","-//webtechs//dtd mozilla html 2.0//","-//webtechs//dtd mozilla html//"],Tc=[...mc,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"],_c=new Set(["-//w3o//dtd w3 html strict 3.0//en//","-/w3c/dtd html 4.0 transitional/en","html"]),Ac=["-//w3c//dtd xhtml 1.0 frameset//","-//w3c//dtd xhtml 1.0 transitional//"],gc=[...Ac,"-//w3c//dtd html 4.01 frameset//","-//w3c//dtd html 4.01 transitional//"];function Nc(e,t){return t.some((t=>e.startsWith(t)))}const Cc="text/html",Ic="application/xhtml+xml",Sc=new Map(["attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map((e=>[e.toLowerCase(),e]))),bc=new Map([["xlink:actuate",{prefix:"xlink",name:"actuate",namespace:vo.XLINK}],["xlink:arcrole",{prefix:"xlink",name:"arcrole",namespace:vo.XLINK}],["xlink:href",{prefix:"xlink",name:"href",namespace:vo.XLINK}],["xlink:role",{prefix:"xlink",name:"role",namespace:vo.XLINK}],["xlink:show",{prefix:"xlink",name:"show",namespace:vo.XLINK}],["xlink:title",{prefix:"xlink",name:"title",namespace:vo.XLINK}],["xlink:type",{prefix:"xlink",name:"type",namespace:vo.XLINK}],["xml:base",{prefix:"xml",name:"base",namespace:vo.XML}],["xml:lang",{prefix:"xml",name:"lang",namespace:vo.XML}],["xml:space",{prefix:"xml",name:"space",namespace:vo.XML}],["xmlns",{prefix:"",name:"xmlns",namespace:vo.XMLNS}],["xmlns:xlink",{prefix:"xmlns",name:"xlink",namespace:vo.XMLNS}]]),Oc=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map((e=>[e.toLowerCase(),e]))),yc=new Set([Po.B,Po.BIG,Po.BLOCKQUOTE,Po.BODY,Po.BR,Po.CENTER,Po.CODE,Po.DD,Po.DIV,Po.DL,Po.DT,Po.EM,Po.EMBED,Po.H1,Po.H2,Po.H3,Po.H4,Po.H5,Po.H6,Po.HEAD,Po.HR,Po.I,Po.IMG,Po.LI,Po.LISTING,Po.MENU,Po.META,Po.NOBR,Po.OL,Po.P,Po.PRE,Po.RUBY,Po.S,Po.SMALL,Po.SPAN,Po.STRONG,Po.STRIKE,Po.SUB,Po.SUP,Po.TABLE,Po.TT,Po.U,Po.UL,Po.VAR]);function Lc(e){for(let t=0;t0&&this._setContextModes(e,t)}onItemPop(e,t){var n,r;if(this.options.sourceCodeLocationInfo&&this._setEndLocation(e,this.currentToken),null===(r=(n=this.treeAdapter).onItemPop)||void 0===r||r.call(n,e,this.openElements.current),t){let e,t;0===this.openElements.stackTop&&this.fragmentContext?(e=this.fragmentContext,t=this.fragmentContextID):({current:e,currentTagId:t}=this.openElements),this._setContextModes(e,t)}}_setContextModes(e,t){const n=e===this.document||this.treeAdapter.getNamespaceURI(e)===vo.HTML;this.currentNotInHTML=!n,this.tokenizer.inForeignNode=!n&&!this._isIntegrationPoint(t,e)}_switchToTextParsing(e,t){this._insertElement(e,vo.HTML),this.tokenizer.state=t,this.originalInsertionMode=this.insertionMode,this.insertionMode=Rc.TEXT}switchToPlaintextParsing(){this.insertionMode=Rc.TEXT,this.originalInsertionMode=Rc.IN_BODY,this.tokenizer.state=jo.PLAINTEXT}_getAdjustedCurrentElement(){return 0===this.openElements.stackTop&&this.fragmentContext?this.fragmentContext:this.openElements.current}_findFormInFragmentContext(){let e=this.fragmentContext;for(;e;){if(this.treeAdapter.getTagName(e)===Mo.FORM){this.formElement=e;break}e=this.treeAdapter.getParentNode(e)}}_initTokenizerForFragmentParsing(){if(this.fragmentContext&&this.treeAdapter.getNamespaceURI(this.fragmentContext)===vo.HTML)switch(this.fragmentContextID){case Po.TITLE:case Po.TEXTAREA:this.tokenizer.state=jo.RCDATA;break;case Po.STYLE:case Po.XMP:case Po.IFRAME:case Po.NOEMBED:case Po.NOFRAMES:case Po.NOSCRIPT:this.tokenizer.state=jo.RAWTEXT;break;case Po.SCRIPT:this.tokenizer.state=jo.SCRIPT_DATA;break;case Po.PLAINTEXT:this.tokenizer.state=jo.PLAINTEXT}}_setDocumentType(e){const t=e.name||"",n=e.publicId||"",r=e.systemId||"";if(this.treeAdapter.setDocumentType(this.document,t,n,r),e.location){const t=this.treeAdapter.getChildNodes(this.document).find((e=>this.treeAdapter.isDocumentTypeNode(e)));t&&this.treeAdapter.setNodeSourceCodeLocation(t,e.location)}}_attachElementToTree(e,t){if(this.options.sourceCodeLocationInfo){const n=t&&{...t,startTag:t};this.treeAdapter.setNodeSourceCodeLocation(e,n)}if(this._shouldFosterParentOnInsertion())this._fosterParentElement(e);else{const t=this.openElements.currentTmplContentOrNode;this.treeAdapter.appendChild(t,e)}}_appendElement(e,t){const n=this.treeAdapter.createElement(e.tagName,t,e.attrs);this._attachElementToTree(n,e.location)}_insertElement(e,t){const n=this.treeAdapter.createElement(e.tagName,t,e.attrs);this._attachElementToTree(n,e.location),this.openElements.push(n,e.tagID)}_insertFakeElement(e,t){const n=this.treeAdapter.createElement(e,vo.HTML,[]);this._attachElementToTree(n,null),this.openElements.push(n,t)}_insertTemplate(e){const t=this.treeAdapter.createElement(e.tagName,vo.HTML,e.attrs),n=this.treeAdapter.createDocumentFragment();this.treeAdapter.setTemplateContent(t,n),this._attachElementToTree(t,e.location),this.openElements.push(t,e.tagID),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,null)}_insertFakeRootElement(){const e=this.treeAdapter.createElement(Mo.HTML,vo.HTML,[]);this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(e,null),this.treeAdapter.appendChild(this.openElements.current,e),this.openElements.push(e,Po.HTML)}_appendCommentNode(e,t){const n=this.treeAdapter.createCommentNode(e.data);this.treeAdapter.appendChild(t,n),this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(n,e.location)}_insertCharacters(e){let t,n;if(this._shouldFosterParentOnInsertion()?(({parent:t,beforeElement:n}=this._findFosterParentingLocation()),n?this.treeAdapter.insertTextBefore(t,e.chars,n):this.treeAdapter.insertText(t,e.chars)):(t=this.openElements.currentTmplContentOrNode,this.treeAdapter.insertText(t,e.chars)),!e.location)return;const r=this.treeAdapter.getChildNodes(t),i=n?r.lastIndexOf(n):r.length,s=r[i-1];if(this.treeAdapter.getNodeSourceCodeLocation(s)){const{endLine:t,endCol:n,endOffset:r}=e.location;this.treeAdapter.updateNodeSourceCodeLocation(s,{endLine:t,endCol:n,endOffset:r})}else this.options.sourceCodeLocationInfo&&this.treeAdapter.setNodeSourceCodeLocation(s,e.location)}_adoptNodes(e,t){for(let n=this.treeAdapter.getFirstChild(e);n;n=this.treeAdapter.getFirstChild(e))this.treeAdapter.detachNode(n),this.treeAdapter.appendChild(t,n)}_setEndLocation(e,t){if(this.treeAdapter.getNodeSourceCodeLocation(e)&&t.location){const n=t.location,r=this.treeAdapter.getTagName(e),i=t.type===To.END_TAG&&r===t.tagName?{endTag:{...n},endLine:n.endLine,endCol:n.endCol,endOffset:n.endOffset}:{endLine:n.startLine,endCol:n.startCol,endOffset:n.startOffset};this.treeAdapter.updateNodeSourceCodeLocation(e,i)}}shouldProcessStartTagTokenInForeignContent(e){if(!this.currentNotInHTML)return!1;let t,n;return 0===this.openElements.stackTop&&this.fragmentContext?(t=this.fragmentContext,n=this.fragmentContextID):({current:t,currentTagId:n}=this.openElements),(e.tagID!==Po.SVG||this.treeAdapter.getTagName(t)!==Mo.ANNOTATION_XML||this.treeAdapter.getNamespaceURI(t)!==vo.MATHML)&&(this.tokenizer.inForeignNode||(e.tagID===Po.MGLYPH||e.tagID===Po.MALIGNMARK)&&!this._isIntegrationPoint(n,t,vo.HTML))}_processToken(e){switch(e.type){case To.CHARACTER:this.onCharacter(e);break;case To.NULL_CHARACTER:this.onNullCharacter(e);break;case To.COMMENT:this.onComment(e);break;case To.DOCTYPE:this.onDoctype(e);break;case To.START_TAG:this._processStartTag(e);break;case To.END_TAG:this.onEndTag(e);break;case To.EOF:this.onEof(e);break;case To.WHITESPACE_CHARACTER:this.onWhitespaceCharacter(e)}}_isIntegrationPoint(e,t,n){return Dc(e,this.treeAdapter.getNamespaceURI(t),this.treeAdapter.getAttrList(t),n)}_reconstructActiveFormattingElements(){const e=this.activeFormattingElements.entries.length;if(e){const t=this.activeFormattingElements.entries.findIndex((e=>e.type===uc.Marker||this.openElements.contains(e.element)));for(let n=t<0?e-1:t-1;n>=0;n--){const e=this.activeFormattingElements.entries[n];this._insertElement(e.token,this.treeAdapter.getNamespaceURI(e.element)),e.element=this.openElements.current}}}_closeTableCell(){this.openElements.generateImpliedEndTags(),this.openElements.popUntilTableCellPopped(),this.activeFormattingElements.clearToLastMarker(),this.insertionMode=Rc.IN_ROW}_closePElement(){this.openElements.generateImpliedEndTagsWithExclusion(Po.P),this.openElements.popUntilTagNamePopped(Po.P)}_resetInsertionMode(){for(let e=this.openElements.stackTop;e>=0;e--)switch(0===e&&this.fragmentContext?this.fragmentContextID:this.openElements.tagIDs[e]){case Po.TR:return void(this.insertionMode=Rc.IN_ROW);case Po.TBODY:case Po.THEAD:case Po.TFOOT:return void(this.insertionMode=Rc.IN_TABLE_BODY);case Po.CAPTION:return void(this.insertionMode=Rc.IN_CAPTION);case Po.COLGROUP:return void(this.insertionMode=Rc.IN_COLUMN_GROUP);case Po.TABLE:return void(this.insertionMode=Rc.IN_TABLE);case Po.BODY:return void(this.insertionMode=Rc.IN_BODY);case Po.FRAMESET:return void(this.insertionMode=Rc.IN_FRAMESET);case Po.SELECT:return void this._resetInsertionModeForSelect(e);case Po.TEMPLATE:return void(this.insertionMode=this.tmplInsertionModeStack[0]);case Po.HTML:return void(this.insertionMode=this.headElement?Rc.AFTER_HEAD:Rc.BEFORE_HEAD);case Po.TD:case Po.TH:if(e>0)return void(this.insertionMode=Rc.IN_CELL);break;case Po.HEAD:if(e>0)return void(this.insertionMode=Rc.IN_HEAD)}this.insertionMode=Rc.IN_BODY}_resetInsertionModeForSelect(e){if(e>0)for(let t=e-1;t>0;t--){const e=this.openElements.tagIDs[t];if(e===Po.TEMPLATE)break;if(e===Po.TABLE)return void(this.insertionMode=Rc.IN_SELECT_IN_TABLE)}this.insertionMode=Rc.IN_SELECT}_isElementCausesFosterParenting(e){return Pc.has(e)}_shouldFosterParentOnInsertion(){return this.fosterParentingEnabled&&this._isElementCausesFosterParenting(this.openElements.currentTagId)}_findFosterParentingLocation(){for(let e=this.openElements.stackTop;e>=0;e--){const t=this.openElements.items[e];switch(this.openElements.tagIDs[e]){case Po.TEMPLATE:if(this.treeAdapter.getNamespaceURI(t)===vo.HTML)return{parent:this.treeAdapter.getTemplateContent(t),beforeElement:null};break;case Po.TABLE:{const n=this.treeAdapter.getParentNode(t);return n?{parent:n,beforeElement:t}:{parent:this.openElements.items[e-1],beforeElement:null}}}}return{parent:this.openElements.items[0],beforeElement:null}}_fosterParentElement(e){const t=this._findFosterParentingLocation();t.beforeElement?this.treeAdapter.insertBefore(t.parent,e,t.beforeElement):this.treeAdapter.appendChild(t.parent,e)}_isSpecialElement(e,t){const n=this.treeAdapter.getNamespaceURI(e);return Ho[n].has(t)}onCharacter(e){if(this.skipNextNewLine=!1,this.tokenizer.inForeignNode)!function(e,t){e._insertCharacters(t),e.framesetOk=!1}(this,e);else switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:Wc(this,e);break;case Rc.BEFORE_HEAD:Qc(this,e);break;case Rc.IN_HEAD:zc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:Jc(this,e);break;case Rc.AFTER_HEAD:Zc(this,e);break;case Rc.IN_BODY:case Rc.IN_CAPTION:case Rc.IN_CELL:case Rc.IN_TEMPLATE:nl(this,e);break;case Rc.TEXT:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:this._insertCharacters(e);break;case Rc.IN_TABLE:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:ul(this,e);break;case Rc.IN_TABLE_TEXT:ml(this,e);break;case Rc.IN_COLUMN_GROUP:gl(this,e);break;case Rc.AFTER_BODY:kl(this,e);break;case Rc.AFTER_AFTER_BODY:vl(this,e)}}onNullCharacter(e){if(this.skipNextNewLine=!1,this.tokenizer.inForeignNode)!function(e,t){t.chars="�",e._insertCharacters(t)}(this,e);else switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:Wc(this,e);break;case Rc.BEFORE_HEAD:Qc(this,e);break;case Rc.IN_HEAD:zc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:Jc(this,e);break;case Rc.AFTER_HEAD:Zc(this,e);break;case Rc.TEXT:this._insertCharacters(e);break;case Rc.IN_TABLE:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:ul(this,e);break;case Rc.IN_COLUMN_GROUP:gl(this,e);break;case Rc.AFTER_BODY:kl(this,e);break;case Rc.AFTER_AFTER_BODY:vl(this,e)}}onComment(e){if(this.skipNextNewLine=!1,this.currentNotInHTML)Kc(this,e);else switch(this.insertionMode){case Rc.INITIAL:case Rc.BEFORE_HTML:case Rc.BEFORE_HEAD:case Rc.IN_HEAD:case Rc.IN_HEAD_NO_SCRIPT:case Rc.AFTER_HEAD:case Rc.IN_BODY:case Rc.IN_TABLE:case Rc.IN_CAPTION:case Rc.IN_COLUMN_GROUP:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:case Rc.IN_CELL:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:case Rc.IN_TEMPLATE:case Rc.IN_FRAMESET:case Rc.AFTER_FRAMESET:Kc(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.AFTER_BODY:!function(e,t){e._appendCommentNode(t,e.openElements.items[0])}(this,e);break;case Rc.AFTER_AFTER_BODY:case Rc.AFTER_AFTER_FRAMESET:!function(e,t){e._appendCommentNode(t,e.document)}(this,e)}}onDoctype(e){switch(this.skipNextNewLine=!1,this.insertionMode){case Rc.INITIAL:!function(e,t){e._setDocumentType(t);const n=t.forceQuirks?Ro.QUIRKS:function(e){if("html"!==e.name)return Ro.QUIRKS;const{systemId:t}=e;if(t&&"http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd"===t.toLowerCase())return Ro.QUIRKS;let{publicId:n}=e;if(null!==n){if(n=n.toLowerCase(),_c.has(n))return Ro.QUIRKS;let e=null===t?Tc:mc;if(Nc(n,e))return Ro.QUIRKS;if(e=null===t?Ac:gc,Nc(n,e))return Ro.LIMITED_QUIRKS}return Ro.NO_QUIRKS}(t);(function(e){return"html"===e.name&&null===e.publicId&&(null===e.systemId||"about:legacy-compat"===e.systemId)})(t)||e._err(t,Eo.nonConformingDoctype);e.treeAdapter.setDocumentMode(e.document,n),e.insertionMode=Rc.BEFORE_HTML}(this,e);break;case Rc.BEFORE_HEAD:case Rc.IN_HEAD:case Rc.IN_HEAD_NO_SCRIPT:case Rc.AFTER_HEAD:this._err(e,Eo.misplacedDoctype);break;case Rc.IN_TABLE_TEXT:Tl(this,e)}}onStartTag(e){this.skipNextNewLine=!1,this.currentToken=e,this._processStartTag(e),e.selfClosing&&!e.ackSelfClosing&&this._err(e,Eo.nonVoidHtmlElementStartTagWithTrailingSolidus)}_processStartTag(e){this.shouldProcessStartTagTokenInForeignContent(e)?function(e,t){if(function(e){const t=e.tagID;return t===Po.FONT&&e.attrs.some((({name:e})=>e===Do.COLOR||e===Do.SIZE||e===Do.FACE))||yc.has(t)}(t))Dl(e),e._startTagOutsideForeignContent(t);else{const n=e._getAdjustedCurrentElement(),r=e.treeAdapter.getNamespaceURI(n);r===vo.MATHML?Lc(t):r===vo.SVG&&(!function(e){const t=Oc.get(e.tagName);null!=t&&(e.tagName=t,e.tagID=Fo(e.tagName))}(t),kc(t)),vc(t),t.selfClosing?e._appendElement(t,r):e._insertElement(t,r),t.ackSelfClosing=!0}}(this,e):this._startTagOutsideForeignContent(e)}_startTagOutsideForeignContent(e){switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:!function(e,t){t.tagID===Po.HTML?(e._insertElement(t,vo.HTML),e.insertionMode=Rc.BEFORE_HEAD):Wc(e,t)}(this,e);break;case Rc.BEFORE_HEAD:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.HEAD:e._insertElement(t,vo.HTML),e.headElement=e.openElements.current,e.insertionMode=Rc.IN_HEAD;break;default:Qc(e,t)}}(this,e);break;case Rc.IN_HEAD:Xc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.BASEFONT:case Po.BGSOUND:case Po.HEAD:case Po.LINK:case Po.META:case Po.NOFRAMES:case Po.STYLE:Xc(e,t);break;case Po.NOSCRIPT:e._err(t,Eo.nestedNoscriptInHead);break;default:Jc(e,t)}}(this,e);break;case Rc.AFTER_HEAD:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.BODY:e._insertElement(t,vo.HTML),e.framesetOk=!1,e.insertionMode=Rc.IN_BODY;break;case Po.FRAMESET:e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_FRAMESET;break;case Po.BASE:case Po.BASEFONT:case Po.BGSOUND:case Po.LINK:case Po.META:case Po.NOFRAMES:case Po.SCRIPT:case Po.STYLE:case Po.TEMPLATE:case Po.TITLE:e._err(t,Eo.abandonedHeadElementChild),e.openElements.push(e.headElement,Po.HEAD),Xc(e,t),e.openElements.remove(e.headElement);break;case Po.HEAD:e._err(t,Eo.misplacedStartTagForHeadElement);break;default:Zc(e,t)}}(this,e);break;case Rc.IN_BODY:ol(this,e);break;case Rc.IN_TABLE:pl(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.IN_CAPTION:!function(e,t){const n=t.tagID;_l.has(n)?e.openElements.hasInTableScope(Po.CAPTION)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Po.CAPTION),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Rc.IN_TABLE,pl(e,t)):ol(e,t)}(this,e);break;case Rc.IN_COLUMN_GROUP:Al(this,e);break;case Rc.IN_TABLE_BODY:Nl(this,e);break;case Rc.IN_ROW:Il(this,e);break;case Rc.IN_CELL:!function(e,t){const n=t.tagID;_l.has(n)?(e.openElements.hasInTableScope(Po.TD)||e.openElements.hasInTableScope(Po.TH))&&(e._closeTableCell(),Il(e,t)):ol(e,t)}(this,e);break;case Rc.IN_SELECT:bl(this,e);break;case Rc.IN_SELECT_IN_TABLE:!function(e,t){const n=t.tagID;n===Po.CAPTION||n===Po.TABLE||n===Po.TBODY||n===Po.TFOOT||n===Po.THEAD||n===Po.TR||n===Po.TD||n===Po.TH?(e.openElements.popUntilTagNamePopped(Po.SELECT),e._resetInsertionMode(),e._processStartTag(t)):bl(e,t)}(this,e);break;case Rc.IN_TEMPLATE:!function(e,t){switch(t.tagID){case Po.BASE:case Po.BASEFONT:case Po.BGSOUND:case Po.LINK:case Po.META:case Po.NOFRAMES:case Po.SCRIPT:case Po.STYLE:case Po.TEMPLATE:case Po.TITLE:Xc(e,t);break;case Po.CAPTION:case Po.COLGROUP:case Po.TBODY:case Po.TFOOT:case Po.THEAD:e.tmplInsertionModeStack[0]=Rc.IN_TABLE,e.insertionMode=Rc.IN_TABLE,pl(e,t);break;case Po.COL:e.tmplInsertionModeStack[0]=Rc.IN_COLUMN_GROUP,e.insertionMode=Rc.IN_COLUMN_GROUP,Al(e,t);break;case Po.TR:e.tmplInsertionModeStack[0]=Rc.IN_TABLE_BODY,e.insertionMode=Rc.IN_TABLE_BODY,Nl(e,t);break;case Po.TD:case Po.TH:e.tmplInsertionModeStack[0]=Rc.IN_ROW,e.insertionMode=Rc.IN_ROW,Il(e,t);break;default:e.tmplInsertionModeStack[0]=Rc.IN_BODY,e.insertionMode=Rc.IN_BODY,ol(e,t)}}(this,e);break;case Rc.AFTER_BODY:!function(e,t){t.tagID===Po.HTML?ol(e,t):kl(e,t)}(this,e);break;case Rc.IN_FRAMESET:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.FRAMESET:e._insertElement(t,vo.HTML);break;case Po.FRAME:e._appendElement(t,vo.HTML),t.ackSelfClosing=!0;break;case Po.NOFRAMES:Xc(e,t)}}(this,e);break;case Rc.AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.NOFRAMES:Xc(e,t)}}(this,e);break;case Rc.AFTER_AFTER_BODY:!function(e,t){t.tagID===Po.HTML?ol(e,t):vl(e,t)}(this,e);break;case Rc.AFTER_AFTER_FRAMESET:!function(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.NOFRAMES:Xc(e,t)}}(this,e)}}onEndTag(e){this.skipNextNewLine=!1,this.currentToken=e,this.currentNotInHTML?function(e,t){if(t.tagID===Po.P||t.tagID===Po.BR)return Dl(e),void e._endTagOutsideForeignContent(t);for(let n=e.openElements.stackTop;n>0;n--){const r=e.openElements.items[n];if(e.treeAdapter.getNamespaceURI(r)===vo.HTML){e._endTagOutsideForeignContent(t);break}const i=e.treeAdapter.getTagName(r);if(i.toLowerCase()===t.tagName){t.tagName=i,e.openElements.shortenToLength(n);break}}}(this,e):this._endTagOutsideForeignContent(e)}_endTagOutsideForeignContent(e){switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:!function(e,t){const n=t.tagID;n!==Po.HTML&&n!==Po.HEAD&&n!==Po.BODY&&n!==Po.BR||Wc(e,t)}(this,e);break;case Rc.BEFORE_HEAD:!function(e,t){const n=t.tagID;n===Po.HEAD||n===Po.BODY||n===Po.HTML||n===Po.BR?Qc(e,t):e._err(t,Eo.endTagWithoutMatchingOpenElement)}(this,e);break;case Rc.IN_HEAD:!function(e,t){switch(t.tagID){case Po.HEAD:e.openElements.pop(),e.insertionMode=Rc.AFTER_HEAD;break;case Po.BODY:case Po.BR:case Po.HTML:zc(e,t);break;case Po.TEMPLATE:$c(e,t);break;default:e._err(t,Eo.endTagWithoutMatchingOpenElement)}}(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:!function(e,t){switch(t.tagID){case Po.NOSCRIPT:e.openElements.pop(),e.insertionMode=Rc.IN_HEAD;break;case Po.BR:Jc(e,t);break;default:e._err(t,Eo.endTagWithoutMatchingOpenElement)}}(this,e);break;case Rc.AFTER_HEAD:!function(e,t){switch(t.tagID){case Po.BODY:case Po.HTML:case Po.BR:Zc(e,t);break;case Po.TEMPLATE:$c(e,t);break;default:e._err(t,Eo.endTagWithoutMatchingOpenElement)}}(this,e);break;case Rc.IN_BODY:ll(this,e);break;case Rc.TEXT:!function(e,t){var n;t.tagID===Po.SCRIPT&&(null===(n=e.scriptHandler)||void 0===n||n.call(e,e.openElements.current));e.openElements.pop(),e.insertionMode=e.originalInsertionMode}(this,e);break;case Rc.IN_TABLE:fl(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.IN_CAPTION:!function(e,t){const n=t.tagID;switch(n){case Po.CAPTION:case Po.TABLE:e.openElements.hasInTableScope(Po.CAPTION)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Po.CAPTION),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Rc.IN_TABLE,n===Po.TABLE&&fl(e,t));break;case Po.BODY:case Po.COL:case Po.COLGROUP:case Po.HTML:case Po.TBODY:case Po.TD:case Po.TFOOT:case Po.TH:case Po.THEAD:case Po.TR:break;default:ll(e,t)}}(this,e);break;case Rc.IN_COLUMN_GROUP:!function(e,t){switch(t.tagID){case Po.COLGROUP:e.openElements.currentTagId===Po.COLGROUP&&(e.openElements.pop(),e.insertionMode=Rc.IN_TABLE);break;case Po.TEMPLATE:$c(e,t);break;case Po.COL:break;default:gl(e,t)}}(this,e);break;case Rc.IN_TABLE_BODY:Cl(this,e);break;case Rc.IN_ROW:Sl(this,e);break;case Rc.IN_CELL:!function(e,t){const n=t.tagID;switch(n){case Po.TD:case Po.TH:e.openElements.hasInTableScope(n)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(n),e.activeFormattingElements.clearToLastMarker(),e.insertionMode=Rc.IN_ROW);break;case Po.TABLE:case Po.TBODY:case Po.TFOOT:case Po.THEAD:case Po.TR:e.openElements.hasInTableScope(n)&&(e._closeTableCell(),Sl(e,t));break;case Po.BODY:case Po.CAPTION:case Po.COL:case Po.COLGROUP:case Po.HTML:break;default:ll(e,t)}}(this,e);break;case Rc.IN_SELECT:Ol(this,e);break;case Rc.IN_SELECT_IN_TABLE:!function(e,t){const n=t.tagID;n===Po.CAPTION||n===Po.TABLE||n===Po.TBODY||n===Po.TFOOT||n===Po.THEAD||n===Po.TR||n===Po.TD||n===Po.TH?e.openElements.hasInTableScope(n)&&(e.openElements.popUntilTagNamePopped(Po.SELECT),e._resetInsertionMode(),e.onEndTag(t)):Ol(e,t)}(this,e);break;case Rc.IN_TEMPLATE:!function(e,t){t.tagID===Po.TEMPLATE&&$c(e,t)}(this,e);break;case Rc.AFTER_BODY:Ll(this,e);break;case Rc.IN_FRAMESET:!function(e,t){t.tagID!==Po.FRAMESET||e.openElements.isRootHtmlElementCurrent()||(e.openElements.pop(),e.fragmentContext||e.openElements.currentTagId===Po.FRAMESET||(e.insertionMode=Rc.AFTER_FRAMESET))}(this,e);break;case Rc.AFTER_FRAMESET:!function(e,t){t.tagID===Po.HTML&&(e.insertionMode=Rc.AFTER_AFTER_FRAMESET)}(this,e);break;case Rc.AFTER_AFTER_BODY:vl(this,e)}}onEof(e){switch(this.insertionMode){case Rc.INITIAL:Vc(this,e);break;case Rc.BEFORE_HTML:Wc(this,e);break;case Rc.BEFORE_HEAD:Qc(this,e);break;case Rc.IN_HEAD:zc(this,e);break;case Rc.IN_HEAD_NO_SCRIPT:Jc(this,e);break;case Rc.AFTER_HEAD:Zc(this,e);break;case Rc.IN_BODY:case Rc.IN_TABLE:case Rc.IN_CAPTION:case Rc.IN_COLUMN_GROUP:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:case Rc.IN_CELL:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:hl(this,e);break;case Rc.TEXT:!function(e,t){e._err(t,Eo.eofInElementThatCanContainOnlyText),e.openElements.pop(),e.insertionMode=e.originalInsertionMode,e.onEof(t)}(this,e);break;case Rc.IN_TABLE_TEXT:Tl(this,e);break;case Rc.IN_TEMPLATE:yl(this,e);break;case Rc.AFTER_BODY:case Rc.IN_FRAMESET:case Rc.AFTER_FRAMESET:case Rc.AFTER_AFTER_BODY:case Rc.AFTER_AFTER_FRAMESET:jc(this,e)}}onWhitespaceCharacter(e){if(this.skipNextNewLine&&(this.skipNextNewLine=!1,e.chars.charCodeAt(0)===io.LINE_FEED)){if(1===e.chars.length)return;e.chars=e.chars.substr(1)}if(this.tokenizer.inForeignNode)this._insertCharacters(e);else switch(this.insertionMode){case Rc.IN_HEAD:case Rc.IN_HEAD_NO_SCRIPT:case Rc.AFTER_HEAD:case Rc.TEXT:case Rc.IN_COLUMN_GROUP:case Rc.IN_SELECT:case Rc.IN_SELECT_IN_TABLE:case Rc.IN_FRAMESET:case Rc.AFTER_FRAMESET:this._insertCharacters(e);break;case Rc.IN_BODY:case Rc.IN_CAPTION:case Rc.IN_CELL:case Rc.IN_TEMPLATE:case Rc.AFTER_BODY:case Rc.AFTER_AFTER_BODY:case Rc.AFTER_AFTER_FRAMESET:tl(this,e);break;case Rc.IN_TABLE:case Rc.IN_TABLE_BODY:case Rc.IN_ROW:ul(this,e);break;case Rc.IN_TABLE_TEXT:El(this,e)}}}function Bc(e,t){let n=e.activeFormattingElements.getElementEntryInScopeWithTagName(t.tagName);return n?e.openElements.contains(n.element)?e.openElements.hasInScope(t.tagID)||(n=null):(e.activeFormattingElements.removeEntry(n),n=null):cl(e,t),n}function Fc(e,t){let n=null,r=e.openElements.stackTop;for(;r>=0;r--){const i=e.openElements.items[r];if(i===t.element)break;e._isSpecialElement(i,e.openElements.tagIDs[r])&&(n=i)}return n||(e.openElements.shortenToLength(r<0?0:r),e.activeFormattingElements.removeEntry(t)),n}function Uc(e,t,n){let r=t,i=e.openElements.getCommonAncestor(t);for(let s=0,a=i;a!==n;s++,a=i){i=e.openElements.getCommonAncestor(a);const n=e.activeFormattingElements.getElementEntry(a),o=n&&s>=3;!n||o?(o&&e.activeFormattingElements.removeEntry(n),e.openElements.remove(a)):(a=Hc(e,n),r===t&&(e.activeFormattingElements.bookmark=n),e.treeAdapter.detachNode(r),e.treeAdapter.appendChild(a,r),r=a)}return r}function Hc(e,t){const n=e.treeAdapter.getNamespaceURI(t.element),r=e.treeAdapter.createElement(t.token.tagName,n,t.token.attrs);return e.openElements.replace(t.element,r),t.element=r,r}function Gc(e,t,n){const r=Fo(e.treeAdapter.getTagName(t));if(e._isElementCausesFosterParenting(r))e._fosterParentElement(n);else{const i=e.treeAdapter.getNamespaceURI(t);r===Po.TEMPLATE&&i===vo.HTML&&(t=e.treeAdapter.getTemplateContent(t)),e.treeAdapter.appendChild(t,n)}}function Yc(e,t,n){const r=e.treeAdapter.getNamespaceURI(n.element),{token:i}=n,s=e.treeAdapter.createElement(i.tagName,r,i.attrs);e._adoptNodes(t,s),e.treeAdapter.appendChild(t,s),e.activeFormattingElements.insertElementAfterBookmark(s,i),e.activeFormattingElements.removeEntry(n),e.openElements.remove(n.element),e.openElements.insertAfter(t,s,i.tagID)}function qc(e,t){for(let n=0;n<8;n++){const n=Bc(e,t);if(!n)break;const r=Fc(e,n);if(!r)break;e.activeFormattingElements.bookmark=n;const i=Uc(e,r,n.element),s=e.openElements.getCommonAncestor(n.element);e.treeAdapter.detachNode(i),s&&Gc(e,s,i),Yc(e,r,n)}}function Kc(e,t){e._appendCommentNode(t,e.openElements.currentTmplContentOrNode)}function jc(e,t){if(e.stopped=!0,t.location){const n=e.fragmentContext?0:2;for(let r=e.openElements.stackTop;r>=n;r--)e._setEndLocation(e.openElements.items[r],t);if(!e.fragmentContext&&e.openElements.stackTop>=0){const n=e.openElements.items[0],r=e.treeAdapter.getNodeSourceCodeLocation(n);if(r&&!r.endTag&&(e._setEndLocation(n,t),e.openElements.stackTop>=1)){const n=e.openElements.items[1],r=e.treeAdapter.getNodeSourceCodeLocation(n);r&&!r.endTag&&e._setEndLocation(n,t)}}}}function Vc(e,t){e._err(t,Eo.missingDoctype,!0),e.treeAdapter.setDocumentMode(e.document,Ro.QUIRKS),e.insertionMode=Rc.BEFORE_HTML,e._processToken(t)}function Wc(e,t){e._insertFakeRootElement(),e.insertionMode=Rc.BEFORE_HEAD,e._processToken(t)}function Qc(e,t){e._insertFakeElement(Mo.HEAD,Po.HEAD),e.headElement=e.openElements.current,e.insertionMode=Rc.IN_HEAD,e._processToken(t)}function Xc(e,t){switch(t.tagID){case Po.HTML:ol(e,t);break;case Po.BASE:case Po.BASEFONT:case Po.BGSOUND:case Po.LINK:case Po.META:e._appendElement(t,vo.HTML),t.ackSelfClosing=!0;break;case Po.TITLE:e._switchToTextParsing(t,jo.RCDATA);break;case Po.NOSCRIPT:e.options.scriptingEnabled?e._switchToTextParsing(t,jo.RAWTEXT):(e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_HEAD_NO_SCRIPT);break;case Po.NOFRAMES:case Po.STYLE:e._switchToTextParsing(t,jo.RAWTEXT);break;case Po.SCRIPT:e._switchToTextParsing(t,jo.SCRIPT_DATA);break;case Po.TEMPLATE:e._insertTemplate(t),e.activeFormattingElements.insertMarker(),e.framesetOk=!1,e.insertionMode=Rc.IN_TEMPLATE,e.tmplInsertionModeStack.unshift(Rc.IN_TEMPLATE);break;case Po.HEAD:e._err(t,Eo.misplacedStartTagForHeadElement);break;default:zc(e,t)}}function $c(e,t){e.openElements.tmplCount>0?(e.openElements.generateImpliedEndTagsThoroughly(),e.openElements.currentTagId!==Po.TEMPLATE&&e._err(t,Eo.closingOfElementWithOpenChildElements),e.openElements.popUntilTagNamePopped(Po.TEMPLATE),e.activeFormattingElements.clearToLastMarker(),e.tmplInsertionModeStack.shift(),e._resetInsertionMode()):e._err(t,Eo.endTagWithoutMatchingOpenElement)}function zc(e,t){e.openElements.pop(),e.insertionMode=Rc.AFTER_HEAD,e._processToken(t)}function Jc(e,t){const n=t.type===To.EOF?Eo.openElementsLeftAfterEof:Eo.disallowedContentInNoscriptInHead;e._err(t,n),e.openElements.pop(),e.insertionMode=Rc.IN_HEAD,e._processToken(t)}function Zc(e,t){e._insertFakeElement(Mo.BODY,Po.BODY),e.insertionMode=Rc.IN_BODY,el(e,t)}function el(e,t){switch(t.type){case To.CHARACTER:nl(e,t);break;case To.WHITESPACE_CHARACTER:tl(e,t);break;case To.COMMENT:Kc(e,t);break;case To.START_TAG:ol(e,t);break;case To.END_TAG:ll(e,t);break;case To.EOF:hl(e,t)}}function tl(e,t){e._reconstructActiveFormattingElements(),e._insertCharacters(t)}function nl(e,t){e._reconstructActiveFormattingElements(),e._insertCharacters(t),e.framesetOk=!1}function rl(e,t){e._reconstructActiveFormattingElements(),e._appendElement(t,vo.HTML),e.framesetOk=!1,t.ackSelfClosing=!0}function il(e){const t=_o(e,Do.TYPE);return null!=t&&"hidden"===t.toLowerCase()}function sl(e,t){e._switchToTextParsing(t,jo.RAWTEXT)}function al(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML)}function ol(e,t){switch(t.tagID){case Po.I:case Po.S:case Po.B:case Po.U:case Po.EM:case Po.TT:case Po.BIG:case Po.CODE:case Po.FONT:case Po.SMALL:case Po.STRIKE:case Po.STRONG:!function(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t)}(e,t);break;case Po.A:!function(e,t){const n=e.activeFormattingElements.getElementEntryInScopeWithTagName(Mo.A);n&&(qc(e,t),e.openElements.remove(n.element),e.activeFormattingElements.removeEntry(n)),e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t)}(e,t);break;case Po.H1:case Po.H2:case Po.H3:case Po.H4:case Po.H5:case Po.H6:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),Go(e.openElements.currentTagId)&&e.openElements.pop(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.P:case Po.DL:case Po.OL:case Po.UL:case Po.DIV:case Po.DIR:case Po.NAV:case Po.MAIN:case Po.MENU:case Po.ASIDE:case Po.CENTER:case Po.FIGURE:case Po.FOOTER:case Po.HEADER:case Po.HGROUP:case Po.DIALOG:case Po.DETAILS:case Po.ADDRESS:case Po.ARTICLE:case Po.SECTION:case Po.SUMMARY:case Po.FIELDSET:case Po.BLOCKQUOTE:case Po.FIGCAPTION:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.LI:case Po.DD:case Po.DT:!function(e,t){e.framesetOk=!1;const n=t.tagID;for(let t=e.openElements.stackTop;t>=0;t--){const r=e.openElements.tagIDs[t];if(n===Po.LI&&r===Po.LI||(n===Po.DD||n===Po.DT)&&(r===Po.DD||r===Po.DT)){e.openElements.generateImpliedEndTagsWithExclusion(r),e.openElements.popUntilTagNamePopped(r);break}if(r!==Po.ADDRESS&&r!==Po.DIV&&r!==Po.P&&e._isSpecialElement(e.openElements.items[t],r))break}e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.BR:case Po.IMG:case Po.WBR:case Po.AREA:case Po.EMBED:case Po.KEYGEN:rl(e,t);break;case Po.HR:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._appendElement(t,vo.HTML),e.framesetOk=!1,t.ackSelfClosing=!0}(e,t);break;case Po.RB:case Po.RTC:!function(e,t){e.openElements.hasInScope(Po.RUBY)&&e.openElements.generateImpliedEndTags(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.RT:case Po.RP:!function(e,t){e.openElements.hasInScope(Po.RUBY)&&e.openElements.generateImpliedEndTagsWithExclusion(Po.RTC),e._insertElement(t,vo.HTML)}(e,t);break;case Po.PRE:case Po.LISTING:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),e.skipNextNewLine=!0,e.framesetOk=!1}(e,t);break;case Po.XMP:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._reconstructActiveFormattingElements(),e.framesetOk=!1,e._switchToTextParsing(t,jo.RAWTEXT)}(e,t);break;case Po.SVG:!function(e,t){e._reconstructActiveFormattingElements(),kc(t),vc(t),t.selfClosing?e._appendElement(t,vo.SVG):e._insertElement(t,vo.SVG),t.ackSelfClosing=!0}(e,t);break;case Po.HTML:!function(e,t){0===e.openElements.tmplCount&&e.treeAdapter.adoptAttributes(e.openElements.items[0],t.attrs)}(e,t);break;case Po.BASE:case Po.LINK:case Po.META:case Po.STYLE:case Po.TITLE:case Po.SCRIPT:case Po.BGSOUND:case Po.BASEFONT:case Po.TEMPLATE:Xc(e,t);break;case Po.BODY:!function(e,t){const n=e.openElements.tryPeekProperlyNestedBodyElement();n&&0===e.openElements.tmplCount&&(e.framesetOk=!1,e.treeAdapter.adoptAttributes(n,t.attrs))}(e,t);break;case Po.FORM:!function(e,t){const n=e.openElements.tmplCount>0;e.formElement&&!n||(e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),n||(e.formElement=e.openElements.current))}(e,t);break;case Po.NOBR:!function(e,t){e._reconstructActiveFormattingElements(),e.openElements.hasInScope(Po.NOBR)&&(qc(e,t),e._reconstructActiveFormattingElements()),e._insertElement(t,vo.HTML),e.activeFormattingElements.pushElement(e.openElements.current,t)}(e,t);break;case Po.MATH:!function(e,t){e._reconstructActiveFormattingElements(),Lc(t),vc(t),t.selfClosing?e._appendElement(t,vo.MATHML):e._insertElement(t,vo.MATHML),t.ackSelfClosing=!0}(e,t);break;case Po.TABLE:!function(e,t){e.treeAdapter.getDocumentMode(e.document)!==Ro.QUIRKS&&e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),e.framesetOk=!1,e.insertionMode=Rc.IN_TABLE}(e,t);break;case Po.INPUT:!function(e,t){e._reconstructActiveFormattingElements(),e._appendElement(t,vo.HTML),il(t)||(e.framesetOk=!1),t.ackSelfClosing=!0}(e,t);break;case Po.PARAM:case Po.TRACK:case Po.SOURCE:!function(e,t){e._appendElement(t,vo.HTML),t.ackSelfClosing=!0}(e,t);break;case Po.IMAGE:!function(e,t){t.tagName=Mo.IMG,t.tagID=Po.IMG,rl(e,t)}(e,t);break;case Po.BUTTON:!function(e,t){e.openElements.hasInScope(Po.BUTTON)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(Po.BUTTON)),e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.framesetOk=!1}(e,t);break;case Po.APPLET:case Po.OBJECT:case Po.MARQUEE:!function(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.activeFormattingElements.insertMarker(),e.framesetOk=!1}(e,t);break;case Po.IFRAME:!function(e,t){e.framesetOk=!1,e._switchToTextParsing(t,jo.RAWTEXT)}(e,t);break;case Po.SELECT:!function(e,t){e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML),e.framesetOk=!1,e.insertionMode=e.insertionMode===Rc.IN_TABLE||e.insertionMode===Rc.IN_CAPTION||e.insertionMode===Rc.IN_TABLE_BODY||e.insertionMode===Rc.IN_ROW||e.insertionMode===Rc.IN_CELL?Rc.IN_SELECT_IN_TABLE:Rc.IN_SELECT}(e,t);break;case Po.OPTION:case Po.OPTGROUP:!function(e,t){e.openElements.currentTagId===Po.OPTION&&e.openElements.pop(),e._reconstructActiveFormattingElements(),e._insertElement(t,vo.HTML)}(e,t);break;case Po.NOEMBED:sl(e,t);break;case Po.FRAMESET:!function(e,t){const n=e.openElements.tryPeekProperlyNestedBodyElement();e.framesetOk&&n&&(e.treeAdapter.detachNode(n),e.openElements.popAllUpToHtmlElement(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_FRAMESET)}(e,t);break;case Po.TEXTAREA:!function(e,t){e._insertElement(t,vo.HTML),e.skipNextNewLine=!0,e.tokenizer.state=jo.RCDATA,e.originalInsertionMode=e.insertionMode,e.framesetOk=!1,e.insertionMode=Rc.TEXT}(e,t);break;case Po.NOSCRIPT:e.options.scriptingEnabled?sl(e,t):al(e,t);break;case Po.PLAINTEXT:!function(e,t){e.openElements.hasInButtonScope(Po.P)&&e._closePElement(),e._insertElement(t,vo.HTML),e.tokenizer.state=jo.PLAINTEXT}(e,t);break;case Po.COL:case Po.TH:case Po.TD:case Po.TR:case Po.HEAD:case Po.FRAME:case Po.TBODY:case Po.TFOOT:case Po.THEAD:case Po.CAPTION:case Po.COLGROUP:break;default:al(e,t)}}function cl(e,t){const n=t.tagName,r=t.tagID;for(let t=e.openElements.stackTop;t>0;t--){const i=e.openElements.items[t],s=e.openElements.tagIDs[t];if(r===s&&(r!==Po.UNKNOWN||e.treeAdapter.getTagName(i)===n)){e.openElements.generateImpliedEndTagsWithExclusion(r),e.openElements.stackTop>=t&&e.openElements.shortenToLength(t);break}if(e._isSpecialElement(i,s))break}}function ll(e,t){switch(t.tagID){case Po.A:case Po.B:case Po.I:case Po.S:case Po.U:case Po.EM:case Po.TT:case Po.BIG:case Po.CODE:case Po.FONT:case Po.NOBR:case Po.SMALL:case Po.STRIKE:case Po.STRONG:qc(e,t);break;case Po.P:!function(e){e.openElements.hasInButtonScope(Po.P)||e._insertFakeElement(Mo.P,Po.P),e._closePElement()}(e);break;case Po.DL:case Po.UL:case Po.OL:case Po.DIR:case Po.DIV:case Po.NAV:case Po.PRE:case Po.MAIN:case Po.MENU:case Po.ASIDE:case Po.BUTTON:case Po.CENTER:case Po.FIGURE:case Po.FOOTER:case Po.HEADER:case Po.HGROUP:case Po.DIALOG:case Po.ADDRESS:case Po.ARTICLE:case Po.DETAILS:case Po.SECTION:case Po.SUMMARY:case Po.LISTING:case Po.FIELDSET:case Po.BLOCKQUOTE:case Po.FIGCAPTION:!function(e,t){const n=t.tagID;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(n))}(e,t);break;case Po.LI:!function(e){e.openElements.hasInListItemScope(Po.LI)&&(e.openElements.generateImpliedEndTagsWithExclusion(Po.LI),e.openElements.popUntilTagNamePopped(Po.LI))}(e);break;case Po.DD:case Po.DT:!function(e,t){const n=t.tagID;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTagsWithExclusion(n),e.openElements.popUntilTagNamePopped(n))}(e,t);break;case Po.H1:case Po.H2:case Po.H3:case Po.H4:case Po.H5:case Po.H6:!function(e){e.openElements.hasNumberedHeaderInScope()&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilNumberedHeaderPopped())}(e);break;case Po.BR:!function(e){e._reconstructActiveFormattingElements(),e._insertFakeElement(Mo.BR,Po.BR),e.openElements.pop(),e.framesetOk=!1}(e);break;case Po.BODY:!function(e,t){if(e.openElements.hasInScope(Po.BODY)&&(e.insertionMode=Rc.AFTER_BODY,e.options.sourceCodeLocationInfo)){const n=e.openElements.tryPeekProperlyNestedBodyElement();n&&e._setEndLocation(n,t)}}(e,t);break;case Po.HTML:!function(e,t){e.openElements.hasInScope(Po.BODY)&&(e.insertionMode=Rc.AFTER_BODY,Ll(e,t))}(e,t);break;case Po.FORM:!function(e){const t=e.openElements.tmplCount>0,{formElement:n}=e;t||(e.formElement=null),(n||t)&&e.openElements.hasInScope(Po.FORM)&&(e.openElements.generateImpliedEndTags(),t?e.openElements.popUntilTagNamePopped(Po.FORM):n&&e.openElements.remove(n))}(e);break;case Po.APPLET:case Po.OBJECT:case Po.MARQUEE:!function(e,t){const n=t.tagID;e.openElements.hasInScope(n)&&(e.openElements.generateImpliedEndTags(),e.openElements.popUntilTagNamePopped(n),e.activeFormattingElements.clearToLastMarker())}(e,t);break;case Po.TEMPLATE:$c(e,t);break;default:cl(e,t)}}function hl(e,t){e.tmplInsertionModeStack.length>0?yl(e,t):jc(e,t)}function ul(e,t){if(Pc.has(e.openElements.currentTagId))switch(e.pendingCharacterTokens.length=0,e.hasNonWhitespacePendingCharacterToken=!1,e.originalInsertionMode=e.insertionMode,e.insertionMode=Rc.IN_TABLE_TEXT,t.type){case To.CHARACTER:ml(e,t);break;case To.WHITESPACE_CHARACTER:El(e,t)}else dl(e,t)}function pl(e,t){switch(t.tagID){case Po.TD:case Po.TH:case Po.TR:!function(e,t){e.openElements.clearBackToTableContext(),e._insertFakeElement(Mo.TBODY,Po.TBODY),e.insertionMode=Rc.IN_TABLE_BODY,Nl(e,t)}(e,t);break;case Po.STYLE:case Po.SCRIPT:case Po.TEMPLATE:Xc(e,t);break;case Po.COL:!function(e,t){e.openElements.clearBackToTableContext(),e._insertFakeElement(Mo.COLGROUP,Po.COLGROUP),e.insertionMode=Rc.IN_COLUMN_GROUP,Al(e,t)}(e,t);break;case Po.FORM:!function(e,t){e.formElement||0!==e.openElements.tmplCount||(e._insertElement(t,vo.HTML),e.formElement=e.openElements.current,e.openElements.pop())}(e,t);break;case Po.TABLE:!function(e,t){e.openElements.hasInTableScope(Po.TABLE)&&(e.openElements.popUntilTagNamePopped(Po.TABLE),e._resetInsertionMode(),e._processStartTag(t))}(e,t);break;case Po.TBODY:case Po.TFOOT:case Po.THEAD:!function(e,t){e.openElements.clearBackToTableContext(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_TABLE_BODY}(e,t);break;case Po.INPUT:!function(e,t){il(t)?e._appendElement(t,vo.HTML):dl(e,t),t.ackSelfClosing=!0}(e,t);break;case Po.CAPTION:!function(e,t){e.openElements.clearBackToTableContext(),e.activeFormattingElements.insertMarker(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_CAPTION}(e,t);break;case Po.COLGROUP:!function(e,t){e.openElements.clearBackToTableContext(),e._insertElement(t,vo.HTML),e.insertionMode=Rc.IN_COLUMN_GROUP}(e,t);break;default:dl(e,t)}}function fl(e,t){switch(t.tagID){case Po.TABLE:e.openElements.hasInTableScope(Po.TABLE)&&(e.openElements.popUntilTagNamePopped(Po.TABLE),e._resetInsertionMode());break;case Po.TEMPLATE:$c(e,t);break;case Po.BODY:case Po.CAPTION:case Po.COL:case Po.COLGROUP:case Po.HTML:case Po.TBODY:case Po.TD:case Po.TFOOT:case Po.TH:case Po.THEAD:case Po.TR:break;default:dl(e,t)}}function dl(e,t){const n=e.fosterParentingEnabled;e.fosterParentingEnabled=!0,el(e,t),e.fosterParentingEnabled=n}function El(e,t){e.pendingCharacterTokens.push(t)}function ml(e,t){e.pendingCharacterTokens.push(t),e.hasNonWhitespacePendingCharacterToken=!0}function Tl(e,t){let n=0;if(e.hasNonWhitespacePendingCharacterToken)for(;n0&&e.openElements.currentTagId===Po.OPTION&&e.openElements.tagIDs[e.openElements.stackTop-1]===Po.OPTGROUP&&e.openElements.pop(),e.openElements.currentTagId===Po.OPTGROUP&&e.openElements.pop();break;case Po.OPTION:e.openElements.currentTagId===Po.OPTION&&e.openElements.pop();break;case Po.SELECT:e.openElements.hasInSelectScope(Po.SELECT)&&(e.openElements.popUntilTagNamePopped(Po.SELECT),e._resetInsertionMode());break;case Po.TEMPLATE:$c(e,t)}}function yl(e,t){e.openElements.tmplCount>0?(e.openElements.popUntilTagNamePopped(Po.TEMPLATE),e.activeFormattingElements.clearToLastMarker(),e.tmplInsertionModeStack.shift(),e._resetInsertionMode(),e.onEof(t)):jc(e,t)}function Ll(e,t){var n;if(t.tagID===Po.HTML){if(e.fragmentContext||(e.insertionMode=Rc.AFTER_AFTER_BODY),e.options.sourceCodeLocationInfo&&e.openElements.tagIDs[0]===Po.HTML){e._setEndLocation(e.openElements.items[0],t);const r=e.openElements.items[1];r&&!(null===(n=e.treeAdapter.getNodeSourceCodeLocation(r))||void 0===n?void 0:n.endTag)&&e._setEndLocation(r,t)}}else kl(e,t)}function kl(e,t){e.insertionMode=Rc.IN_BODY,el(e,t)}function vl(e,t){e.insertionMode=Rc.IN_BODY,el(e,t)}function Dl(e){for(;e.treeAdapter.getNamespaceURI(e.openElements.current)!==vo.HTML&&!e._isIntegrationPoint(e.openElements.currentTagId,e.openElements.current);)e.openElements.pop()}var Rl=Ke((function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.escapeText=t.escapeAttribute=t.escapeUTF8=t.escape=t.encodeXML=t.getCodePoint=t.xmlReplacer=void 0,t.xmlReplacer=/["&'<>$\x80-\uFFFF]/g;var n=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]);function r(e){for(var r,i="",s=0;null!==(r=t.xmlReplacer.exec(e));){var a=r.index,o=e.charCodeAt(a),c=n.get(o);void 0!==c?(i+=e.substring(s,a)+c,s=a+1):(i+="".concat(e.substring(s,a),"&#x").concat((0,t.getCodePoint)(e,a).toString(16),";"),s=t.xmlReplacer.lastIndex+=Number(55296==(64512&o)))}return i+e.substr(s)}function i(e,t){return function(n){for(var r,i=0,s="";r=e.exec(n);)i!==r.index&&(s+=n.substring(i,r.index)),s+=t.get(r[0].charCodeAt(0)),i=r.index+1;return s+n.substring(i)}}t.getCodePoint=null!=String.prototype.codePointAt?function(e,t){return e.codePointAt(t)}:function(e,t){return 55296==(64512&e.charCodeAt(t))?1024*(e.charCodeAt(t)-55296)+e.charCodeAt(t+1)-56320+65536:e.charCodeAt(t)},t.encodeXML=r,t.escape=r,t.escapeUTF8=i(/[&<>'"]/g,n),t.escapeAttribute=i(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),t.escapeText=i(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]]))}));qe(Rl);var Ml=Rl.escapeText,Pl=Rl.escapeAttribute;Rl.escapeUTF8,Rl.escape,Rl.encodeXML,Rl.getCodePoint,Rl.xmlReplacer;const xl=new Set([Mo.AREA,Mo.BASE,Mo.BASEFONT,Mo.BGSOUND,Mo.BR,Mo.COL,Mo.EMBED,Mo.FRAME,Mo.HR,Mo.IMG,Mo.INPUT,Mo.KEYGEN,Mo.LINK,Mo.META,Mo.PARAM,Mo.SOURCE,Mo.TRACK,Mo.WBR]);const wl={treeAdapter:Ec,scriptingEnabled:!0};function Bl(e,t){return Fl(e,{...wl,...t})}function Fl(e,t){return t.treeAdapter.isElementNode(e)?function(e,t){const n=t.treeAdapter.getTagName(e);return`<${n}${function(e,{treeAdapter:t}){let n="";for(const r of t.getAttrList(e)){if(n+=" ",r.namespace)switch(r.namespace){case vo.XML:n+=`xml:${r.name}`;break;case vo.XMLNS:"xmlns"!==r.name&&(n+="xmlns:"),n+=r.name;break;case vo.XLINK:n+=`xlink:${r.name}`;break;default:n+=`${r.prefix}:${r.name}`}else n+=r.name;n+=`="${Pl(r.value)}"`}return n}(e,t)}>${function(e,t){return t.treeAdapter.isElementNode(e)&&t.treeAdapter.getNamespaceURI(e)===vo.HTML&&xl.has(t.treeAdapter.getTagName(e))}(e,t)?"":`${function(e,t){let n="";const r=t.treeAdapter.isElementNode(e)&&t.treeAdapter.getTagName(e)===Mo.TEMPLATE&&t.treeAdapter.getNamespaceURI(e)===vo.HTML?t.treeAdapter.getTemplateContent(e):e,i=t.treeAdapter.getChildNodes(r);if(i)for(const e of i)n+=Fl(e,t);return n}(e,t)}`}`}(e,t):t.treeAdapter.isTextNode(e)?function(e,t){const{treeAdapter:n}=t,r=n.getTextNodeContent(e),i=n.getParentNode(e),s=i&&n.isElementNode(i)&&n.getTagName(i);return s&&n.getNamespaceURI(i)===vo.HTML&&(a=s,o=t.scriptingEnabled,Yo.has(a)||o&&a===Mo.NOSCRIPT)?r:Ml(r);var a,o}(e,t):t.treeAdapter.isCommentNode(e)?function(e,{treeAdapter:t}){return`\x3c!--${t.getCommentNodeContent(e)}--\x3e`}(e,t):t.treeAdapter.isDocumentTypeNode(e)?function(e,{treeAdapter:t}){return``}(e,t):""}function Ul(e){return new m(e)}function Hl(e){const t=e.includes('"')?"'":'"';return t+e+t}const Gl={isCommentNode:O,isElementNode:I,isTextNode:b,createDocument(){const e=new N([]);return e["x-mode"]=Ro.NO_QUIRKS,e},createDocumentFragment:()=>new N([]),createElement(e,t,n){const r=Object.create(null),i=Object.create(null),s=Object.create(null);for(let e=0;enew T(e),appendChild(e,t){const n=e.children[e.children.length-1];n&&(n.next=t,t.prev=n),e.children.push(t),t.parent=e},insertBefore(e,t,n){const r=e.children.indexOf(n),{prev:i}=n;i&&(i.next=t,t.prev=i),n.prev=t,t.next=n,e.children.splice(r,0,t),t.parent=e},setTemplateContent(e,t){Gl.appendChild(e,t)},getTemplateContent:e=>e.children[0],setDocumentType(e,t,n,r){const i=function(e,t,n){let r="!DOCTYPE ";return e&&(r+=e),t?r+=` PUBLIC ${Hl(t)}`:n&&(r+=" SYSTEM"),n&&(r+=` ${Hl(n)}`),r}(t,n,r);let s=e.children.find((e=>y(e)&&"!doctype"===e.name));s?s.data=null!=i?i:null:(s=new _("!doctype",i),Gl.appendChild(e,s)),s["x-name"]=null!=t?t:void 0,s["x-publicId"]=null!=n?n:void 0,s["x-systemId"]=null!=r?r:void 0},setDocumentMode(e,t){e["x-mode"]=t},getDocumentMode:e=>e["x-mode"],detachNode(e){if(e.parent){const t=e.parent.children.indexOf(e),{prev:n,next:r}=e;e.prev=null,e.next=null,n&&(n.next=r),r&&(r.prev=n),e.parent.children.splice(t,1),e.parent=null}},insertText(e,t){const n=e.children[e.children.length-1];n&&b(n)?n.data+=t:Gl.appendChild(e,Ul(t))},insertTextBefore(e,t,n){const r=e.children[e.children.indexOf(n)-1];r&&b(r)?r.data+=t:Gl.insertBefore(e,Ul(t),n)},adoptAttributes(e,t){for(let n=0;ne.children[0],getChildNodes:e=>e.children,getParentNode:e=>e.parent,getAttrList:e=>e.attributes,getTagName:e=>e.name,getNamespaceURI:e=>e.namespace,getTextNodeContent:e=>e.data,getCommentNodeContent:e=>e.data,getDocumentTypeNodeName(e){var t;return null!==(t=e["x-name"])&&void 0!==t?t:""},getDocumentTypeNodePublicId(e){var t;return null!==(t=e["x-publicId"])&&void 0!==t?t:""},getDocumentTypeNodeSystemId(e){var t;return null!==(t=e["x-systemId"])&&void 0!==t?t:""},isDocumentTypeNode:e=>y(e)&&"!doctype"===e.name,setNodeSourceCodeLocation(e,t){t&&(e.startIndex=t.startOffset,e.endIndex=t.endOffset),e.sourceCodeLocation=t},getNodeSourceCodeLocation:e=>e.sourceCodeLocation,updateNodeSourceCodeLocation(e,t){null!=t.endOffset&&(e.endIndex=t.endOffset),e.sourceCodeLocation={...e.sourceCodeLocation,...t}}};var Yl=function(e,t,n){if(n||2===arguments.length)for(var r,i=0,s=t.length;i=Kl.Zero&&e<=Kl.Nine}!function(e){e[e.Tab=9]="Tab",e[e.NewLine=10]="NewLine",e[e.FormFeed=12]="FormFeed",e[e.CarriageReturn=13]="CarriageReturn",e[e.Space=32]="Space",e[e.ExclamationMark=33]="ExclamationMark",e[e.Num=35]="Num",e[e.Amp=38]="Amp",e[e.SingleQuote=39]="SingleQuote",e[e.DoubleQuote=34]="DoubleQuote",e[e.Dash=45]="Dash",e[e.Slash=47]="Slash",e[e.Zero=48]="Zero",e[e.Nine=57]="Nine",e[e.Semi=59]="Semi",e[e.Lt=60]="Lt",e[e.Eq=61]="Eq",e[e.Gt=62]="Gt",e[e.Questionmark=63]="Questionmark",e[e.UpperA=65]="UpperA",e[e.LowerA=97]="LowerA",e[e.UpperF=70]="UpperF",e[e.LowerF=102]="LowerF",e[e.UpperZ=90]="UpperZ",e[e.LowerZ=122]="LowerZ",e[e.LowerX=120]="LowerX",e[e.OpeningSquareBracket=91]="OpeningSquareBracket"}(Kl||(Kl={})),function(e){e[e.Text=1]="Text",e[e.BeforeTagName=2]="BeforeTagName",e[e.InTagName=3]="InTagName",e[e.InSelfClosingTag=4]="InSelfClosingTag",e[e.BeforeClosingTagName=5]="BeforeClosingTagName",e[e.InClosingTagName=6]="InClosingTagName",e[e.AfterClosingTagName=7]="AfterClosingTagName",e[e.BeforeAttributeName=8]="BeforeAttributeName",e[e.InAttributeName=9]="InAttributeName",e[e.AfterAttributeName=10]="AfterAttributeName",e[e.BeforeAttributeValue=11]="BeforeAttributeValue",e[e.InAttributeValueDq=12]="InAttributeValueDq",e[e.InAttributeValueSq=13]="InAttributeValueSq",e[e.InAttributeValueNq=14]="InAttributeValueNq",e[e.BeforeDeclaration=15]="BeforeDeclaration",e[e.InDeclaration=16]="InDeclaration",e[e.InProcessingInstruction=17]="InProcessingInstruction",e[e.BeforeComment=18]="BeforeComment",e[e.CDATASequence=19]="CDATASequence",e[e.InSpecialComment=20]="InSpecialComment",e[e.InCommentLike=21]="InCommentLike",e[e.BeforeSpecialS=22]="BeforeSpecialS",e[e.SpecialStartSequence=23]="SpecialStartSequence",e[e.InSpecialTag=24]="InSpecialTag",e[e.BeforeEntity=25]="BeforeEntity",e[e.BeforeNumericEntity=26]="BeforeNumericEntity",e[e.InNamedEntity=27]="InNamedEntity",e[e.InNumericEntity=28]="InNumericEntity",e[e.InHexEntity=29]="InHexEntity"}(jl||(jl={})),function(e){e[e.NoValue=0]="NoValue",e[e.Unquoted=1]="Unquoted",e[e.Single=2]="Single",e[e.Double=3]="Double"}(Vl||(Vl={}));const zl={Cdata:new Uint8Array([67,68,65,84,65,91]),CdataEnd:new Uint8Array([93,93,62]),CommentEnd:new Uint8Array([45,45,62]),ScriptEnd:new Uint8Array([60,47,115,99,114,105,112,116]),StyleEnd:new Uint8Array([60,47,115,116,121,108,101]),TitleEnd:new Uint8Array([60,47,116,105,116,108,101])};class Jl{constructor({xmlMode:e=!1,decodeEntities:t=!0},n){this.cbs=n,this.state=jl.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=jl.Text,this.isSpecial=!1,this.running=!0,this.offset=0,this.sequenceIndex=0,this.trieIndex=0,this.trieCurrent=0,this.entityResult=0,this.entityExcess=0,this.xmlMode=e,this.decodeEntities=t,this.entityTrie=e?xo:wo}reset(){this.state=jl.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=jl.Text,this.currentSequence=void 0,this.running=!0,this.offset=0}write(e){this.offset+=this.buffer.length,this.buffer=e,this.parse()}end(){this.running&&this.finish()}pause(){this.running=!1}resume(){this.running=!0,this.indexthis.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=jl.BeforeTagName,this.sectionStart=this.index):this.decodeEntities&&e===Kl.Amp&&(this.state=jl.BeforeEntity)}stateSpecialStartSequence(e){const t=this.sequenceIndex===this.currentSequence.length;if(t?Xl(e):(32|e)===this.currentSequence[this.sequenceIndex]){if(!t)return void this.sequenceIndex++}else this.isSpecial=!1;this.sequenceIndex=0,this.state=jl.InTagName,this.stateInTagName(e)}stateInSpecialTag(e){if(this.sequenceIndex===this.currentSequence.length){if(e===Kl.Gt||Ql(e)){const t=this.index-this.currentSequence.length;if(this.sectionStart=Kl.LowerA&&e<=Kl.LowerZ||e>=Kl.UpperA&&e<=Kl.UpperZ}(e)}startSpecial(e,t){this.isSpecial=!0,this.currentSequence=e,this.sequenceIndex=t,this.state=jl.SpecialStartSequence}stateBeforeTagName(e){if(e===Kl.ExclamationMark)this.state=jl.BeforeDeclaration,this.sectionStart=this.index+1;else if(e===Kl.Questionmark)this.state=jl.InProcessingInstruction,this.sectionStart=this.index+1;else if(this.isTagStartChar(e)){const t=32|e;this.sectionStart=this.index,this.xmlMode||t!==zl.TitleEnd[2]?this.state=this.xmlMode||t!==zl.ScriptEnd[2]?jl.InTagName:jl.BeforeSpecialS:this.startSpecial(zl.TitleEnd,3)}else e===Kl.Slash?this.state=jl.BeforeClosingTagName:(this.state=jl.Text,this.stateText(e))}stateInTagName(e){Xl(e)&&(this.cbs.onopentagname(this.sectionStart,this.index),this.sectionStart=-1,this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateBeforeClosingTagName(e){Ql(e)||(e===Kl.Gt?this.state=jl.Text:(this.state=this.isTagStartChar(e)?jl.InClosingTagName:jl.InSpecialComment,this.sectionStart=this.index))}stateInClosingTagName(e){(e===Kl.Gt||Ql(e))&&(this.cbs.onclosetag(this.sectionStart,this.index),this.sectionStart=-1,this.state=jl.AfterClosingTagName,this.stateAfterClosingTagName(e))}stateAfterClosingTagName(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.state=jl.Text,this.sectionStart=this.index+1)}stateBeforeAttributeName(e){e===Kl.Gt?(this.cbs.onopentagend(this.index),this.isSpecial?(this.state=jl.InSpecialTag,this.sequenceIndex=0):this.state=jl.Text,this.baseState=this.state,this.sectionStart=this.index+1):e===Kl.Slash?this.state=jl.InSelfClosingTag:Ql(e)||(this.state=jl.InAttributeName,this.sectionStart=this.index)}stateInSelfClosingTag(e){e===Kl.Gt?(this.cbs.onselfclosingtag(this.index),this.state=jl.Text,this.baseState=jl.Text,this.sectionStart=this.index+1,this.isSpecial=!1):Ql(e)||(this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e))}stateInAttributeName(e){(e===Kl.Eq||Xl(e))&&(this.cbs.onattribname(this.sectionStart,this.index),this.sectionStart=-1,this.state=jl.AfterAttributeName,this.stateAfterAttributeName(e))}stateAfterAttributeName(e){e===Kl.Eq?this.state=jl.BeforeAttributeValue:e===Kl.Slash||e===Kl.Gt?(this.cbs.onattribend(Vl.NoValue,this.index),this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e)):Ql(e)||(this.cbs.onattribend(Vl.NoValue,this.index),this.state=jl.InAttributeName,this.sectionStart=this.index)}stateBeforeAttributeValue(e){e===Kl.DoubleQuote?(this.state=jl.InAttributeValueDq,this.sectionStart=this.index+1):e===Kl.SingleQuote?(this.state=jl.InAttributeValueSq,this.sectionStart=this.index+1):Ql(e)||(this.sectionStart=this.index,this.state=jl.InAttributeValueNq,this.stateInAttributeValueNoQuotes(e))}handleInAttributeValue(e,t){e===t||!this.decodeEntities&&this.fastForwardTo(t)?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(t===Kl.DoubleQuote?Vl.Double:Vl.Single,this.index),this.state=jl.BeforeAttributeName):this.decodeEntities&&e===Kl.Amp&&(this.baseState=this.state,this.state=jl.BeforeEntity)}stateInAttributeValueDoubleQuotes(e){this.handleInAttributeValue(e,Kl.DoubleQuote)}stateInAttributeValueSingleQuotes(e){this.handleInAttributeValue(e,Kl.SingleQuote)}stateInAttributeValueNoQuotes(e){Ql(e)||e===Kl.Gt?(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=-1,this.cbs.onattribend(Vl.Unquoted,this.index),this.state=jl.BeforeAttributeName,this.stateBeforeAttributeName(e)):this.decodeEntities&&e===Kl.Amp&&(this.baseState=this.state,this.state=jl.BeforeEntity)}stateBeforeDeclaration(e){e===Kl.OpeningSquareBracket?(this.state=jl.CDATASequence,this.sequenceIndex=0):this.state=e===Kl.Dash?jl.BeforeComment:jl.InDeclaration}stateInDeclaration(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.cbs.ondeclaration(this.sectionStart,this.index),this.state=jl.Text,this.sectionStart=this.index+1)}stateInProcessingInstruction(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.cbs.onprocessinginstruction(this.sectionStart,this.index),this.state=jl.Text,this.sectionStart=this.index+1)}stateBeforeComment(e){e===Kl.Dash?(this.state=jl.InCommentLike,this.currentSequence=zl.CommentEnd,this.sequenceIndex=2,this.sectionStart=this.index+1):this.state=jl.InDeclaration}stateInSpecialComment(e){(e===Kl.Gt||this.fastForwardTo(Kl.Gt))&&(this.cbs.oncomment(this.sectionStart,this.index,0),this.state=jl.Text,this.sectionStart=this.index+1)}stateBeforeSpecialS(e){const t=32|e;t===zl.ScriptEnd[3]?this.startSpecial(zl.ScriptEnd,4):t===zl.StyleEnd[3]?this.startSpecial(zl.StyleEnd,4):(this.state=jl.InTagName,this.stateInTagName(e))}stateBeforeEntity(e){this.entityExcess=1,this.entityResult=0,e===Kl.Num?this.state=jl.BeforeNumericEntity:e===Kl.Amp||(this.trieIndex=0,this.trieCurrent=this.entityTrie[0],this.state=jl.InNamedEntity,this.stateInNamedEntity(e))}stateInNamedEntity(e){if(this.entityExcess+=1,this.trieIndex=Oo(this.entityTrie,this.trieCurrent,this.trieIndex+1,e),this.trieIndex<0)return this.emitNamedEntity(),void this.index--;this.trieCurrent=this.entityTrie[this.trieIndex];const t=this.trieCurrent&yo.VALUE_LENGTH;if(t){const n=(t>>14)-1;if(this.allowLegacyEntity()||e===Kl.Semi){const e=this.index-this.entityExcess+1;e>this.sectionStart&&this.emitPartial(this.sectionStart,e),this.entityResult=this.trieIndex,this.trieIndex+=n,this.entityExcess=0,this.sectionStart=this.index+1,0===n&&this.emitNamedEntity()}else this.trieIndex+=n}}emitNamedEntity(){if(this.state=this.baseState,0===this.entityResult)return;switch((this.entityTrie[this.entityResult]&yo.VALUE_LENGTH)>>14){case 1:this.emitCodePoint(this.entityTrie[this.entityResult]&~yo.VALUE_LENGTH);break;case 2:this.emitCodePoint(this.entityTrie[this.entityResult+1]);break;case 3:this.emitCodePoint(this.entityTrie[this.entityResult+1]),this.emitCodePoint(this.entityTrie[this.entityResult+2])}}stateBeforeNumericEntity(e){(32|e)===Kl.LowerX?(this.entityExcess++,this.state=jl.InHexEntity):(this.state=jl.InNumericEntity,this.stateInNumericEntity(e))}emitNumericEntity(e){const t=this.index-this.entityExcess-1;t+2+Number(this.state===jl.InHexEntity)!==this.index&&(t>this.sectionStart&&this.emitPartial(this.sectionStart,t),this.sectionStart=this.index+Number(e),this.emitCodePoint(ko(this.entityResult))),this.state=this.baseState}stateInNumericEntity(e){e===Kl.Semi?this.emitNumericEntity(!0):$l(e)?(this.entityResult=10*this.entityResult+(e-Kl.Zero),this.entityExcess++):(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--)}stateInHexEntity(e){e===Kl.Semi?this.emitNumericEntity(!0):$l(e)?(this.entityResult=16*this.entityResult+(e-Kl.Zero),this.entityExcess++):!function(e){return e>=Kl.UpperA&&e<=Kl.UpperF||e>=Kl.LowerA&&e<=Kl.LowerF}(e)?(this.allowLegacyEntity()?this.emitNumericEntity(!1):this.state=this.baseState,this.index--):(this.entityResult=16*this.entityResult+((32|e)-Kl.LowerA+10),this.entityExcess++)}allowLegacyEntity(){return!this.xmlMode&&(this.baseState===jl.Text||this.baseState===jl.InSpecialTag)}cleanup(){this.running&&this.sectionStart!==this.index&&(this.state===jl.Text||this.state===jl.InSpecialTag&&0===this.sequenceIndex?(this.cbs.ontext(this.sectionStart,this.index),this.sectionStart=this.index):this.state!==jl.InAttributeValueDq&&this.state!==jl.InAttributeValueSq&&this.state!==jl.InAttributeValueNq||(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=this.index))}shouldContinue(){return this.index0&&s.has(this.stack[this.stack.length-1]);){const e=this.stack.pop();null===(n=(t=this.cbs).onclosetag)||void 0===n||n.call(t,e,!0)}this.isVoidElement(e)||(this.stack.push(e),ah.has(e)?this.foreignContext.push(!0):oh.has(e)&&this.foreignContext.push(!1)),null===(i=(r=this.cbs).onopentagname)||void 0===i||i.call(r,e),this.cbs.onopentag&&(this.attribs={})}endOpenTag(e){var t,n;this.startIndex=this.openTagStart,this.attribs&&(null===(n=(t=this.cbs).onopentag)||void 0===n||n.call(t,this.tagname,this.attribs,e),this.attribs=null),this.cbs.onclosetag&&this.isVoidElement(this.tagname)&&this.cbs.onclosetag(this.tagname,!0),this.tagname=""}onopentagend(e){this.endIndex=e,this.endOpenTag(!1),this.startIndex=e+1}onclosetag(e,t){var n,r,i,s,a,o;this.endIndex=t;let c=this.getSlice(e,t);if(this.lowerCaseTagNames&&(c=c.toLowerCase()),(ah.has(c)||oh.has(c))&&this.foreignContext.pop(),this.isVoidElement(c))this.options.xmlMode||"br"!==c||(null===(r=(n=this.cbs).onopentagname)||void 0===r||r.call(n,"br"),null===(s=(i=this.cbs).onopentag)||void 0===s||s.call(i,"br",{},!0),null===(o=(a=this.cbs).onclosetag)||void 0===o||o.call(a,"br",!1));else{const e=this.stack.lastIndexOf(c);if(-1!==e)if(this.cbs.onclosetag){let t=this.stack.length-e;for(;t--;)this.cbs.onclosetag(this.stack.pop(),0!==t)}else this.stack.length=e;else this.options.xmlMode||"p"!==c||(this.emitOpenTag("p"),this.closeCurrentTag(!0))}this.startIndex=t+1}onselfclosingtag(e){this.endIndex=e,this.options.xmlMode||this.options.recognizeSelfClosing||this.foreignContext[this.foreignContext.length-1]?(this.closeCurrentTag(!1),this.startIndex=e+1):this.onopentagend(e)}closeCurrentTag(e){var t,n;const r=this.tagname;this.endOpenTag(e),this.stack[this.stack.length-1]===r&&(null===(n=(t=this.cbs).onclosetag)||void 0===n||n.call(t,r,!e),this.stack.pop())}onattribname(e,t){this.startIndex=e;const n=this.getSlice(e,t);this.attribname=this.lowerCaseAttributeNames?n.toLowerCase():n}onattribdata(e,t){this.attribvalue+=this.getSlice(e,t)}onattribentity(e){this.attribvalue+=Lo(e)}onattribend(e,t){var n,r;this.endIndex=t,null===(r=(n=this.cbs).onattribute)||void 0===r||r.call(n,this.attribname,this.attribvalue,e===Vl.Double?'"':e===Vl.Single?"'":e===Vl.NoValue?void 0:null),this.attribs&&!Object.prototype.hasOwnProperty.call(this.attribs,this.attribname)&&(this.attribs[this.attribname]=this.attribvalue),this.attribvalue=""}getInstructionName(e){const t=e.search(ch);let n=t<0?e:e.substr(0,t);return this.lowerCaseTagNames&&(n=n.toLowerCase()),n}ondeclaration(e,t){this.endIndex=t;const n=this.getSlice(e,t);if(this.cbs.onprocessinginstruction){const e=this.getInstructionName(n);this.cbs.onprocessinginstruction(`!${e}`,`!${n}`)}this.startIndex=t+1}onprocessinginstruction(e,t){this.endIndex=t;const n=this.getSlice(e,t);if(this.cbs.onprocessinginstruction){const e=this.getInstructionName(n);this.cbs.onprocessinginstruction(`?${e}`,`?${n}`)}this.startIndex=t+1}oncomment(e,t,n){var r,i,s,a;this.endIndex=t,null===(i=(r=this.cbs).oncomment)||void 0===i||i.call(r,this.getSlice(e,t-n)),null===(a=(s=this.cbs).oncommentend)||void 0===a||a.call(s),this.startIndex=t+1}oncdata(e,t,n){var r,i,s,a,o,c,l,h,u,p;this.endIndex=t;const f=this.getSlice(e,t-n);this.options.xmlMode||this.options.recognizeCDATA?(null===(i=(r=this.cbs).oncdatastart)||void 0===i||i.call(r),null===(a=(s=this.cbs).ontext)||void 0===a||a.call(s,f),null===(c=(o=this.cbs).oncdataend)||void 0===c||c.call(o)):(null===(h=(l=this.cbs).oncomment)||void 0===h||h.call(l,`[CDATA[${f}]]`),null===(p=(u=this.cbs).oncommentend)||void 0===p||p.call(u)),this.startIndex=t+1}onend(){var e,t;if(this.cbs.onclosetag){this.endIndex=this.startIndex;for(let e=this.stack.length;e>0;this.cbs.onclosetag(this.stack[--e],!0));}null===(t=(e=this.cbs).onend)||void 0===t||t.call(e)}reset(){var e,t,n,r;null===(t=(e=this.cbs).onreset)||void 0===t||t.call(e),this.tokenizer.reset(),this.tagname="",this.attribname="",this.attribs=null,this.stack.length=0,this.startIndex=0,this.endIndex=0,null===(r=(n=this.cbs).onparserinit)||void 0===r||r.call(n,this),this.buffers.length=0,this.bufferOffset=0,this.writeIndex=0,this.ended=!1}parseComplete(e){this.reset(),this.end(e)}getSlice(e,t){for(;e-this.bufferOffset>=this.buffers[0].length;)this.shiftBuffer();let n=this.buffers[0].slice(e-this.bufferOffset,t-this.bufferOffset);for(;t-this.bufferOffset>this.buffers[0].length;)this.shiftBuffer(),n+=this.buffers[0].slice(0,t-this.bufferOffset);return n}shiftBuffer(){this.bufferOffset+=this.buffers[0].length,this.writeIndex--,this.buffers.shift()}write(e){var t,n;this.ended?null===(n=(t=this.cbs).onerror)||void 0===n||n.call(t,new Error(".write() after done!")):(this.buffers.push(e),this.tokenizer.running&&(this.tokenizer.write(e),this.writeIndex++))}end(e){var t,n;this.ended?null===(n=(t=this.cbs).onerror)||void 0===n||n.call(t,Error(".end() after done!")):(e&&this.write(e),this.ended=!0,this.tokenizer.end())}pause(){this.tokenizer.pause()}resume(){for(this.tokenizer.resume();this.tokenizer.running&&this.writeIndex>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + thisWords[(thisSigBytes + i) >>> 2] |= thatByte << (24 - ((thisSigBytes + i) % 4) * 8); + } + } else { + // Copy one word at a time + for (var j = 0; j < thatSigBytes; j += 4) { + thisWords[(thisSigBytes + j) >>> 2] = thatWords[j >>> 2]; + } + } + this.sigBytes += thatSigBytes; + + // Chainable + return this; + }, + + /** + * Removes insignificant bits. + * + * @example + * + * wordArray.clamp(); + */ + clamp: function () { + // Shortcuts + var words = this.words; + var sigBytes = this.sigBytes; + + // Clamp + words[sigBytes >>> 2] &= 0xffffffff << (32 - (sigBytes % 4) * 8); + words.length = Math.ceil(sigBytes / 4); + }, + + /** + * Creates a copy of this word array. + * + * @return {WordArray} The clone. + * + * @example + * + * var clone = wordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone.words = this.words.slice(0); + + return clone; + }, + + /** + * Creates a word array filled with random bytes. + * + * @param {number} nBytes The number of random bytes to generate. + * + * @return {WordArray} The random word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.lib.WordArray.random(16); + */ + random: function (nBytes) { + var words = []; + + for (var i = 0; i < nBytes; i += 4) { + words.push(cryptoSecureRandomInt()); + } + + return new WordArray.init(words, nBytes); + } + }); + + /** + * Encoder namespace. + */ + var C_enc = C.enc = {}; + + /** + * Hex encoding strategy. + */ + var Hex = C_enc.Hex = { + /** + * Converts a word array to a hex string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The hex string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.enc.Hex.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var hexChars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + hexChars.push((bite >>> 4).toString(16)); + hexChars.push((bite & 0x0f).toString(16)); + } + + return hexChars.join(''); + }, + + /** + * Converts a hex string to a word array. + * + * @param {string} hexStr The hex string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Hex.parse(hexString); + */ + parse: function (hexStr) { + // Shortcut + var hexStrLength = hexStr.length; + + // Convert + var words = []; + for (var i = 0; i < hexStrLength; i += 2) { + words[i >>> 3] |= parseInt(hexStr.substr(i, 2), 16) << (24 - (i % 8) * 4); + } + + return new WordArray.init(words, hexStrLength / 2); + } + }; + + /** + * Latin1 encoding strategy. + */ + var Latin1 = C_enc.Latin1 = { + /** + * Converts a word array to a Latin1 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Latin1 string. + * + * @static + * + * @example + * + * var latin1String = CryptoJS.enc.Latin1.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var latin1Chars = []; + for (var i = 0; i < sigBytes; i++) { + var bite = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + latin1Chars.push(String.fromCharCode(bite)); + } + + return latin1Chars.join(''); + }, + + /** + * Converts a Latin1 string to a word array. + * + * @param {string} latin1Str The Latin1 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Latin1.parse(latin1String); + */ + parse: function (latin1Str) { + // Shortcut + var latin1StrLength = latin1Str.length; + + // Convert + var words = []; + for (var i = 0; i < latin1StrLength; i++) { + words[i >>> 2] |= (latin1Str.charCodeAt(i) & 0xff) << (24 - (i % 4) * 8); + } + + return new WordArray.init(words, latin1StrLength); + } + }; + + /** + * UTF-8 encoding strategy. + */ + var Utf8 = C_enc.Utf8 = { + /** + * Converts a word array to a UTF-8 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-8 string. + * + * @static + * + * @example + * + * var utf8String = CryptoJS.enc.Utf8.stringify(wordArray); + */ + stringify: function (wordArray) { + try { + return decodeURIComponent(escape(Latin1.stringify(wordArray))); + } catch (e) { + throw new Error('Malformed UTF-8 data'); + } + }, + + /** + * Converts a UTF-8 string to a word array. + * + * @param {string} utf8Str The UTF-8 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf8.parse(utf8String); + */ + parse: function (utf8Str) { + return Latin1.parse(unescape(encodeURIComponent(utf8Str))); + } + }; + + /** + * Abstract buffered block algorithm template. + * + * The property blockSize must be implemented in a concrete subtype. + * + * @property {number} _minBufferSize The number of blocks that should be kept unprocessed in the buffer. Default: 0 + */ + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm = Base.extend({ + /** + * Resets this block algorithm's data buffer to its initial state. + * + * @example + * + * bufferedBlockAlgorithm.reset(); + */ + reset: function () { + // Initial values + this._data = new WordArray.init(); + this._nDataBytes = 0; + }, + + /** + * Adds new data to this block algorithm's buffer. + * + * @param {WordArray|string} data The data to append. Strings are converted to a WordArray using UTF-8. + * + * @example + * + * bufferedBlockAlgorithm._append('data'); + * bufferedBlockAlgorithm._append(wordArray); + */ + _append: function (data) { + // Convert string to WordArray, else assume WordArray already + if (typeof data == 'string') { + data = Utf8.parse(data); + } + + // Append + this._data.concat(data); + this._nDataBytes += data.sigBytes; + }, + + /** + * Processes available data blocks. + * + * This method invokes _doProcessBlock(offset), which must be implemented by a concrete subtype. + * + * @param {boolean} doFlush Whether all blocks and partial blocks should be processed. + * + * @return {WordArray} The processed data. + * + * @example + * + * var processedData = bufferedBlockAlgorithm._process(); + * var processedData = bufferedBlockAlgorithm._process(!!'flush'); + */ + _process: function (doFlush) { + var processedWords; + + // Shortcuts + var data = this._data; + var dataWords = data.words; + var dataSigBytes = data.sigBytes; + var blockSize = this.blockSize; + var blockSizeBytes = blockSize * 4; + + // Count blocks ready + var nBlocksReady = dataSigBytes / blockSizeBytes; + if (doFlush) { + // Round up to include partial blocks + nBlocksReady = Math.ceil(nBlocksReady); + } else { + // Round down to include only full blocks, + // less the number of blocks that must remain in the buffer + nBlocksReady = Math.max((nBlocksReady | 0) - this._minBufferSize, 0); + } + + // Count words ready + var nWordsReady = nBlocksReady * blockSize; + + // Count bytes ready + var nBytesReady = Math.min(nWordsReady * 4, dataSigBytes); + + // Process blocks + if (nWordsReady) { + for (var offset = 0; offset < nWordsReady; offset += blockSize) { + // Perform concrete-algorithm logic + this._doProcessBlock(dataWords, offset); + } + + // Remove processed words + processedWords = dataWords.splice(0, nWordsReady); + data.sigBytes -= nBytesReady; + } + + // Return processed words + return new WordArray.init(processedWords, nBytesReady); + }, + + /** + * Creates a copy of this object. + * + * @return {Object} The clone. + * + * @example + * + * var clone = bufferedBlockAlgorithm.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + clone._data = this._data.clone(); + + return clone; + }, + + _minBufferSize: 0 + }); + + /** + * Abstract hasher template. + * + * @property {number} blockSize The number of 32-bit words this hasher operates on. Default: 16 (512 bits) + */ + var Hasher = C_lib.Hasher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + */ + cfg: Base.extend(), + + /** + * Initializes a newly created hasher. + * + * @param {Object} cfg (Optional) The configuration options to use for this hash computation. + * + * @example + * + * var hasher = CryptoJS.algo.SHA256.create(); + */ + init: function (cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Set initial values + this.reset(); + }, + + /** + * Resets this hasher to its initial state. + * + * @example + * + * hasher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-hasher logic + this._doReset(); + }, + + /** + * Updates this hasher with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {Hasher} This hasher. + * + * @example + * + * hasher.update('message'); + * hasher.update(wordArray); + */ + update: function (messageUpdate) { + // Append + this._append(messageUpdate); + + // Update the hash + this._process(); + + // Chainable + return this; + }, + + /** + * Finalizes the hash computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The hash. + * + * @example + * + * var hash = hasher.finalize(); + * var hash = hasher.finalize('message'); + * var hash = hasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Final message update + if (messageUpdate) { + this._append(messageUpdate); + } + + // Perform concrete-hasher logic + var hash = this._doFinalize(); + + return hash; + }, + + blockSize: 512/32, + + /** + * Creates a shortcut function to a hasher's object interface. + * + * @param {Hasher} hasher The hasher to create a helper for. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var SHA256 = CryptoJS.lib.Hasher._createHelper(CryptoJS.algo.SHA256); + */ + _createHelper: function (hasher) { + return function (message, cfg) { + return new hasher.init(cfg).finalize(message); + }; + }, + + /** + * Creates a shortcut function to the HMAC's object interface. + * + * @param {Hasher} hasher The hasher to use in this HMAC helper. + * + * @return {Function} The shortcut function. + * + * @static + * + * @example + * + * var HmacSHA256 = CryptoJS.lib.Hasher._createHmacHelper(CryptoJS.algo.SHA256); + */ + _createHmacHelper: function (hasher) { + return function (message, key) { + return new C_algo.HMAC.init(hasher, key).finalize(message); + }; + } + }); + + /** + * Algorithm namespace. + */ + var C_algo = C.algo = {}; + + return C; + }(Math)); + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var X32WordArray = C_lib.WordArray; + + /** + * x64 namespace. + */ + var C_x64 = C.x64 = {}; + + /** + * A 64-bit word. + */ + var X64Word = C_x64.Word = Base.extend({ + /** + * Initializes a newly created 64-bit word. + * + * @param {number} high The high 32 bits. + * @param {number} low The low 32 bits. + * + * @example + * + * var x64Word = CryptoJS.x64.Word.create(0x00010203, 0x04050607); + */ + init: function (high, low) { + this.high = high; + this.low = low; + } + + /** + * Bitwise NOTs this word. + * + * @return {X64Word} A new x64-Word object after negating. + * + * @example + * + * var negated = x64Word.not(); + */ + // not: function () { + // var high = ~this.high; + // var low = ~this.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ANDs this word with the passed word. + * + * @param {X64Word} word The x64-Word to AND with this word. + * + * @return {X64Word} A new x64-Word object after ANDing. + * + * @example + * + * var anded = x64Word.and(anotherX64Word); + */ + // and: function (word) { + // var high = this.high & word.high; + // var low = this.low & word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise ORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to OR with this word. + * + * @return {X64Word} A new x64-Word object after ORing. + * + * @example + * + * var ored = x64Word.or(anotherX64Word); + */ + // or: function (word) { + // var high = this.high | word.high; + // var low = this.low | word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Bitwise XORs this word with the passed word. + * + * @param {X64Word} word The x64-Word to XOR with this word. + * + * @return {X64Word} A new x64-Word object after XORing. + * + * @example + * + * var xored = x64Word.xor(anotherX64Word); + */ + // xor: function (word) { + // var high = this.high ^ word.high; + // var low = this.low ^ word.low; + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the left. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftL(25); + */ + // shiftL: function (n) { + // if (n < 32) { + // var high = (this.high << n) | (this.low >>> (32 - n)); + // var low = this.low << n; + // } else { + // var high = this.low << (n - 32); + // var low = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Shifts this word n bits to the right. + * + * @param {number} n The number of bits to shift. + * + * @return {X64Word} A new x64-Word object after shifting. + * + * @example + * + * var shifted = x64Word.shiftR(7); + */ + // shiftR: function (n) { + // if (n < 32) { + // var low = (this.low >>> n) | (this.high << (32 - n)); + // var high = this.high >>> n; + // } else { + // var low = this.high >>> (n - 32); + // var high = 0; + // } + + // return X64Word.create(high, low); + // }, + + /** + * Rotates this word n bits to the left. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotL(25); + */ + // rotL: function (n) { + // return this.shiftL(n).or(this.shiftR(64 - n)); + // }, + + /** + * Rotates this word n bits to the right. + * + * @param {number} n The number of bits to rotate. + * + * @return {X64Word} A new x64-Word object after rotating. + * + * @example + * + * var rotated = x64Word.rotR(7); + */ + // rotR: function (n) { + // return this.shiftR(n).or(this.shiftL(64 - n)); + // }, + + /** + * Adds this word with the passed word. + * + * @param {X64Word} word The x64-Word to add with this word. + * + * @return {X64Word} A new x64-Word object after adding. + * + * @example + * + * var added = x64Word.add(anotherX64Word); + */ + // add: function (word) { + // var low = (this.low + word.low) | 0; + // var carry = (low >>> 0) < (this.low >>> 0) ? 1 : 0; + // var high = (this.high + word.high + carry) | 0; + + // return X64Word.create(high, low); + // } + }); + + /** + * An array of 64-bit words. + * + * @property {Array} words The array of CryptoJS.x64.Word objects. + * @property {number} sigBytes The number of significant bytes in this word array. + */ + var X64WordArray = C_x64.WordArray = Base.extend({ + /** + * Initializes a newly created word array. + * + * @param {Array} words (Optional) An array of CryptoJS.x64.Word objects. + * @param {number} sigBytes (Optional) The number of significant bytes in the words. + * + * @example + * + * var wordArray = CryptoJS.x64.WordArray.create(); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ]); + * + * var wordArray = CryptoJS.x64.WordArray.create([ + * CryptoJS.x64.Word.create(0x00010203, 0x04050607), + * CryptoJS.x64.Word.create(0x18191a1b, 0x1c1d1e1f) + * ], 10); + */ + init: function (words, sigBytes) { + words = this.words = words || []; + + if (sigBytes != undefined) { + this.sigBytes = sigBytes; + } else { + this.sigBytes = words.length * 8; + } + }, + + /** + * Converts this 64-bit word array to a 32-bit word array. + * + * @return {CryptoJS.lib.WordArray} This word array's data as a 32-bit word array. + * + * @example + * + * var x32WordArray = x64WordArray.toX32(); + */ + toX32: function () { + // Shortcuts + var x64Words = this.words; + var x64WordsLength = x64Words.length; + + // Convert + var x32Words = []; + for (var i = 0; i < x64WordsLength; i++) { + var x64Word = x64Words[i]; + x32Words.push(x64Word.high); + x32Words.push(x64Word.low); + } + + return X32WordArray.create(x32Words, this.sigBytes); + }, + + /** + * Creates a copy of this word array. + * + * @return {X64WordArray} The clone. + * + * @example + * + * var clone = x64WordArray.clone(); + */ + clone: function () { + var clone = Base.clone.call(this); + + // Clone "words" array + var words = clone.words = this.words.slice(0); + + // Clone each X64Word object + var wordsLength = words.length; + for (var i = 0; i < wordsLength; i++) { + words[i] = words[i].clone(); + } + + return clone; + } + }); + }()); + + + (function () { + // Check if typed arrays are supported + if (typeof ArrayBuffer != 'function') { + return; + } + + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + + // Reference original init + var superInit = WordArray.init; + + // Augment WordArray.init to handle typed arrays + var subInit = WordArray.init = function (typedArray) { + // Convert buffers to uint8 + if (typedArray instanceof ArrayBuffer) { + typedArray = new Uint8Array(typedArray); + } + + // Convert other array views to uint8 + if ( + typedArray instanceof Int8Array || + (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray) || + typedArray instanceof Int16Array || + typedArray instanceof Uint16Array || + typedArray instanceof Int32Array || + typedArray instanceof Uint32Array || + typedArray instanceof Float32Array || + typedArray instanceof Float64Array + ) { + typedArray = new Uint8Array(typedArray.buffer, typedArray.byteOffset, typedArray.byteLength); + } + + // Handle Uint8Array + if (typedArray instanceof Uint8Array) { + // Shortcut + var typedArrayByteLength = typedArray.byteLength; + + // Extract bytes + var words = []; + for (var i = 0; i < typedArrayByteLength; i++) { + words[i >>> 2] |= typedArray[i] << (24 - (i % 4) * 8); + } + + // Initialize this word array + superInit.call(this, words, typedArrayByteLength); + } else { + // Else call normal init + superInit.apply(this, arguments); + } + }; + + subInit.prototype = WordArray; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * UTF-16 BE encoding strategy. + */ + var Utf16BE = C_enc.Utf16 = C_enc.Utf16BE = { + /** + * Converts a word array to a UTF-16 BE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 BE string. + * + * @static + * + * @example + * + * var utf16String = CryptoJS.enc.Utf16.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = (words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff; + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 BE string to a word array. + * + * @param {string} utf16Str The UTF-16 BE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16.parse(utf16String); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= utf16Str.charCodeAt(i) << (16 - (i % 2) * 16); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + /** + * UTF-16 LE encoding strategy. + */ + C_enc.Utf16LE = { + /** + * Converts a word array to a UTF-16 LE string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The UTF-16 LE string. + * + * @static + * + * @example + * + * var utf16Str = CryptoJS.enc.Utf16LE.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + + // Convert + var utf16Chars = []; + for (var i = 0; i < sigBytes; i += 2) { + var codePoint = swapEndian((words[i >>> 2] >>> (16 - (i % 4) * 8)) & 0xffff); + utf16Chars.push(String.fromCharCode(codePoint)); + } + + return utf16Chars.join(''); + }, + + /** + * Converts a UTF-16 LE string to a word array. + * + * @param {string} utf16Str The UTF-16 LE string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Utf16LE.parse(utf16Str); + */ + parse: function (utf16Str) { + // Shortcut + var utf16StrLength = utf16Str.length; + + // Convert + var words = []; + for (var i = 0; i < utf16StrLength; i++) { + words[i >>> 1] |= swapEndian(utf16Str.charCodeAt(i) << (16 - (i % 2) * 16)); + } + + return WordArray.create(words, utf16StrLength * 2); + } + }; + + function swapEndian(word) { + return ((word << 8) & 0xff00ff00) | ((word >>> 8) & 0x00ff00ff); + } + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64 encoding strategy. + */ + var Base64 = C_enc.Base64 = { + /** + * Converts a word array to a Base64 string. + * + * @param {WordArray} wordArray The word array. + * + * @return {string} The Base64 string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64.stringify(wordArray); + */ + stringify: function (wordArray) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64 string to a word array. + * + * @param {string} base64Str The Base64 string. + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64.parse(base64String); + */ + parse: function (base64Str) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + var bitsCombined = bits1 | bits2; + words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_enc = C.enc; + + /** + * Base64url encoding strategy. + */ + var Base64url = C_enc.Base64url = { + /** + * Converts a word array to a Base64url string. + * + * @param {WordArray} wordArray The word array. + * + * @param {boolean} urlSafe Whether to use url safe + * + * @return {string} The Base64url string. + * + * @static + * + * @example + * + * var base64String = CryptoJS.enc.Base64url.stringify(wordArray); + */ + stringify: function (wordArray, urlSafe=true) { + // Shortcuts + var words = wordArray.words; + var sigBytes = wordArray.sigBytes; + var map = urlSafe ? this._safe_map : this._map; + + // Clamp excess bits + wordArray.clamp(); + + // Convert + var base64Chars = []; + for (var i = 0; i < sigBytes; i += 3) { + var byte1 = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; + var byte2 = (words[(i + 1) >>> 2] >>> (24 - ((i + 1) % 4) * 8)) & 0xff; + var byte3 = (words[(i + 2) >>> 2] >>> (24 - ((i + 2) % 4) * 8)) & 0xff; + + var triplet = (byte1 << 16) | (byte2 << 8) | byte3; + + for (var j = 0; (j < 4) && (i + j * 0.75 < sigBytes); j++) { + base64Chars.push(map.charAt((triplet >>> (6 * (3 - j))) & 0x3f)); + } + } + + // Add padding + var paddingChar = map.charAt(64); + if (paddingChar) { + while (base64Chars.length % 4) { + base64Chars.push(paddingChar); + } + } + + return base64Chars.join(''); + }, + + /** + * Converts a Base64url string to a word array. + * + * @param {string} base64Str The Base64url string. + * + * @param {boolean} urlSafe Whether to use url safe + * + * @return {WordArray} The word array. + * + * @static + * + * @example + * + * var wordArray = CryptoJS.enc.Base64url.parse(base64String); + */ + parse: function (base64Str, urlSafe=true) { + // Shortcuts + var base64StrLength = base64Str.length; + var map = urlSafe ? this._safe_map : this._map; + var reverseMap = this._reverseMap; + + if (!reverseMap) { + reverseMap = this._reverseMap = []; + for (var j = 0; j < map.length; j++) { + reverseMap[map.charCodeAt(j)] = j; + } + } + + // Ignore padding + var paddingChar = map.charAt(64); + if (paddingChar) { + var paddingIndex = base64Str.indexOf(paddingChar); + if (paddingIndex !== -1) { + base64StrLength = paddingIndex; + } + } + + // Convert + return parseLoop(base64Str, base64StrLength, reverseMap); + + }, + + _map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', + _safe_map: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_', + }; + + function parseLoop(base64Str, base64StrLength, reverseMap) { + var words = []; + var nBytes = 0; + for (var i = 0; i < base64StrLength; i++) { + if (i % 4) { + var bits1 = reverseMap[base64Str.charCodeAt(i - 1)] << ((i % 4) * 2); + var bits2 = reverseMap[base64Str.charCodeAt(i)] >>> (6 - (i % 4) * 2); + var bitsCombined = bits1 | bits2; + words[nBytes >>> 2] |= bitsCombined << (24 - (nBytes % 4) * 8); + nBytes++; + } + } + return WordArray.create(words, nBytes); + } + }()); + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var T = []; + + // Compute constants + (function () { + for (var i = 0; i < 64; i++) { + T[i] = (Math.abs(Math.sin(i + 1)) * 0x100000000) | 0; + } + }()); + + /** + * MD5 hash algorithm. + */ + var MD5 = C_algo.MD5 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + + // Shortcuts + var H = this._hash.words; + + var M_offset_0 = M[offset + 0]; + var M_offset_1 = M[offset + 1]; + var M_offset_2 = M[offset + 2]; + var M_offset_3 = M[offset + 3]; + var M_offset_4 = M[offset + 4]; + var M_offset_5 = M[offset + 5]; + var M_offset_6 = M[offset + 6]; + var M_offset_7 = M[offset + 7]; + var M_offset_8 = M[offset + 8]; + var M_offset_9 = M[offset + 9]; + var M_offset_10 = M[offset + 10]; + var M_offset_11 = M[offset + 11]; + var M_offset_12 = M[offset + 12]; + var M_offset_13 = M[offset + 13]; + var M_offset_14 = M[offset + 14]; + var M_offset_15 = M[offset + 15]; + + // Working varialbes + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + + // Computation + a = FF(a, b, c, d, M_offset_0, 7, T[0]); + d = FF(d, a, b, c, M_offset_1, 12, T[1]); + c = FF(c, d, a, b, M_offset_2, 17, T[2]); + b = FF(b, c, d, a, M_offset_3, 22, T[3]); + a = FF(a, b, c, d, M_offset_4, 7, T[4]); + d = FF(d, a, b, c, M_offset_5, 12, T[5]); + c = FF(c, d, a, b, M_offset_6, 17, T[6]); + b = FF(b, c, d, a, M_offset_7, 22, T[7]); + a = FF(a, b, c, d, M_offset_8, 7, T[8]); + d = FF(d, a, b, c, M_offset_9, 12, T[9]); + c = FF(c, d, a, b, M_offset_10, 17, T[10]); + b = FF(b, c, d, a, M_offset_11, 22, T[11]); + a = FF(a, b, c, d, M_offset_12, 7, T[12]); + d = FF(d, a, b, c, M_offset_13, 12, T[13]); + c = FF(c, d, a, b, M_offset_14, 17, T[14]); + b = FF(b, c, d, a, M_offset_15, 22, T[15]); + + a = GG(a, b, c, d, M_offset_1, 5, T[16]); + d = GG(d, a, b, c, M_offset_6, 9, T[17]); + c = GG(c, d, a, b, M_offset_11, 14, T[18]); + b = GG(b, c, d, a, M_offset_0, 20, T[19]); + a = GG(a, b, c, d, M_offset_5, 5, T[20]); + d = GG(d, a, b, c, M_offset_10, 9, T[21]); + c = GG(c, d, a, b, M_offset_15, 14, T[22]); + b = GG(b, c, d, a, M_offset_4, 20, T[23]); + a = GG(a, b, c, d, M_offset_9, 5, T[24]); + d = GG(d, a, b, c, M_offset_14, 9, T[25]); + c = GG(c, d, a, b, M_offset_3, 14, T[26]); + b = GG(b, c, d, a, M_offset_8, 20, T[27]); + a = GG(a, b, c, d, M_offset_13, 5, T[28]); + d = GG(d, a, b, c, M_offset_2, 9, T[29]); + c = GG(c, d, a, b, M_offset_7, 14, T[30]); + b = GG(b, c, d, a, M_offset_12, 20, T[31]); + + a = HH(a, b, c, d, M_offset_5, 4, T[32]); + d = HH(d, a, b, c, M_offset_8, 11, T[33]); + c = HH(c, d, a, b, M_offset_11, 16, T[34]); + b = HH(b, c, d, a, M_offset_14, 23, T[35]); + a = HH(a, b, c, d, M_offset_1, 4, T[36]); + d = HH(d, a, b, c, M_offset_4, 11, T[37]); + c = HH(c, d, a, b, M_offset_7, 16, T[38]); + b = HH(b, c, d, a, M_offset_10, 23, T[39]); + a = HH(a, b, c, d, M_offset_13, 4, T[40]); + d = HH(d, a, b, c, M_offset_0, 11, T[41]); + c = HH(c, d, a, b, M_offset_3, 16, T[42]); + b = HH(b, c, d, a, M_offset_6, 23, T[43]); + a = HH(a, b, c, d, M_offset_9, 4, T[44]); + d = HH(d, a, b, c, M_offset_12, 11, T[45]); + c = HH(c, d, a, b, M_offset_15, 16, T[46]); + b = HH(b, c, d, a, M_offset_2, 23, T[47]); + + a = II(a, b, c, d, M_offset_0, 6, T[48]); + d = II(d, a, b, c, M_offset_7, 10, T[49]); + c = II(c, d, a, b, M_offset_14, 15, T[50]); + b = II(b, c, d, a, M_offset_5, 21, T[51]); + a = II(a, b, c, d, M_offset_12, 6, T[52]); + d = II(d, a, b, c, M_offset_3, 10, T[53]); + c = II(c, d, a, b, M_offset_10, 15, T[54]); + b = II(b, c, d, a, M_offset_1, 21, T[55]); + a = II(a, b, c, d, M_offset_8, 6, T[56]); + d = II(d, a, b, c, M_offset_15, 10, T[57]); + c = II(c, d, a, b, M_offset_6, 15, T[58]); + b = II(b, c, d, a, M_offset_13, 21, T[59]); + a = II(a, b, c, d, M_offset_4, 6, T[60]); + d = II(d, a, b, c, M_offset_11, 10, T[61]); + c = II(c, d, a, b, M_offset_2, 15, T[62]); + b = II(b, c, d, a, M_offset_9, 21, T[63]); + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + + var nBitsTotalH = Math.floor(nBitsTotal / 0x100000000); + var nBitsTotalL = nBitsTotal; + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = ( + (((nBitsTotalH << 8) | (nBitsTotalH >>> 24)) & 0x00ff00ff) | + (((nBitsTotalH << 24) | (nBitsTotalH >>> 8)) & 0xff00ff00) + ); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotalL << 8) | (nBitsTotalL >>> 24)) & 0x00ff00ff) | + (((nBitsTotalL << 24) | (nBitsTotalL >>> 8)) & 0xff00ff00) + ); + + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 4; i++) { + // Shortcut + var H_i = H[i]; + + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + function FF(a, b, c, d, x, s, t) { + var n = a + ((b & c) | (~b & d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function GG(a, b, c, d, x, s, t) { + var n = a + ((b & d) | (c & ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function HH(a, b, c, d, x, s, t) { + var n = a + (b ^ c ^ d) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + function II(a, b, c, d, x, s, t) { + var n = a + (c ^ (b | ~d)) + x + t; + return ((n << s) | (n >>> (32 - s))) + b; + } + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.MD5('message'); + * var hash = CryptoJS.MD5(wordArray); + */ + C.MD5 = Hasher._createHelper(MD5); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacMD5(message, key); + */ + C.HmacMD5 = Hasher._createHmacHelper(MD5); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Reusable object + var W = []; + + /** + * SHA-1 hash algorithm. + */ + var SHA1 = C_algo.SHA1 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0x67452301, 0xefcdab89, + 0x98badcfe, 0x10325476, + 0xc3d2e1f0 + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + + // Computation + for (var i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var n = W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16]; + W[i] = (n << 1) | (n >>> 31); + } + + var t = ((a << 5) | (a >>> 27)) + e + W[i]; + if (i < 20) { + t += ((b & c) | (~b & d)) + 0x5a827999; + } else if (i < 40) { + t += (b ^ c ^ d) + 0x6ed9eba1; + } else if (i < 60) { + t += ((b & c) | (b & d) | (c & d)) - 0x70e44324; + } else /* if (i < 80) */ { + t += (b ^ c ^ d) - 0x359d3e2a; + } + + e = d; + d = c; + c = (b << 30) | (b >>> 2); + b = a; + a = t; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA1('message'); + * var hash = CryptoJS.SHA1(wordArray); + */ + C.SHA1 = Hasher._createHelper(SHA1); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA1(message, key); + */ + C.HmacSHA1 = Hasher._createHmacHelper(SHA1); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Initialization and round constants tables + var H = []; + var K = []; + + // Compute constants + (function () { + function isPrime(n) { + var sqrtN = Math.sqrt(n); + for (var factor = 2; factor <= sqrtN; factor++) { + if (!(n % factor)) { + return false; + } + } + + return true; + } + + function getFractionalBits(n) { + return ((n - (n | 0)) * 0x100000000) | 0; + } + + var n = 2; + var nPrime = 0; + while (nPrime < 64) { + if (isPrime(n)) { + if (nPrime < 8) { + H[nPrime] = getFractionalBits(Math.pow(n, 1 / 2)); + } + K[nPrime] = getFractionalBits(Math.pow(n, 1 / 3)); + + nPrime++; + } + + n++; + } + }()); + + // Reusable object + var W = []; + + /** + * SHA-256 hash algorithm. + */ + var SHA256 = C_algo.SHA256 = Hasher.extend({ + _doReset: function () { + this._hash = new WordArray.init(H.slice(0)); + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var H = this._hash.words; + + // Working variables + var a = H[0]; + var b = H[1]; + var c = H[2]; + var d = H[3]; + var e = H[4]; + var f = H[5]; + var g = H[6]; + var h = H[7]; + + // Computation + for (var i = 0; i < 64; i++) { + if (i < 16) { + W[i] = M[offset + i] | 0; + } else { + var gamma0x = W[i - 15]; + var gamma0 = ((gamma0x << 25) | (gamma0x >>> 7)) ^ + ((gamma0x << 14) | (gamma0x >>> 18)) ^ + (gamma0x >>> 3); + + var gamma1x = W[i - 2]; + var gamma1 = ((gamma1x << 15) | (gamma1x >>> 17)) ^ + ((gamma1x << 13) | (gamma1x >>> 19)) ^ + (gamma1x >>> 10); + + W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16]; + } + + var ch = (e & f) ^ (~e & g); + var maj = (a & b) ^ (a & c) ^ (b & c); + + var sigma0 = ((a << 30) | (a >>> 2)) ^ ((a << 19) | (a >>> 13)) ^ ((a << 10) | (a >>> 22)); + var sigma1 = ((e << 26) | (e >>> 6)) ^ ((e << 21) | (e >>> 11)) ^ ((e << 7) | (e >>> 25)); + + var t1 = h + sigma1 + ch + K[i] + W[i]; + var t2 = sigma0 + maj; + + h = g; + g = f; + f = e; + e = (d + t1) | 0; + d = c; + c = b; + b = a; + a = (t1 + t2) | 0; + } + + // Intermediate hash value + H[0] = (H[0] + a) | 0; + H[1] = (H[1] + b) | 0; + H[2] = (H[2] + c) | 0; + H[3] = (H[3] + d) | 0; + H[4] = (H[4] + e) | 0; + H[5] = (H[5] + f) | 0; + H[6] = (H[6] + g) | 0; + H[7] = (H[7] + h) | 0; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 15] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Return final computed hash + return this._hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA256('message'); + * var hash = CryptoJS.SHA256(wordArray); + */ + C.SHA256 = Hasher._createHelper(SHA256); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA256(message, key); + */ + C.HmacSHA256 = Hasher._createHmacHelper(SHA256); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA256 = C_algo.SHA256; + + /** + * SHA-224 hash algorithm. + */ + var SHA224 = C_algo.SHA224 = SHA256.extend({ + _doReset: function () { + this._hash = new WordArray.init([ + 0xc1059ed8, 0x367cd507, 0x3070dd17, 0xf70e5939, + 0xffc00b31, 0x68581511, 0x64f98fa7, 0xbefa4fa4 + ]); + }, + + _doFinalize: function () { + var hash = SHA256._doFinalize.call(this); + + hash.sigBytes -= 4; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA224('message'); + * var hash = CryptoJS.SHA224(wordArray); + */ + C.SHA224 = SHA256._createHelper(SHA224); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA224(message, key); + */ + C.HmacSHA224 = SHA256._createHmacHelper(SHA224); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + + function X64Word_create() { + return X64Word.create.apply(X64Word, arguments); + } + + // Constants + var K = [ + X64Word_create(0x428a2f98, 0xd728ae22), X64Word_create(0x71374491, 0x23ef65cd), + X64Word_create(0xb5c0fbcf, 0xec4d3b2f), X64Word_create(0xe9b5dba5, 0x8189dbbc), + X64Word_create(0x3956c25b, 0xf348b538), X64Word_create(0x59f111f1, 0xb605d019), + X64Word_create(0x923f82a4, 0xaf194f9b), X64Word_create(0xab1c5ed5, 0xda6d8118), + X64Word_create(0xd807aa98, 0xa3030242), X64Word_create(0x12835b01, 0x45706fbe), + X64Word_create(0x243185be, 0x4ee4b28c), X64Word_create(0x550c7dc3, 0xd5ffb4e2), + X64Word_create(0x72be5d74, 0xf27b896f), X64Word_create(0x80deb1fe, 0x3b1696b1), + X64Word_create(0x9bdc06a7, 0x25c71235), X64Word_create(0xc19bf174, 0xcf692694), + X64Word_create(0xe49b69c1, 0x9ef14ad2), X64Word_create(0xefbe4786, 0x384f25e3), + X64Word_create(0x0fc19dc6, 0x8b8cd5b5), X64Word_create(0x240ca1cc, 0x77ac9c65), + X64Word_create(0x2de92c6f, 0x592b0275), X64Word_create(0x4a7484aa, 0x6ea6e483), + X64Word_create(0x5cb0a9dc, 0xbd41fbd4), X64Word_create(0x76f988da, 0x831153b5), + X64Word_create(0x983e5152, 0xee66dfab), X64Word_create(0xa831c66d, 0x2db43210), + X64Word_create(0xb00327c8, 0x98fb213f), X64Word_create(0xbf597fc7, 0xbeef0ee4), + X64Word_create(0xc6e00bf3, 0x3da88fc2), X64Word_create(0xd5a79147, 0x930aa725), + X64Word_create(0x06ca6351, 0xe003826f), X64Word_create(0x14292967, 0x0a0e6e70), + X64Word_create(0x27b70a85, 0x46d22ffc), X64Word_create(0x2e1b2138, 0x5c26c926), + X64Word_create(0x4d2c6dfc, 0x5ac42aed), X64Word_create(0x53380d13, 0x9d95b3df), + X64Word_create(0x650a7354, 0x8baf63de), X64Word_create(0x766a0abb, 0x3c77b2a8), + X64Word_create(0x81c2c92e, 0x47edaee6), X64Word_create(0x92722c85, 0x1482353b), + X64Word_create(0xa2bfe8a1, 0x4cf10364), X64Word_create(0xa81a664b, 0xbc423001), + X64Word_create(0xc24b8b70, 0xd0f89791), X64Word_create(0xc76c51a3, 0x0654be30), + X64Word_create(0xd192e819, 0xd6ef5218), X64Word_create(0xd6990624, 0x5565a910), + X64Word_create(0xf40e3585, 0x5771202a), X64Word_create(0x106aa070, 0x32bbd1b8), + X64Word_create(0x19a4c116, 0xb8d2d0c8), X64Word_create(0x1e376c08, 0x5141ab53), + X64Word_create(0x2748774c, 0xdf8eeb99), X64Word_create(0x34b0bcb5, 0xe19b48a8), + X64Word_create(0x391c0cb3, 0xc5c95a63), X64Word_create(0x4ed8aa4a, 0xe3418acb), + X64Word_create(0x5b9cca4f, 0x7763e373), X64Word_create(0x682e6ff3, 0xd6b2b8a3), + X64Word_create(0x748f82ee, 0x5defb2fc), X64Word_create(0x78a5636f, 0x43172f60), + X64Word_create(0x84c87814, 0xa1f0ab72), X64Word_create(0x8cc70208, 0x1a6439ec), + X64Word_create(0x90befffa, 0x23631e28), X64Word_create(0xa4506ceb, 0xde82bde9), + X64Word_create(0xbef9a3f7, 0xb2c67915), X64Word_create(0xc67178f2, 0xe372532b), + X64Word_create(0xca273ece, 0xea26619c), X64Word_create(0xd186b8c7, 0x21c0c207), + X64Word_create(0xeada7dd6, 0xcde0eb1e), X64Word_create(0xf57d4f7f, 0xee6ed178), + X64Word_create(0x06f067aa, 0x72176fba), X64Word_create(0x0a637dc5, 0xa2c898a6), + X64Word_create(0x113f9804, 0xbef90dae), X64Word_create(0x1b710b35, 0x131c471b), + X64Word_create(0x28db77f5, 0x23047d84), X64Word_create(0x32caab7b, 0x40c72493), + X64Word_create(0x3c9ebe0a, 0x15c9bebc), X64Word_create(0x431d67c4, 0x9c100d4c), + X64Word_create(0x4cc5d4be, 0xcb3e42b6), X64Word_create(0x597f299c, 0xfc657e2a), + X64Word_create(0x5fcb6fab, 0x3ad6faec), X64Word_create(0x6c44198c, 0x4a475817) + ]; + + // Reusable objects + var W = []; + (function () { + for (var i = 0; i < 80; i++) { + W[i] = X64Word_create(); + } + }()); + + /** + * SHA-512 hash algorithm. + */ + var SHA512 = C_algo.SHA512 = Hasher.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0x6a09e667, 0xf3bcc908), new X64Word.init(0xbb67ae85, 0x84caa73b), + new X64Word.init(0x3c6ef372, 0xfe94f82b), new X64Word.init(0xa54ff53a, 0x5f1d36f1), + new X64Word.init(0x510e527f, 0xade682d1), new X64Word.init(0x9b05688c, 0x2b3e6c1f), + new X64Word.init(0x1f83d9ab, 0xfb41bd6b), new X64Word.init(0x5be0cd19, 0x137e2179) + ]); + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var H = this._hash.words; + + var H0 = H[0]; + var H1 = H[1]; + var H2 = H[2]; + var H3 = H[3]; + var H4 = H[4]; + var H5 = H[5]; + var H6 = H[6]; + var H7 = H[7]; + + var H0h = H0.high; + var H0l = H0.low; + var H1h = H1.high; + var H1l = H1.low; + var H2h = H2.high; + var H2l = H2.low; + var H3h = H3.high; + var H3l = H3.low; + var H4h = H4.high; + var H4l = H4.low; + var H5h = H5.high; + var H5l = H5.low; + var H6h = H6.high; + var H6l = H6.low; + var H7h = H7.high; + var H7l = H7.low; + + // Working variables + var ah = H0h; + var al = H0l; + var bh = H1h; + var bl = H1l; + var ch = H2h; + var cl = H2l; + var dh = H3h; + var dl = H3l; + var eh = H4h; + var el = H4l; + var fh = H5h; + var fl = H5l; + var gh = H6h; + var gl = H6l; + var hh = H7h; + var hl = H7l; + + // Rounds + for (var i = 0; i < 80; i++) { + var Wil; + var Wih; + + // Shortcut + var Wi = W[i]; + + // Extend message + if (i < 16) { + Wih = Wi.high = M[offset + i * 2] | 0; + Wil = Wi.low = M[offset + i * 2 + 1] | 0; + } else { + // Gamma0 + var gamma0x = W[i - 15]; + var gamma0xh = gamma0x.high; + var gamma0xl = gamma0x.low; + var gamma0h = ((gamma0xh >>> 1) | (gamma0xl << 31)) ^ ((gamma0xh >>> 8) | (gamma0xl << 24)) ^ (gamma0xh >>> 7); + var gamma0l = ((gamma0xl >>> 1) | (gamma0xh << 31)) ^ ((gamma0xl >>> 8) | (gamma0xh << 24)) ^ ((gamma0xl >>> 7) | (gamma0xh << 25)); + + // Gamma1 + var gamma1x = W[i - 2]; + var gamma1xh = gamma1x.high; + var gamma1xl = gamma1x.low; + var gamma1h = ((gamma1xh >>> 19) | (gamma1xl << 13)) ^ ((gamma1xh << 3) | (gamma1xl >>> 29)) ^ (gamma1xh >>> 6); + var gamma1l = ((gamma1xl >>> 19) | (gamma1xh << 13)) ^ ((gamma1xl << 3) | (gamma1xh >>> 29)) ^ ((gamma1xl >>> 6) | (gamma1xh << 26)); + + // W[i] = gamma0 + W[i - 7] + gamma1 + W[i - 16] + var Wi7 = W[i - 7]; + var Wi7h = Wi7.high; + var Wi7l = Wi7.low; + + var Wi16 = W[i - 16]; + var Wi16h = Wi16.high; + var Wi16l = Wi16.low; + + Wil = gamma0l + Wi7l; + Wih = gamma0h + Wi7h + ((Wil >>> 0) < (gamma0l >>> 0) ? 1 : 0); + Wil = Wil + gamma1l; + Wih = Wih + gamma1h + ((Wil >>> 0) < (gamma1l >>> 0) ? 1 : 0); + Wil = Wil + Wi16l; + Wih = Wih + Wi16h + ((Wil >>> 0) < (Wi16l >>> 0) ? 1 : 0); + + Wi.high = Wih; + Wi.low = Wil; + } + + var chh = (eh & fh) ^ (~eh & gh); + var chl = (el & fl) ^ (~el & gl); + var majh = (ah & bh) ^ (ah & ch) ^ (bh & ch); + var majl = (al & bl) ^ (al & cl) ^ (bl & cl); + + var sigma0h = ((ah >>> 28) | (al << 4)) ^ ((ah << 30) | (al >>> 2)) ^ ((ah << 25) | (al >>> 7)); + var sigma0l = ((al >>> 28) | (ah << 4)) ^ ((al << 30) | (ah >>> 2)) ^ ((al << 25) | (ah >>> 7)); + var sigma1h = ((eh >>> 14) | (el << 18)) ^ ((eh >>> 18) | (el << 14)) ^ ((eh << 23) | (el >>> 9)); + var sigma1l = ((el >>> 14) | (eh << 18)) ^ ((el >>> 18) | (eh << 14)) ^ ((el << 23) | (eh >>> 9)); + + // t1 = h + sigma1 + ch + K[i] + W[i] + var Ki = K[i]; + var Kih = Ki.high; + var Kil = Ki.low; + + var t1l = hl + sigma1l; + var t1h = hh + sigma1h + ((t1l >>> 0) < (hl >>> 0) ? 1 : 0); + var t1l = t1l + chl; + var t1h = t1h + chh + ((t1l >>> 0) < (chl >>> 0) ? 1 : 0); + var t1l = t1l + Kil; + var t1h = t1h + Kih + ((t1l >>> 0) < (Kil >>> 0) ? 1 : 0); + var t1l = t1l + Wil; + var t1h = t1h + Wih + ((t1l >>> 0) < (Wil >>> 0) ? 1 : 0); + + // t2 = sigma0 + maj + var t2l = sigma0l + majl; + var t2h = sigma0h + majh + ((t2l >>> 0) < (sigma0l >>> 0) ? 1 : 0); + + // Update working variables + hh = gh; + hl = gl; + gh = fh; + gl = fl; + fh = eh; + fl = el; + el = (dl + t1l) | 0; + eh = (dh + t1h + ((el >>> 0) < (dl >>> 0) ? 1 : 0)) | 0; + dh = ch; + dl = cl; + ch = bh; + cl = bl; + bh = ah; + bl = al; + al = (t1l + t2l) | 0; + ah = (t1h + t2h + ((al >>> 0) < (t1l >>> 0) ? 1 : 0)) | 0; + } + + // Intermediate hash value + H0l = H0.low = (H0l + al); + H0.high = (H0h + ah + ((H0l >>> 0) < (al >>> 0) ? 1 : 0)); + H1l = H1.low = (H1l + bl); + H1.high = (H1h + bh + ((H1l >>> 0) < (bl >>> 0) ? 1 : 0)); + H2l = H2.low = (H2l + cl); + H2.high = (H2h + ch + ((H2l >>> 0) < (cl >>> 0) ? 1 : 0)); + H3l = H3.low = (H3l + dl); + H3.high = (H3h + dh + ((H3l >>> 0) < (dl >>> 0) ? 1 : 0)); + H4l = H4.low = (H4l + el); + H4.high = (H4h + eh + ((H4l >>> 0) < (el >>> 0) ? 1 : 0)); + H5l = H5.low = (H5l + fl); + H5.high = (H5h + fh + ((H5l >>> 0) < (fl >>> 0) ? 1 : 0)); + H6l = H6.low = (H6l + gl); + H6.high = (H6h + gh + ((H6l >>> 0) < (gl >>> 0) ? 1 : 0)); + H7l = H7.low = (H7l + hl); + H7.high = (H7h + hh + ((H7l >>> 0) < (hl >>> 0) ? 1 : 0)); + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 30] = Math.floor(nBitsTotal / 0x100000000); + dataWords[(((nBitsLeft + 128) >>> 10) << 5) + 31] = nBitsTotal; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Convert hash to 32-bit word array before returning + var hash = this._hash.toX32(); + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + }, + + blockSize: 1024/32 + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA512('message'); + * var hash = CryptoJS.SHA512(wordArray); + */ + C.SHA512 = Hasher._createHelper(SHA512); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA512(message, key); + */ + C.HmacSHA512 = Hasher._createHmacHelper(SHA512); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var X64WordArray = C_x64.WordArray; + var C_algo = C.algo; + var SHA512 = C_algo.SHA512; + + /** + * SHA-384 hash algorithm. + */ + var SHA384 = C_algo.SHA384 = SHA512.extend({ + _doReset: function () { + this._hash = new X64WordArray.init([ + new X64Word.init(0xcbbb9d5d, 0xc1059ed8), new X64Word.init(0x629a292a, 0x367cd507), + new X64Word.init(0x9159015a, 0x3070dd17), new X64Word.init(0x152fecd8, 0xf70e5939), + new X64Word.init(0x67332667, 0xffc00b31), new X64Word.init(0x8eb44a87, 0x68581511), + new X64Word.init(0xdb0c2e0d, 0x64f98fa7), new X64Word.init(0x47b5481d, 0xbefa4fa4) + ]); + }, + + _doFinalize: function () { + var hash = SHA512._doFinalize.call(this); + + hash.sigBytes -= 16; + + return hash; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA384('message'); + * var hash = CryptoJS.SHA384(wordArray); + */ + C.SHA384 = SHA512._createHelper(SHA384); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA384(message, key); + */ + C.HmacSHA384 = SHA512._createHmacHelper(SHA384); + }()); + + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_x64 = C.x64; + var X64Word = C_x64.Word; + var C_algo = C.algo; + + // Constants tables + var RHO_OFFSETS = []; + var PI_INDEXES = []; + var ROUND_CONSTANTS = []; + + // Compute Constants + (function () { + // Compute rho offset constants + var x = 1, y = 0; + for (var t = 0; t < 24; t++) { + RHO_OFFSETS[x + 5 * y] = ((t + 1) * (t + 2) / 2) % 64; + + var newX = y % 5; + var newY = (2 * x + 3 * y) % 5; + x = newX; + y = newY; + } + + // Compute pi index constants + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + PI_INDEXES[x + 5 * y] = y + ((2 * x + 3 * y) % 5) * 5; + } + } + + // Compute round constants + var LFSR = 0x01; + for (var i = 0; i < 24; i++) { + var roundConstantMsw = 0; + var roundConstantLsw = 0; + + for (var j = 0; j < 7; j++) { + if (LFSR & 0x01) { + var bitPosition = (1 << j) - 1; + if (bitPosition < 32) { + roundConstantLsw ^= 1 << bitPosition; + } else /* if (bitPosition >= 32) */ { + roundConstantMsw ^= 1 << (bitPosition - 32); + } + } + + // Compute next LFSR + if (LFSR & 0x80) { + // Primitive polynomial over GF(2): x^8 + x^6 + x^5 + x^4 + 1 + LFSR = (LFSR << 1) ^ 0x71; + } else { + LFSR <<= 1; + } + } + + ROUND_CONSTANTS[i] = X64Word.create(roundConstantMsw, roundConstantLsw); + } + }()); + + // Reusable objects for temporary values + var T = []; + (function () { + for (var i = 0; i < 25; i++) { + T[i] = X64Word.create(); + } + }()); + + /** + * SHA-3 hash algorithm. + */ + var SHA3 = C_algo.SHA3 = Hasher.extend({ + /** + * Configuration options. + * + * @property {number} outputLength + * The desired number of bits in the output hash. + * Only values permitted are: 224, 256, 384, 512. + * Default: 512 + */ + cfg: Hasher.cfg.extend({ + outputLength: 512 + }), + + _doReset: function () { + var state = this._state = [] + for (var i = 0; i < 25; i++) { + state[i] = new X64Word.init(); + } + + this.blockSize = (1600 - 2 * this.cfg.outputLength) / 32; + }, + + _doProcessBlock: function (M, offset) { + // Shortcuts + var state = this._state; + var nBlockSizeLanes = this.blockSize / 2; + + // Absorb + for (var i = 0; i < nBlockSizeLanes; i++) { + // Shortcuts + var M2i = M[offset + 2 * i]; + var M2i1 = M[offset + 2 * i + 1]; + + // Swap endian + M2i = ( + (((M2i << 8) | (M2i >>> 24)) & 0x00ff00ff) | + (((M2i << 24) | (M2i >>> 8)) & 0xff00ff00) + ); + M2i1 = ( + (((M2i1 << 8) | (M2i1 >>> 24)) & 0x00ff00ff) | + (((M2i1 << 24) | (M2i1 >>> 8)) & 0xff00ff00) + ); + + // Absorb message into state + var lane = state[i]; + lane.high ^= M2i1; + lane.low ^= M2i; + } + + // Rounds + for (var round = 0; round < 24; round++) { + // Theta + for (var x = 0; x < 5; x++) { + // Mix column lanes + var tMsw = 0, tLsw = 0; + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + tMsw ^= lane.high; + tLsw ^= lane.low; + } + + // Temporary values + var Tx = T[x]; + Tx.high = tMsw; + Tx.low = tLsw; + } + for (var x = 0; x < 5; x++) { + // Shortcuts + var Tx4 = T[(x + 4) % 5]; + var Tx1 = T[(x + 1) % 5]; + var Tx1Msw = Tx1.high; + var Tx1Lsw = Tx1.low; + + // Mix surrounding columns + var tMsw = Tx4.high ^ ((Tx1Msw << 1) | (Tx1Lsw >>> 31)); + var tLsw = Tx4.low ^ ((Tx1Lsw << 1) | (Tx1Msw >>> 31)); + for (var y = 0; y < 5; y++) { + var lane = state[x + 5 * y]; + lane.high ^= tMsw; + lane.low ^= tLsw; + } + } + + // Rho Pi + for (var laneIndex = 1; laneIndex < 25; laneIndex++) { + var tMsw; + var tLsw; + + // Shortcuts + var lane = state[laneIndex]; + var laneMsw = lane.high; + var laneLsw = lane.low; + var rhoOffset = RHO_OFFSETS[laneIndex]; + + // Rotate lanes + if (rhoOffset < 32) { + tMsw = (laneMsw << rhoOffset) | (laneLsw >>> (32 - rhoOffset)); + tLsw = (laneLsw << rhoOffset) | (laneMsw >>> (32 - rhoOffset)); + } else /* if (rhoOffset >= 32) */ { + tMsw = (laneLsw << (rhoOffset - 32)) | (laneMsw >>> (64 - rhoOffset)); + tLsw = (laneMsw << (rhoOffset - 32)) | (laneLsw >>> (64 - rhoOffset)); + } + + // Transpose lanes + var TPiLane = T[PI_INDEXES[laneIndex]]; + TPiLane.high = tMsw; + TPiLane.low = tLsw; + } + + // Rho pi at x = y = 0 + var T0 = T[0]; + var state0 = state[0]; + T0.high = state0.high; + T0.low = state0.low; + + // Chi + for (var x = 0; x < 5; x++) { + for (var y = 0; y < 5; y++) { + // Shortcuts + var laneIndex = x + 5 * y; + var lane = state[laneIndex]; + var TLane = T[laneIndex]; + var Tx1Lane = T[((x + 1) % 5) + 5 * y]; + var Tx2Lane = T[((x + 2) % 5) + 5 * y]; + + // Mix rows + lane.high = TLane.high ^ (~Tx1Lane.high & Tx2Lane.high); + lane.low = TLane.low ^ (~Tx1Lane.low & Tx2Lane.low); + } + } + + // Iota + var lane = state[0]; + var roundConstant = ROUND_CONSTANTS[round]; + lane.high ^= roundConstant.high; + lane.low ^= roundConstant.low; + } + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + var blockSizeBits = this.blockSize * 32; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x1 << (24 - nBitsLeft % 32); + dataWords[((Math.ceil((nBitsLeft + 1) / blockSizeBits) * blockSizeBits) >>> 5) - 1] |= 0x80; + data.sigBytes = dataWords.length * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var state = this._state; + var outputLengthBytes = this.cfg.outputLength / 8; + var outputLengthLanes = outputLengthBytes / 8; + + // Squeeze + var hashWords = []; + for (var i = 0; i < outputLengthLanes; i++) { + // Shortcuts + var lane = state[i]; + var laneMsw = lane.high; + var laneLsw = lane.low; + + // Swap endian + laneMsw = ( + (((laneMsw << 8) | (laneMsw >>> 24)) & 0x00ff00ff) | + (((laneMsw << 24) | (laneMsw >>> 8)) & 0xff00ff00) + ); + laneLsw = ( + (((laneLsw << 8) | (laneLsw >>> 24)) & 0x00ff00ff) | + (((laneLsw << 24) | (laneLsw >>> 8)) & 0xff00ff00) + ); + + // Squeeze state to retrieve hash + hashWords.push(laneLsw); + hashWords.push(laneMsw); + } + + // Return final computed hash + return new WordArray.init(hashWords, outputLengthBytes); + }, + + clone: function () { + var clone = Hasher.clone.call(this); + + var state = clone._state = this._state.slice(0); + for (var i = 0; i < 25; i++) { + state[i] = state[i].clone(); + } + + return clone; + } + }); + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.SHA3('message'); + * var hash = CryptoJS.SHA3(wordArray); + */ + C.SHA3 = Hasher._createHelper(SHA3); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacSHA3(message, key); + */ + C.HmacSHA3 = Hasher._createHmacHelper(SHA3); + }(Math)); + + + /** @preserve + (c) 2012 by Cédric Mesnil. All rights reserved. + + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + (function (Math) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var Hasher = C_lib.Hasher; + var C_algo = C.algo; + + // Constants table + var _zl = WordArray.create([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13]); + var _zr = WordArray.create([ + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11]); + var _sl = WordArray.create([ + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]); + var _sr = WordArray.create([ + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]); + + var _hl = WordArray.create([ 0x00000000, 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xA953FD4E]); + var _hr = WordArray.create([ 0x50A28BE6, 0x5C4DD124, 0x6D703EF3, 0x7A6D76E9, 0x00000000]); + + /** + * RIPEMD160 hash algorithm. + */ + var RIPEMD160 = C_algo.RIPEMD160 = Hasher.extend({ + _doReset: function () { + this._hash = WordArray.create([0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]); + }, + + _doProcessBlock: function (M, offset) { + + // Swap endian + for (var i = 0; i < 16; i++) { + // Shortcuts + var offset_i = offset + i; + var M_offset_i = M[offset_i]; + + // Swap + M[offset_i] = ( + (((M_offset_i << 8) | (M_offset_i >>> 24)) & 0x00ff00ff) | + (((M_offset_i << 24) | (M_offset_i >>> 8)) & 0xff00ff00) + ); + } + // Shortcut + var H = this._hash.words; + var hl = _hl.words; + var hr = _hr.words; + var zl = _zl.words; + var zr = _zr.words; + var sl = _sl.words; + var sr = _sr.words; + + // Working variables + var al, bl, cl, dl, el; + var ar, br, cr, dr, er; + + ar = al = H[0]; + br = bl = H[1]; + cr = cl = H[2]; + dr = dl = H[3]; + er = el = H[4]; + // Computation + var t; + for (var i = 0; i < 80; i += 1) { + t = (al + M[offset+zl[i]])|0; + if (i<16){ + t += f1(bl,cl,dl) + hl[0]; + } else if (i<32) { + t += f2(bl,cl,dl) + hl[1]; + } else if (i<48) { + t += f3(bl,cl,dl) + hl[2]; + } else if (i<64) { + t += f4(bl,cl,dl) + hl[3]; + } else {// if (i<80) { + t += f5(bl,cl,dl) + hl[4]; + } + t = t|0; + t = rotl(t,sl[i]); + t = (t+el)|0; + al = el; + el = dl; + dl = rotl(cl, 10); + cl = bl; + bl = t; + + t = (ar + M[offset+zr[i]])|0; + if (i<16){ + t += f5(br,cr,dr) + hr[0]; + } else if (i<32) { + t += f4(br,cr,dr) + hr[1]; + } else if (i<48) { + t += f3(br,cr,dr) + hr[2]; + } else if (i<64) { + t += f2(br,cr,dr) + hr[3]; + } else {// if (i<80) { + t += f1(br,cr,dr) + hr[4]; + } + t = t|0; + t = rotl(t,sr[i]) ; + t = (t+er)|0; + ar = er; + er = dr; + dr = rotl(cr, 10); + cr = br; + br = t; + } + // Intermediate hash value + t = (H[1] + cl + dr)|0; + H[1] = (H[2] + dl + er)|0; + H[2] = (H[3] + el + ar)|0; + H[3] = (H[4] + al + br)|0; + H[4] = (H[0] + bl + cr)|0; + H[0] = t; + }, + + _doFinalize: function () { + // Shortcuts + var data = this._data; + var dataWords = data.words; + + var nBitsTotal = this._nDataBytes * 8; + var nBitsLeft = data.sigBytes * 8; + + // Add padding + dataWords[nBitsLeft >>> 5] |= 0x80 << (24 - nBitsLeft % 32); + dataWords[(((nBitsLeft + 64) >>> 9) << 4) + 14] = ( + (((nBitsTotal << 8) | (nBitsTotal >>> 24)) & 0x00ff00ff) | + (((nBitsTotal << 24) | (nBitsTotal >>> 8)) & 0xff00ff00) + ); + data.sigBytes = (dataWords.length + 1) * 4; + + // Hash final blocks + this._process(); + + // Shortcuts + var hash = this._hash; + var H = hash.words; + + // Swap endian + for (var i = 0; i < 5; i++) { + // Shortcut + var H_i = H[i]; + + // Swap + H[i] = (((H_i << 8) | (H_i >>> 24)) & 0x00ff00ff) | + (((H_i << 24) | (H_i >>> 8)) & 0xff00ff00); + } + + // Return final computed hash + return hash; + }, + + clone: function () { + var clone = Hasher.clone.call(this); + clone._hash = this._hash.clone(); + + return clone; + } + }); + + + function f1(x, y, z) { + return ((x) ^ (y) ^ (z)); + + } + + function f2(x, y, z) { + return (((x)&(y)) | ((~x)&(z))); + } + + function f3(x, y, z) { + return (((x) | (~(y))) ^ (z)); + } + + function f4(x, y, z) { + return (((x) & (z)) | ((y)&(~(z)))); + } + + function f5(x, y, z) { + return ((x) ^ ((y) |(~(z)))); + + } + + function rotl(x,n) { + return (x<>>(32-n)); + } + + + /** + * Shortcut function to the hasher's object interface. + * + * @param {WordArray|string} message The message to hash. + * + * @return {WordArray} The hash. + * + * @static + * + * @example + * + * var hash = CryptoJS.RIPEMD160('message'); + * var hash = CryptoJS.RIPEMD160(wordArray); + */ + C.RIPEMD160 = Hasher._createHelper(RIPEMD160); + + /** + * Shortcut function to the HMAC's object interface. + * + * @param {WordArray|string} message The message to hash. + * @param {WordArray|string} key The secret key. + * + * @return {WordArray} The HMAC. + * + * @static + * + * @example + * + * var hmac = CryptoJS.HmacRIPEMD160(message, key); + */ + C.HmacRIPEMD160 = Hasher._createHmacHelper(RIPEMD160); + }(Math)); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var C_algo = C.algo; + + /** + * HMAC algorithm. + */ + var HMAC = C_algo.HMAC = Base.extend({ + /** + * Initializes a newly created HMAC. + * + * @param {Hasher} hasher The hash algorithm to use. + * @param {WordArray|string} key The secret key. + * + * @example + * + * var hmacHasher = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, key); + */ + init: function (hasher, key) { + // Init hasher + hasher = this._hasher = new hasher.init(); + + // Convert string to WordArray, else assume WordArray already + if (typeof key == 'string') { + key = Utf8.parse(key); + } + + // Shortcuts + var hasherBlockSize = hasher.blockSize; + var hasherBlockSizeBytes = hasherBlockSize * 4; + + // Allow arbitrary length keys + if (key.sigBytes > hasherBlockSizeBytes) { + key = hasher.finalize(key); + } + + // Clamp excess bits + key.clamp(); + + // Clone key for inner and outer pads + var oKey = this._oKey = key.clone(); + var iKey = this._iKey = key.clone(); + + // Shortcuts + var oKeyWords = oKey.words; + var iKeyWords = iKey.words; + + // XOR keys with pad constants + for (var i = 0; i < hasherBlockSize; i++) { + oKeyWords[i] ^= 0x5c5c5c5c; + iKeyWords[i] ^= 0x36363636; + } + oKey.sigBytes = iKey.sigBytes = hasherBlockSizeBytes; + + // Set initial values + this.reset(); + }, + + /** + * Resets this HMAC to its initial state. + * + * @example + * + * hmacHasher.reset(); + */ + reset: function () { + // Shortcut + var hasher = this._hasher; + + // Reset + hasher.reset(); + hasher.update(this._iKey); + }, + + /** + * Updates this HMAC with a message. + * + * @param {WordArray|string} messageUpdate The message to append. + * + * @return {HMAC} This HMAC instance. + * + * @example + * + * hmacHasher.update('message'); + * hmacHasher.update(wordArray); + */ + update: function (messageUpdate) { + this._hasher.update(messageUpdate); + + // Chainable + return this; + }, + + /** + * Finalizes the HMAC computation. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} messageUpdate (Optional) A final message update. + * + * @return {WordArray} The HMAC. + * + * @example + * + * var hmac = hmacHasher.finalize(); + * var hmac = hmacHasher.finalize('message'); + * var hmac = hmacHasher.finalize(wordArray); + */ + finalize: function (messageUpdate) { + // Shortcut + var hasher = this._hasher; + + // Compute HMAC + var innerHash = hasher.finalize(messageUpdate); + hasher.reset(); + var hmac = hasher.finalize(this._oKey.clone().concat(innerHash)); + + return hmac; + } + }); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var SHA1 = C_algo.SHA1; + var HMAC = C_algo.HMAC; + + /** + * Password-Based Key Derivation Function 2 algorithm. + */ + var PBKDF2 = C_algo.PBKDF2 = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hasher to use. Default: SHA1 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: SHA1, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.PBKDF2.create(); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.PBKDF2.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + // Shortcut + var cfg = this.cfg; + + // Init HMAC + var hmac = HMAC.create(cfg.hasher, password); + + // Initial values + var derivedKey = WordArray.create(); + var blockIndex = WordArray.create([0x00000001]); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var blockIndexWords = blockIndex.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + var block = hmac.update(salt).finalize(blockIndex); + hmac.reset(); + + // Shortcuts + var blockWords = block.words; + var blockWordsLength = blockWords.length; + + // Iterations + var intermediate = block; + for (var i = 1; i < iterations; i++) { + intermediate = hmac.finalize(intermediate); + hmac.reset(); + + // Shortcut + var intermediateWords = intermediate.words; + + // XOR intermediate with block + for (var j = 0; j < blockWordsLength; j++) { + blockWords[j] ^= intermediateWords[j]; + } + } + + derivedKey.concat(block); + blockIndexWords[0]++; + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Computes the Password-Based Key Derivation Function 2. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.PBKDF2(password, salt); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8 }); + * var key = CryptoJS.PBKDF2(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.PBKDF2 = function (password, salt, cfg) { + return PBKDF2.create(cfg).compute(password, salt); + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var C_algo = C.algo; + var MD5 = C_algo.MD5; + + /** + * This key derivation function is meant to conform with EVP_BytesToKey. + * www.openssl.org/docs/crypto/EVP_BytesToKey.html + */ + var EvpKDF = C_algo.EvpKDF = Base.extend({ + /** + * Configuration options. + * + * @property {number} keySize The key size in words to generate. Default: 4 (128 bits) + * @property {Hasher} hasher The hash algorithm to use. Default: MD5 + * @property {number} iterations The number of iterations to perform. Default: 1 + */ + cfg: Base.extend({ + keySize: 128/32, + hasher: MD5, + iterations: 1 + }), + + /** + * Initializes a newly created key derivation function. + * + * @param {Object} cfg (Optional) The configuration options to use for the derivation. + * + * @example + * + * var kdf = CryptoJS.algo.EvpKDF.create(); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8 }); + * var kdf = CryptoJS.algo.EvpKDF.create({ keySize: 8, iterations: 1000 }); + */ + init: function (cfg) { + this.cfg = this.cfg.extend(cfg); + }, + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * + * @return {WordArray} The derived key. + * + * @example + * + * var key = kdf.compute(password, salt); + */ + compute: function (password, salt) { + var block; + + // Shortcut + var cfg = this.cfg; + + // Init hasher + var hasher = cfg.hasher.create(); + + // Initial values + var derivedKey = WordArray.create(); + + // Shortcuts + var derivedKeyWords = derivedKey.words; + var keySize = cfg.keySize; + var iterations = cfg.iterations; + + // Generate key + while (derivedKeyWords.length < keySize) { + if (block) { + hasher.update(block); + } + block = hasher.update(password).finalize(salt); + hasher.reset(); + + // Iterations + for (var i = 1; i < iterations; i++) { + block = hasher.finalize(block); + hasher.reset(); + } + + derivedKey.concat(block); + } + derivedKey.sigBytes = keySize * 4; + + return derivedKey; + } + }); + + /** + * Derives a key from a password. + * + * @param {WordArray|string} password The password. + * @param {WordArray|string} salt A salt. + * @param {Object} cfg (Optional) The configuration options to use for this computation. + * + * @return {WordArray} The derived key. + * + * @static + * + * @example + * + * var key = CryptoJS.EvpKDF(password, salt); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8 }); + * var key = CryptoJS.EvpKDF(password, salt, { keySize: 8, iterations: 1000 }); + */ + C.EvpKDF = function (password, salt, cfg) { + return EvpKDF.create(cfg).compute(password, salt); + }; + }()); + + + /** + * Cipher core components. + */ + CryptoJS.lib.Cipher || (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var Base = C_lib.Base; + var WordArray = C_lib.WordArray; + var BufferedBlockAlgorithm = C_lib.BufferedBlockAlgorithm; + var C_enc = C.enc; + var Utf8 = C_enc.Utf8; + var Base64 = C_enc.Base64; + var C_algo = C.algo; + var EvpKDF = C_algo.EvpKDF; + + /** + * Abstract base cipher template. + * + * @property {number} keySize This cipher's key size. Default: 4 (128 bits) + * @property {number} ivSize This cipher's IV size. Default: 4 (128 bits) + * @property {number} _ENC_XFORM_MODE A constant representing encryption mode. + * @property {number} _DEC_XFORM_MODE A constant representing decryption mode. + */ + var Cipher = C_lib.Cipher = BufferedBlockAlgorithm.extend({ + /** + * Configuration options. + * + * @property {WordArray} iv The IV to use for this operation. + */ + cfg: Base.extend(), + + /** + * Creates this cipher in encryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createEncryptor(keyWordArray, { iv: ivWordArray }); + */ + createEncryptor: function (key, cfg) { + return this.create(this._ENC_XFORM_MODE, key, cfg); + }, + + /** + * Creates this cipher in decryption mode. + * + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {Cipher} A cipher instance. + * + * @static + * + * @example + * + * var cipher = CryptoJS.algo.AES.createDecryptor(keyWordArray, { iv: ivWordArray }); + */ + createDecryptor: function (key, cfg) { + return this.create(this._DEC_XFORM_MODE, key, cfg); + }, + + /** + * Initializes a newly created cipher. + * + * @param {number} xformMode Either the encryption or decryption transormation mode constant. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @example + * + * var cipher = CryptoJS.algo.AES.create(CryptoJS.algo.AES._ENC_XFORM_MODE, keyWordArray, { iv: ivWordArray }); + */ + init: function (xformMode, key, cfg) { + // Apply config defaults + this.cfg = this.cfg.extend(cfg); + + // Store transform mode and key + this._xformMode = xformMode; + this._key = key; + + // Set initial values + this.reset(); + }, + + /** + * Resets this cipher to its initial state. + * + * @example + * + * cipher.reset(); + */ + reset: function () { + // Reset data buffer + BufferedBlockAlgorithm.reset.call(this); + + // Perform concrete-cipher logic + this._doReset(); + }, + + /** + * Adds data to be encrypted or decrypted. + * + * @param {WordArray|string} dataUpdate The data to encrypt or decrypt. + * + * @return {WordArray} The data after processing. + * + * @example + * + * var encrypted = cipher.process('data'); + * var encrypted = cipher.process(wordArray); + */ + process: function (dataUpdate) { + // Append + this._append(dataUpdate); + + // Process available blocks + return this._process(); + }, + + /** + * Finalizes the encryption or decryption process. + * Note that the finalize operation is effectively a destructive, read-once operation. + * + * @param {WordArray|string} dataUpdate The final data to encrypt or decrypt. + * + * @return {WordArray} The data after final processing. + * + * @example + * + * var encrypted = cipher.finalize(); + * var encrypted = cipher.finalize('data'); + * var encrypted = cipher.finalize(wordArray); + */ + finalize: function (dataUpdate) { + // Final data update + if (dataUpdate) { + this._append(dataUpdate); + } + + // Perform concrete-cipher logic + var finalProcessedData = this._doFinalize(); + + return finalProcessedData; + }, + + keySize: 128/32, + + ivSize: 128/32, + + _ENC_XFORM_MODE: 1, + + _DEC_XFORM_MODE: 2, + + /** + * Creates shortcut functions to a cipher's object interface. + * + * @param {Cipher} cipher The cipher to create a helper for. + * + * @return {Object} An object with encrypt and decrypt shortcut functions. + * + * @static + * + * @example + * + * var AES = CryptoJS.lib.Cipher._createHelper(CryptoJS.algo.AES); + */ + _createHelper: (function () { + function selectCipherStrategy(key) { + if (typeof key == 'string') { + return PasswordBasedCipher; + } else { + return SerializableCipher; + } + } + + return function (cipher) { + return { + encrypt: function (message, key, cfg) { + return selectCipherStrategy(key).encrypt(cipher, message, key, cfg); + }, + + decrypt: function (ciphertext, key, cfg) { + return selectCipherStrategy(key).decrypt(cipher, ciphertext, key, cfg); + } + }; + }; + }()) + }); + + /** + * Abstract base stream cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 1 (32 bits) + */ + var StreamCipher = C_lib.StreamCipher = Cipher.extend({ + _doFinalize: function () { + // Process partial blocks + var finalProcessedBlocks = this._process(!!'flush'); + + return finalProcessedBlocks; + }, + + blockSize: 1 + }); + + /** + * Mode namespace. + */ + var C_mode = C.mode = {}; + + /** + * Abstract base block cipher mode template. + */ + var BlockCipherMode = C_lib.BlockCipherMode = Base.extend({ + /** + * Creates this mode for encryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createEncryptor(cipher, iv.words); + */ + createEncryptor: function (cipher, iv) { + return this.Encryptor.create(cipher, iv); + }, + + /** + * Creates this mode for decryption. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @static + * + * @example + * + * var mode = CryptoJS.mode.CBC.createDecryptor(cipher, iv.words); + */ + createDecryptor: function (cipher, iv) { + return this.Decryptor.create(cipher, iv); + }, + + /** + * Initializes a newly created mode. + * + * @param {Cipher} cipher A block cipher instance. + * @param {Array} iv The IV words. + * + * @example + * + * var mode = CryptoJS.mode.CBC.Encryptor.create(cipher, iv.words); + */ + init: function (cipher, iv) { + this._cipher = cipher; + this._iv = iv; + } + }); + + /** + * Cipher Block Chaining mode. + */ + var CBC = C_mode.CBC = (function () { + /** + * Abstract base CBC mode. + */ + var CBC = BlockCipherMode.extend(); + + /** + * CBC encryptor. + */ + CBC.Encryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // XOR and encrypt + xorBlock.call(this, words, offset, blockSize); + cipher.encryptBlock(words, offset); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + /** + * CBC decryptor. + */ + CBC.Decryptor = CBC.extend({ + /** + * Processes the data block at offset. + * + * @param {Array} words The data words to operate on. + * @param {number} offset The offset where the block starts. + * + * @example + * + * mode.processBlock(data.words, offset); + */ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + // Decrypt and XOR + cipher.decryptBlock(words, offset); + xorBlock.call(this, words, offset, blockSize); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function xorBlock(words, offset, blockSize) { + var block; + + // Shortcut + var iv = this._iv; + + // Choose mixing block + if (iv) { + block = iv; + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + block = this._prevBlock; + } + + // XOR blocks + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= block[i]; + } + } + + return CBC; + }()); + + /** + * Padding namespace. + */ + var C_pad = C.pad = {}; + + /** + * PKCS #5/7 padding strategy. + */ + var Pkcs7 = C_pad.Pkcs7 = { + /** + * Pads data using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to pad. + * @param {number} blockSize The multiple that the data should be padded to. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.pad(wordArray, 4); + */ + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Create padding word + var paddingWord = (nPaddingBytes << 24) | (nPaddingBytes << 16) | (nPaddingBytes << 8) | nPaddingBytes; + + // Create padding + var paddingWords = []; + for (var i = 0; i < nPaddingBytes; i += 4) { + paddingWords.push(paddingWord); + } + var padding = WordArray.create(paddingWords, nPaddingBytes); + + // Add padding + data.concat(padding); + }, + + /** + * Unpads data that had been padded using the algorithm defined in PKCS #5/7. + * + * @param {WordArray} data The data to unpad. + * + * @static + * + * @example + * + * CryptoJS.pad.Pkcs7.unpad(wordArray); + */ + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + /** + * Abstract base block cipher template. + * + * @property {number} blockSize The number of 32-bit words this cipher operates on. Default: 4 (128 bits) + */ + var BlockCipher = C_lib.BlockCipher = Cipher.extend({ + /** + * Configuration options. + * + * @property {Mode} mode The block mode to use. Default: CBC + * @property {Padding} padding The padding strategy to use. Default: Pkcs7 + */ + cfg: Cipher.cfg.extend({ + mode: CBC, + padding: Pkcs7 + }), + + reset: function () { + var modeCreator; + + // Reset cipher + Cipher.reset.call(this); + + // Shortcuts + var cfg = this.cfg; + var iv = cfg.iv; + var mode = cfg.mode; + + // Reset block mode + if (this._xformMode == this._ENC_XFORM_MODE) { + modeCreator = mode.createEncryptor; + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + modeCreator = mode.createDecryptor; + // Keep at least one block in the buffer for unpadding + this._minBufferSize = 1; + } + + if (this._mode && this._mode.__creator == modeCreator) { + this._mode.init(this, iv && iv.words); + } else { + this._mode = modeCreator.call(mode, this, iv && iv.words); + this._mode.__creator = modeCreator; + } + }, + + _doProcessBlock: function (words, offset) { + this._mode.processBlock(words, offset); + }, + + _doFinalize: function () { + var finalProcessedBlocks; + + // Shortcut + var padding = this.cfg.padding; + + // Finalize + if (this._xformMode == this._ENC_XFORM_MODE) { + // Pad data + padding.pad(this._data, this.blockSize); + + // Process final blocks + finalProcessedBlocks = this._process(!!'flush'); + } else /* if (this._xformMode == this._DEC_XFORM_MODE) */ { + // Process final blocks + finalProcessedBlocks = this._process(!!'flush'); + + // Unpad data + padding.unpad(finalProcessedBlocks); + } + + return finalProcessedBlocks; + }, + + blockSize: 128/32 + }); + + /** + * A collection of cipher parameters. + * + * @property {WordArray} ciphertext The raw ciphertext. + * @property {WordArray} key The key to this ciphertext. + * @property {WordArray} iv The IV used in the ciphering operation. + * @property {WordArray} salt The salt used with a key derivation function. + * @property {Cipher} algorithm The cipher algorithm. + * @property {Mode} mode The block mode used in the ciphering operation. + * @property {Padding} padding The padding scheme used in the ciphering operation. + * @property {number} blockSize The block size of the cipher. + * @property {Format} formatter The default formatting strategy to convert this cipher params object to a string. + */ + var CipherParams = C_lib.CipherParams = Base.extend({ + /** + * Initializes a newly created cipher params object. + * + * @param {Object} cipherParams An object with any of the possible cipher parameters. + * + * @example + * + * var cipherParams = CryptoJS.lib.CipherParams.create({ + * ciphertext: ciphertextWordArray, + * key: keyWordArray, + * iv: ivWordArray, + * salt: saltWordArray, + * algorithm: CryptoJS.algo.AES, + * mode: CryptoJS.mode.CBC, + * padding: CryptoJS.pad.PKCS7, + * blockSize: 4, + * formatter: CryptoJS.format.OpenSSL + * }); + */ + init: function (cipherParams) { + this.mixIn(cipherParams); + }, + + /** + * Converts this cipher params object to a string. + * + * @param {Format} formatter (Optional) The formatting strategy to use. + * + * @return {string} The stringified cipher params. + * + * @throws Error If neither the formatter nor the default formatter is set. + * + * @example + * + * var string = cipherParams + ''; + * var string = cipherParams.toString(); + * var string = cipherParams.toString(CryptoJS.format.OpenSSL); + */ + toString: function (formatter) { + return (formatter || this.formatter).stringify(this); + } + }); + + /** + * Format namespace. + */ + var C_format = C.format = {}; + + /** + * OpenSSL formatting strategy. + */ + var OpenSSLFormatter = C_format.OpenSSL = { + /** + * Converts a cipher params object to an OpenSSL-compatible string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The OpenSSL-compatible string. + * + * @static + * + * @example + * + * var openSSLString = CryptoJS.format.OpenSSL.stringify(cipherParams); + */ + stringify: function (cipherParams) { + var wordArray; + + // Shortcuts + var ciphertext = cipherParams.ciphertext; + var salt = cipherParams.salt; + + // Format + if (salt) { + wordArray = WordArray.create([0x53616c74, 0x65645f5f]).concat(salt).concat(ciphertext); + } else { + wordArray = ciphertext; + } + + return wordArray.toString(Base64); + }, + + /** + * Converts an OpenSSL-compatible string to a cipher params object. + * + * @param {string} openSSLStr The OpenSSL-compatible string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.OpenSSL.parse(openSSLString); + */ + parse: function (openSSLStr) { + var salt; + + // Parse base64 + var ciphertext = Base64.parse(openSSLStr); + + // Shortcut + var ciphertextWords = ciphertext.words; + + // Test for salt + if (ciphertextWords[0] == 0x53616c74 && ciphertextWords[1] == 0x65645f5f) { + // Extract salt + salt = WordArray.create(ciphertextWords.slice(2, 4)); + + // Remove salt from ciphertext + ciphertextWords.splice(0, 4); + ciphertext.sigBytes -= 16; + } + + return CipherParams.create({ ciphertext: ciphertext, salt: salt }); + } + }; + + /** + * A cipher wrapper that returns ciphertext as a serializable cipher params object. + */ + var SerializableCipher = C_lib.SerializableCipher = Base.extend({ + /** + * Configuration options. + * + * @property {Formatter} format The formatting strategy to convert cipher param objects to and from a string. Default: OpenSSL + */ + cfg: Base.extend({ + format: OpenSSLFormatter + }), + + /** + * Encrypts a message. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv }); + * var ciphertextParams = CryptoJS.lib.SerializableCipher.encrypt(CryptoJS.algo.AES, message, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Encrypt + var encryptor = cipher.createEncryptor(key, cfg); + var ciphertext = encryptor.finalize(message); + + // Shortcut + var cipherCfg = encryptor.cfg; + + // Create and return serializable cipher params + return CipherParams.create({ + ciphertext: ciphertext, + key: key, + iv: cipherCfg.iv, + algorithm: cipher, + mode: cipherCfg.mode, + padding: cipherCfg.padding, + blockSize: cipher.blockSize, + formatter: cfg.format + }); + }, + + /** + * Decrypts serialized ciphertext. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {WordArray} key The key. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.SerializableCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, key, { iv: iv, format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, key, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Decrypt + var plaintext = cipher.createDecryptor(key, cfg).finalize(ciphertext.ciphertext); + + return plaintext; + }, + + /** + * Converts serialized ciphertext to CipherParams, + * else assumed CipherParams already and returns ciphertext unchanged. + * + * @param {CipherParams|string} ciphertext The ciphertext. + * @param {Formatter} format The formatting strategy to use to parse serialized ciphertext. + * + * @return {CipherParams} The unserialized ciphertext. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.SerializableCipher._parse(ciphertextStringOrParams, format); + */ + _parse: function (ciphertext, format) { + if (typeof ciphertext == 'string') { + return format.parse(ciphertext, this); + } else { + return ciphertext; + } + } + }); + + /** + * Key derivation function namespace. + */ + var C_kdf = C.kdf = {}; + + /** + * OpenSSL key derivation function. + */ + var OpenSSLKdf = C_kdf.OpenSSL = { + /** + * Derives a key and IV from a password. + * + * @param {string} password The password to derive from. + * @param {number} keySize The size in words of the key to generate. + * @param {number} ivSize The size in words of the IV to generate. + * @param {WordArray|string} salt (Optional) A 64-bit salt to use. If omitted, a salt will be generated randomly. + * + * @return {CipherParams} A cipher params object with the key, IV, and salt. + * + * @static + * + * @example + * + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32); + * var derivedParams = CryptoJS.kdf.OpenSSL.execute('Password', 256/32, 128/32, 'saltsalt'); + */ + execute: function (password, keySize, ivSize, salt) { + // Generate random salt + if (!salt) { + salt = WordArray.random(64/8); + } + + // Derive key and IV + var key = EvpKDF.create({ keySize: keySize + ivSize }).compute(password, salt); + + // Separate key and IV + var iv = WordArray.create(key.words.slice(keySize), ivSize * 4); + key.sigBytes = keySize * 4; + + // Return params + return CipherParams.create({ key: key, iv: iv, salt: salt }); + } + }; + + /** + * A serializable cipher wrapper that derives the key from a password, + * and returns ciphertext as a serializable cipher params object. + */ + var PasswordBasedCipher = C_lib.PasswordBasedCipher = SerializableCipher.extend({ + /** + * Configuration options. + * + * @property {KDF} kdf The key derivation function to use to generate a key and IV from a password. Default: OpenSSL + */ + cfg: SerializableCipher.cfg.extend({ + kdf: OpenSSLKdf + }), + + /** + * Encrypts a message using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {WordArray|string} message The message to encrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {CipherParams} A cipher params object. + * + * @static + * + * @example + * + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password'); + * var ciphertextParams = CryptoJS.lib.PasswordBasedCipher.encrypt(CryptoJS.algo.AES, message, 'password', { format: CryptoJS.format.OpenSSL }); + */ + encrypt: function (cipher, message, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Encrypt + var ciphertext = SerializableCipher.encrypt.call(this, cipher, message, derivedParams.key, cfg); + + // Mix in derived params + ciphertext.mixIn(derivedParams); + + return ciphertext; + }, + + /** + * Decrypts serialized ciphertext using a password. + * + * @param {Cipher} cipher The cipher algorithm to use. + * @param {CipherParams|string} ciphertext The ciphertext to decrypt. + * @param {string} password The password. + * @param {Object} cfg (Optional) The configuration options to use for this operation. + * + * @return {WordArray} The plaintext. + * + * @static + * + * @example + * + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, formattedCiphertext, 'password', { format: CryptoJS.format.OpenSSL }); + * var plaintext = CryptoJS.lib.PasswordBasedCipher.decrypt(CryptoJS.algo.AES, ciphertextParams, 'password', { format: CryptoJS.format.OpenSSL }); + */ + decrypt: function (cipher, ciphertext, password, cfg) { + // Apply config defaults + cfg = this.cfg.extend(cfg); + + // Convert string to CipherParams + ciphertext = this._parse(ciphertext, cfg.format); + + // Derive key and other params + var derivedParams = cfg.kdf.execute(password, cipher.keySize, cipher.ivSize, ciphertext.salt); + + // Add IV to config + cfg.iv = derivedParams.iv; + + // Decrypt + var plaintext = SerializableCipher.decrypt.call(this, cipher, ciphertext, derivedParams.key, cfg); + + return plaintext; + } + }); + }()); + + + /** + * Cipher Feedback block mode. + */ + CryptoJS.mode.CFB = (function () { + var CFB = CryptoJS.lib.BlockCipherMode.extend(); + + CFB.Encryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // Remember this block to use with next block + this._prevBlock = words.slice(offset, offset + blockSize); + } + }); + + CFB.Decryptor = CFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher; + var blockSize = cipher.blockSize; + + // Remember this block to use with next block + var thisBlock = words.slice(offset, offset + blockSize); + + generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher); + + // This block becomes the previous block + this._prevBlock = thisBlock; + } + }); + + function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) { + var keystream; + + // Shortcut + var iv = this._iv; + + // Generate keystream + if (iv) { + keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } else { + keystream = this._prevBlock; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + + return CFB; + }()); + + + /** + * Counter block mode. + */ + CryptoJS.mode.CTR = (function () { + var CTR = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = CTR.Encryptor = CTR.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Increment counter + counter[blockSize - 1] = (counter[blockSize - 1] + 1) | 0 + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTR.Decryptor = Encryptor; + + return CTR; + }()); + + + /** @preserve + * Counter block mode compatible with Dr Brian Gladman fileenc.c + * derived from CryptoJS.mode.CTR + * Jan Hruby jhruby.web@gmail.com + */ + CryptoJS.mode.CTRGladman = (function () { + var CTRGladman = CryptoJS.lib.BlockCipherMode.extend(); + + function incWord(word) + { + if (((word >> 24) & 0xff) === 0xff) { //overflow + var b1 = (word >> 16)&0xff; + var b2 = (word >> 8)&0xff; + var b3 = word & 0xff; + + if (b1 === 0xff) // overflow b1 + { + b1 = 0; + if (b2 === 0xff) + { + b2 = 0; + if (b3 === 0xff) + { + b3 = 0; + } + else + { + ++b3; + } + } + else + { + ++b2; + } + } + else + { + ++b1; + } + + word = 0; + word += (b1 << 16); + word += (b2 << 8); + word += b3; + } + else + { + word += (0x01 << 24); + } + return word; + } + + function incCounter(counter) + { + if ((counter[0] = incWord(counter[0])) === 0) + { + // encr_data in fileenc.c from Dr Brian Gladman's counts only with DWORD j < 8 + counter[1] = incWord(counter[1]); + } + return counter; + } + + var Encryptor = CTRGladman.Encryptor = CTRGladman.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var counter = this._counter; + + // Generate keystream + if (iv) { + counter = this._counter = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + + incCounter(counter); + + var keystream = counter.slice(0); + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + CTRGladman.Decryptor = Encryptor; + + return CTRGladman; + }()); + + + + + /** + * Output Feedback block mode. + */ + CryptoJS.mode.OFB = (function () { + var OFB = CryptoJS.lib.BlockCipherMode.extend(); + + var Encryptor = OFB.Encryptor = OFB.extend({ + processBlock: function (words, offset) { + // Shortcuts + var cipher = this._cipher + var blockSize = cipher.blockSize; + var iv = this._iv; + var keystream = this._keystream; + + // Generate keystream + if (iv) { + keystream = this._keystream = iv.slice(0); + + // Remove IV for subsequent blocks + this._iv = undefined; + } + cipher.encryptBlock(keystream, 0); + + // Encrypt + for (var i = 0; i < blockSize; i++) { + words[offset + i] ^= keystream[i]; + } + } + }); + + OFB.Decryptor = Encryptor; + + return OFB; + }()); + + + /** + * Electronic Codebook block mode. + */ + CryptoJS.mode.ECB = (function () { + var ECB = CryptoJS.lib.BlockCipherMode.extend(); + + ECB.Encryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.encryptBlock(words, offset); + } + }); + + ECB.Decryptor = ECB.extend({ + processBlock: function (words, offset) { + this._cipher.decryptBlock(words, offset); + } + }); + + return ECB; + }()); + + + /** + * ANSI X.923 padding strategy. + */ + CryptoJS.pad.AnsiX923 = { + pad: function (data, blockSize) { + // Shortcuts + var dataSigBytes = data.sigBytes; + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - dataSigBytes % blockSizeBytes; + + // Compute last byte position + var lastBytePos = dataSigBytes + nPaddingBytes - 1; + + // Pad + data.clamp(); + data.words[lastBytePos >>> 2] |= nPaddingBytes << (24 - (lastBytePos % 4) * 8); + data.sigBytes += nPaddingBytes; + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO 10126 padding strategy. + */ + CryptoJS.pad.Iso10126 = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Count padding bytes + var nPaddingBytes = blockSizeBytes - data.sigBytes % blockSizeBytes; + + // Pad + data.concat(CryptoJS.lib.WordArray.random(nPaddingBytes - 1)). + concat(CryptoJS.lib.WordArray.create([nPaddingBytes << 24], 1)); + }, + + unpad: function (data) { + // Get number of padding bytes from last byte + var nPaddingBytes = data.words[(data.sigBytes - 1) >>> 2] & 0xff; + + // Remove padding + data.sigBytes -= nPaddingBytes; + } + }; + + + /** + * ISO/IEC 9797-1 Padding Method 2. + */ + CryptoJS.pad.Iso97971 = { + pad: function (data, blockSize) { + // Add 0x80 byte + data.concat(CryptoJS.lib.WordArray.create([0x80000000], 1)); + + // Zero pad the rest + CryptoJS.pad.ZeroPadding.pad(data, blockSize); + }, + + unpad: function (data) { + // Remove zero padding + CryptoJS.pad.ZeroPadding.unpad(data); + + // Remove one more byte -- the 0x80 byte + data.sigBytes--; + } + }; + + + /** + * Zero padding strategy. + */ + CryptoJS.pad.ZeroPadding = { + pad: function (data, blockSize) { + // Shortcut + var blockSizeBytes = blockSize * 4; + + // Pad + data.clamp(); + data.sigBytes += blockSizeBytes - ((data.sigBytes % blockSizeBytes) || blockSizeBytes); + }, + + unpad: function (data) { + // Shortcut + var dataWords = data.words; + + // Unpad + var i = data.sigBytes - 1; + for (var i = data.sigBytes - 1; i >= 0; i--) { + if (((dataWords[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff)) { + data.sigBytes = i + 1; + break; + } + } + } + }; + + + /** + * A noop padding strategy. + */ + CryptoJS.pad.NoPadding = { + pad: function () { + }, + + unpad: function () { + } + }; + + + (function (undefined) { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var CipherParams = C_lib.CipherParams; + var C_enc = C.enc; + var Hex = C_enc.Hex; + var C_format = C.format; + + var HexFormatter = C_format.Hex = { + /** + * Converts the ciphertext of a cipher params object to a hexadecimally encoded string. + * + * @param {CipherParams} cipherParams The cipher params object. + * + * @return {string} The hexadecimally encoded string. + * + * @static + * + * @example + * + * var hexString = CryptoJS.format.Hex.stringify(cipherParams); + */ + stringify: function (cipherParams) { + return cipherParams.ciphertext.toString(Hex); + }, + + /** + * Converts a hexadecimally encoded ciphertext string to a cipher params object. + * + * @param {string} input The hexadecimally encoded string. + * + * @return {CipherParams} The cipher params object. + * + * @static + * + * @example + * + * var cipherParams = CryptoJS.format.Hex.parse(hexString); + */ + parse: function (input) { + var ciphertext = Hex.parse(input); + return CipherParams.create({ ciphertext: ciphertext }); + } + }; + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Lookup tables + var SBOX = []; + var INV_SBOX = []; + var SUB_MIX_0 = []; + var SUB_MIX_1 = []; + var SUB_MIX_2 = []; + var SUB_MIX_3 = []; + var INV_SUB_MIX_0 = []; + var INV_SUB_MIX_1 = []; + var INV_SUB_MIX_2 = []; + var INV_SUB_MIX_3 = []; + + // Compute lookup tables + (function () { + // Compute double table + var d = []; + for (var i = 0; i < 256; i++) { + if (i < 128) { + d[i] = i << 1; + } else { + d[i] = (i << 1) ^ 0x11b; + } + } + + // Walk GF(2^8) + var x = 0; + var xi = 0; + for (var i = 0; i < 256; i++) { + // Compute sbox + var sx = xi ^ (xi << 1) ^ (xi << 2) ^ (xi << 3) ^ (xi << 4); + sx = (sx >>> 8) ^ (sx & 0xff) ^ 0x63; + SBOX[x] = sx; + INV_SBOX[sx] = x; + + // Compute multiplication + var x2 = d[x]; + var x4 = d[x2]; + var x8 = d[x4]; + + // Compute sub bytes, mix columns tables + var t = (d[sx] * 0x101) ^ (sx * 0x1010100); + SUB_MIX_0[x] = (t << 24) | (t >>> 8); + SUB_MIX_1[x] = (t << 16) | (t >>> 16); + SUB_MIX_2[x] = (t << 8) | (t >>> 24); + SUB_MIX_3[x] = t; + + // Compute inv sub bytes, inv mix columns tables + var t = (x8 * 0x1010101) ^ (x4 * 0x10001) ^ (x2 * 0x101) ^ (x * 0x1010100); + INV_SUB_MIX_0[sx] = (t << 24) | (t >>> 8); + INV_SUB_MIX_1[sx] = (t << 16) | (t >>> 16); + INV_SUB_MIX_2[sx] = (t << 8) | (t >>> 24); + INV_SUB_MIX_3[sx] = t; + + // Compute next counter + if (!x) { + x = xi = 1; + } else { + x = x2 ^ d[d[d[x8 ^ x2]]]; + xi ^= d[d[xi]]; + } + } + }()); + + // Precomputed Rcon lookup + var RCON = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; + + /** + * AES block cipher algorithm. + */ + var AES = C_algo.AES = BlockCipher.extend({ + _doReset: function () { + var t; + + // Skip reset of nRounds has been set before and key did not change + if (this._nRounds && this._keyPriorReset === this._key) { + return; + } + + // Shortcuts + var key = this._keyPriorReset = this._key; + var keyWords = key.words; + var keySize = key.sigBytes / 4; + + // Compute number of rounds + var nRounds = this._nRounds = keySize + 6; + + // Compute number of key schedule rows + var ksRows = (nRounds + 1) * 4; + + // Compute key schedule + var keySchedule = this._keySchedule = []; + for (var ksRow = 0; ksRow < ksRows; ksRow++) { + if (ksRow < keySize) { + keySchedule[ksRow] = keyWords[ksRow]; + } else { + t = keySchedule[ksRow - 1]; + + if (!(ksRow % keySize)) { + // Rot word + t = (t << 8) | (t >>> 24); + + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + + // Mix Rcon + t ^= RCON[(ksRow / keySize) | 0] << 24; + } else if (keySize > 6 && ksRow % keySize == 4) { + // Sub word + t = (SBOX[t >>> 24] << 24) | (SBOX[(t >>> 16) & 0xff] << 16) | (SBOX[(t >>> 8) & 0xff] << 8) | SBOX[t & 0xff]; + } + + keySchedule[ksRow] = keySchedule[ksRow - keySize] ^ t; + } + } + + // Compute inv key schedule + var invKeySchedule = this._invKeySchedule = []; + for (var invKsRow = 0; invKsRow < ksRows; invKsRow++) { + var ksRow = ksRows - invKsRow; + + if (invKsRow % 4) { + var t = keySchedule[ksRow]; + } else { + var t = keySchedule[ksRow - 4]; + } + + if (invKsRow < 4 || ksRow <= 4) { + invKeySchedule[invKsRow] = t; + } else { + invKeySchedule[invKsRow] = INV_SUB_MIX_0[SBOX[t >>> 24]] ^ INV_SUB_MIX_1[SBOX[(t >>> 16) & 0xff]] ^ + INV_SUB_MIX_2[SBOX[(t >>> 8) & 0xff]] ^ INV_SUB_MIX_3[SBOX[t & 0xff]]; + } + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX); + }, + + decryptBlock: function (M, offset) { + // Swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + + this._doCryptBlock(M, offset, this._invKeySchedule, INV_SUB_MIX_0, INV_SUB_MIX_1, INV_SUB_MIX_2, INV_SUB_MIX_3, INV_SBOX); + + // Inv swap 2nd and 4th rows + var t = M[offset + 1]; + M[offset + 1] = M[offset + 3]; + M[offset + 3] = t; + }, + + _doCryptBlock: function (M, offset, keySchedule, SUB_MIX_0, SUB_MIX_1, SUB_MIX_2, SUB_MIX_3, SBOX) { + // Shortcut + var nRounds = this._nRounds; + + // Get input, add round key + var s0 = M[offset] ^ keySchedule[0]; + var s1 = M[offset + 1] ^ keySchedule[1]; + var s2 = M[offset + 2] ^ keySchedule[2]; + var s3 = M[offset + 3] ^ keySchedule[3]; + + // Key schedule row counter + var ksRow = 4; + + // Rounds + for (var round = 1; round < nRounds; round++) { + // Shift rows, sub bytes, mix columns, add round key + var t0 = SUB_MIX_0[s0 >>> 24] ^ SUB_MIX_1[(s1 >>> 16) & 0xff] ^ SUB_MIX_2[(s2 >>> 8) & 0xff] ^ SUB_MIX_3[s3 & 0xff] ^ keySchedule[ksRow++]; + var t1 = SUB_MIX_0[s1 >>> 24] ^ SUB_MIX_1[(s2 >>> 16) & 0xff] ^ SUB_MIX_2[(s3 >>> 8) & 0xff] ^ SUB_MIX_3[s0 & 0xff] ^ keySchedule[ksRow++]; + var t2 = SUB_MIX_0[s2 >>> 24] ^ SUB_MIX_1[(s3 >>> 16) & 0xff] ^ SUB_MIX_2[(s0 >>> 8) & 0xff] ^ SUB_MIX_3[s1 & 0xff] ^ keySchedule[ksRow++]; + var t3 = SUB_MIX_0[s3 >>> 24] ^ SUB_MIX_1[(s0 >>> 16) & 0xff] ^ SUB_MIX_2[(s1 >>> 8) & 0xff] ^ SUB_MIX_3[s2 & 0xff] ^ keySchedule[ksRow++]; + + // Update state + s0 = t0; + s1 = t1; + s2 = t2; + s3 = t3; + } + + // Shift rows, sub bytes, add round key + var t0 = ((SBOX[s0 >>> 24] << 24) | (SBOX[(s1 >>> 16) & 0xff] << 16) | (SBOX[(s2 >>> 8) & 0xff] << 8) | SBOX[s3 & 0xff]) ^ keySchedule[ksRow++]; + var t1 = ((SBOX[s1 >>> 24] << 24) | (SBOX[(s2 >>> 16) & 0xff] << 16) | (SBOX[(s3 >>> 8) & 0xff] << 8) | SBOX[s0 & 0xff]) ^ keySchedule[ksRow++]; + var t2 = ((SBOX[s2 >>> 24] << 24) | (SBOX[(s3 >>> 16) & 0xff] << 16) | (SBOX[(s0 >>> 8) & 0xff] << 8) | SBOX[s1 & 0xff]) ^ keySchedule[ksRow++]; + var t3 = ((SBOX[s3 >>> 24] << 24) | (SBOX[(s0 >>> 16) & 0xff] << 16) | (SBOX[(s1 >>> 8) & 0xff] << 8) | SBOX[s2 & 0xff]) ^ keySchedule[ksRow++]; + + // Set output + M[offset] = t0; + M[offset + 1] = t1; + M[offset + 2] = t2; + M[offset + 3] = t3; + }, + + keySize: 256/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.AES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.AES.decrypt(ciphertext, key, cfg); + */ + C.AES = BlockCipher._createHelper(AES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var WordArray = C_lib.WordArray; + var BlockCipher = C_lib.BlockCipher; + var C_algo = C.algo; + + // Permuted Choice 1 constants + var PC1 = [ + 57, 49, 41, 33, 25, 17, 9, 1, + 58, 50, 42, 34, 26, 18, 10, 2, + 59, 51, 43, 35, 27, 19, 11, 3, + 60, 52, 44, 36, 63, 55, 47, 39, + 31, 23, 15, 7, 62, 54, 46, 38, + 30, 22, 14, 6, 61, 53, 45, 37, + 29, 21, 13, 5, 28, 20, 12, 4 + ]; + + // Permuted Choice 2 constants + var PC2 = [ + 14, 17, 11, 24, 1, 5, + 3, 28, 15, 6, 21, 10, + 23, 19, 12, 4, 26, 8, + 16, 7, 27, 20, 13, 2, + 41, 52, 31, 37, 47, 55, + 30, 40, 51, 45, 33, 48, + 44, 49, 39, 56, 34, 53, + 46, 42, 50, 36, 29, 32 + ]; + + // Cumulative bit shift constants + var BIT_SHIFTS = [1, 2, 4, 6, 8, 10, 12, 14, 15, 17, 19, 21, 23, 25, 27, 28]; + + // SBOXes and round permutation constants + var SBOX_P = [ + { + 0x0: 0x808200, + 0x10000000: 0x8000, + 0x20000000: 0x808002, + 0x30000000: 0x2, + 0x40000000: 0x200, + 0x50000000: 0x808202, + 0x60000000: 0x800202, + 0x70000000: 0x800000, + 0x80000000: 0x202, + 0x90000000: 0x800200, + 0xa0000000: 0x8200, + 0xb0000000: 0x808000, + 0xc0000000: 0x8002, + 0xd0000000: 0x800002, + 0xe0000000: 0x0, + 0xf0000000: 0x8202, + 0x8000000: 0x0, + 0x18000000: 0x808202, + 0x28000000: 0x8202, + 0x38000000: 0x8000, + 0x48000000: 0x808200, + 0x58000000: 0x200, + 0x68000000: 0x808002, + 0x78000000: 0x2, + 0x88000000: 0x800200, + 0x98000000: 0x8200, + 0xa8000000: 0x808000, + 0xb8000000: 0x800202, + 0xc8000000: 0x800002, + 0xd8000000: 0x8002, + 0xe8000000: 0x202, + 0xf8000000: 0x800000, + 0x1: 0x8000, + 0x10000001: 0x2, + 0x20000001: 0x808200, + 0x30000001: 0x800000, + 0x40000001: 0x808002, + 0x50000001: 0x8200, + 0x60000001: 0x200, + 0x70000001: 0x800202, + 0x80000001: 0x808202, + 0x90000001: 0x808000, + 0xa0000001: 0x800002, + 0xb0000001: 0x8202, + 0xc0000001: 0x202, + 0xd0000001: 0x800200, + 0xe0000001: 0x8002, + 0xf0000001: 0x0, + 0x8000001: 0x808202, + 0x18000001: 0x808000, + 0x28000001: 0x800000, + 0x38000001: 0x200, + 0x48000001: 0x8000, + 0x58000001: 0x800002, + 0x68000001: 0x2, + 0x78000001: 0x8202, + 0x88000001: 0x8002, + 0x98000001: 0x800202, + 0xa8000001: 0x202, + 0xb8000001: 0x808200, + 0xc8000001: 0x800200, + 0xd8000001: 0x0, + 0xe8000001: 0x8200, + 0xf8000001: 0x808002 + }, + { + 0x0: 0x40084010, + 0x1000000: 0x4000, + 0x2000000: 0x80000, + 0x3000000: 0x40080010, + 0x4000000: 0x40000010, + 0x5000000: 0x40084000, + 0x6000000: 0x40004000, + 0x7000000: 0x10, + 0x8000000: 0x84000, + 0x9000000: 0x40004010, + 0xa000000: 0x40000000, + 0xb000000: 0x84010, + 0xc000000: 0x80010, + 0xd000000: 0x0, + 0xe000000: 0x4010, + 0xf000000: 0x40080000, + 0x800000: 0x40004000, + 0x1800000: 0x84010, + 0x2800000: 0x10, + 0x3800000: 0x40004010, + 0x4800000: 0x40084010, + 0x5800000: 0x40000000, + 0x6800000: 0x80000, + 0x7800000: 0x40080010, + 0x8800000: 0x80010, + 0x9800000: 0x0, + 0xa800000: 0x4000, + 0xb800000: 0x40080000, + 0xc800000: 0x40000010, + 0xd800000: 0x84000, + 0xe800000: 0x40084000, + 0xf800000: 0x4010, + 0x10000000: 0x0, + 0x11000000: 0x40080010, + 0x12000000: 0x40004010, + 0x13000000: 0x40084000, + 0x14000000: 0x40080000, + 0x15000000: 0x10, + 0x16000000: 0x84010, + 0x17000000: 0x4000, + 0x18000000: 0x4010, + 0x19000000: 0x80000, + 0x1a000000: 0x80010, + 0x1b000000: 0x40000010, + 0x1c000000: 0x84000, + 0x1d000000: 0x40004000, + 0x1e000000: 0x40000000, + 0x1f000000: 0x40084010, + 0x10800000: 0x84010, + 0x11800000: 0x80000, + 0x12800000: 0x40080000, + 0x13800000: 0x4000, + 0x14800000: 0x40004000, + 0x15800000: 0x40084010, + 0x16800000: 0x10, + 0x17800000: 0x40000000, + 0x18800000: 0x40084000, + 0x19800000: 0x40000010, + 0x1a800000: 0x40004010, + 0x1b800000: 0x80010, + 0x1c800000: 0x0, + 0x1d800000: 0x4010, + 0x1e800000: 0x40080010, + 0x1f800000: 0x84000 + }, + { + 0x0: 0x104, + 0x100000: 0x0, + 0x200000: 0x4000100, + 0x300000: 0x10104, + 0x400000: 0x10004, + 0x500000: 0x4000004, + 0x600000: 0x4010104, + 0x700000: 0x4010000, + 0x800000: 0x4000000, + 0x900000: 0x4010100, + 0xa00000: 0x10100, + 0xb00000: 0x4010004, + 0xc00000: 0x4000104, + 0xd00000: 0x10000, + 0xe00000: 0x4, + 0xf00000: 0x100, + 0x80000: 0x4010100, + 0x180000: 0x4010004, + 0x280000: 0x0, + 0x380000: 0x4000100, + 0x480000: 0x4000004, + 0x580000: 0x10000, + 0x680000: 0x10004, + 0x780000: 0x104, + 0x880000: 0x4, + 0x980000: 0x100, + 0xa80000: 0x4010000, + 0xb80000: 0x10104, + 0xc80000: 0x10100, + 0xd80000: 0x4000104, + 0xe80000: 0x4010104, + 0xf80000: 0x4000000, + 0x1000000: 0x4010100, + 0x1100000: 0x10004, + 0x1200000: 0x10000, + 0x1300000: 0x4000100, + 0x1400000: 0x100, + 0x1500000: 0x4010104, + 0x1600000: 0x4000004, + 0x1700000: 0x0, + 0x1800000: 0x4000104, + 0x1900000: 0x4000000, + 0x1a00000: 0x4, + 0x1b00000: 0x10100, + 0x1c00000: 0x4010000, + 0x1d00000: 0x104, + 0x1e00000: 0x10104, + 0x1f00000: 0x4010004, + 0x1080000: 0x4000000, + 0x1180000: 0x104, + 0x1280000: 0x4010100, + 0x1380000: 0x0, + 0x1480000: 0x10004, + 0x1580000: 0x4000100, + 0x1680000: 0x100, + 0x1780000: 0x4010004, + 0x1880000: 0x10000, + 0x1980000: 0x4010104, + 0x1a80000: 0x10104, + 0x1b80000: 0x4000004, + 0x1c80000: 0x4000104, + 0x1d80000: 0x4010000, + 0x1e80000: 0x4, + 0x1f80000: 0x10100 + }, + { + 0x0: 0x80401000, + 0x10000: 0x80001040, + 0x20000: 0x401040, + 0x30000: 0x80400000, + 0x40000: 0x0, + 0x50000: 0x401000, + 0x60000: 0x80000040, + 0x70000: 0x400040, + 0x80000: 0x80000000, + 0x90000: 0x400000, + 0xa0000: 0x40, + 0xb0000: 0x80001000, + 0xc0000: 0x80400040, + 0xd0000: 0x1040, + 0xe0000: 0x1000, + 0xf0000: 0x80401040, + 0x8000: 0x80001040, + 0x18000: 0x40, + 0x28000: 0x80400040, + 0x38000: 0x80001000, + 0x48000: 0x401000, + 0x58000: 0x80401040, + 0x68000: 0x0, + 0x78000: 0x80400000, + 0x88000: 0x1000, + 0x98000: 0x80401000, + 0xa8000: 0x400000, + 0xb8000: 0x1040, + 0xc8000: 0x80000000, + 0xd8000: 0x400040, + 0xe8000: 0x401040, + 0xf8000: 0x80000040, + 0x100000: 0x400040, + 0x110000: 0x401000, + 0x120000: 0x80000040, + 0x130000: 0x0, + 0x140000: 0x1040, + 0x150000: 0x80400040, + 0x160000: 0x80401000, + 0x170000: 0x80001040, + 0x180000: 0x80401040, + 0x190000: 0x80000000, + 0x1a0000: 0x80400000, + 0x1b0000: 0x401040, + 0x1c0000: 0x80001000, + 0x1d0000: 0x400000, + 0x1e0000: 0x40, + 0x1f0000: 0x1000, + 0x108000: 0x80400000, + 0x118000: 0x80401040, + 0x128000: 0x0, + 0x138000: 0x401000, + 0x148000: 0x400040, + 0x158000: 0x80000000, + 0x168000: 0x80001040, + 0x178000: 0x40, + 0x188000: 0x80000040, + 0x198000: 0x1000, + 0x1a8000: 0x80001000, + 0x1b8000: 0x80400040, + 0x1c8000: 0x1040, + 0x1d8000: 0x80401000, + 0x1e8000: 0x400000, + 0x1f8000: 0x401040 + }, + { + 0x0: 0x80, + 0x1000: 0x1040000, + 0x2000: 0x40000, + 0x3000: 0x20000000, + 0x4000: 0x20040080, + 0x5000: 0x1000080, + 0x6000: 0x21000080, + 0x7000: 0x40080, + 0x8000: 0x1000000, + 0x9000: 0x20040000, + 0xa000: 0x20000080, + 0xb000: 0x21040080, + 0xc000: 0x21040000, + 0xd000: 0x0, + 0xe000: 0x1040080, + 0xf000: 0x21000000, + 0x800: 0x1040080, + 0x1800: 0x21000080, + 0x2800: 0x80, + 0x3800: 0x1040000, + 0x4800: 0x40000, + 0x5800: 0x20040080, + 0x6800: 0x21040000, + 0x7800: 0x20000000, + 0x8800: 0x20040000, + 0x9800: 0x0, + 0xa800: 0x21040080, + 0xb800: 0x1000080, + 0xc800: 0x20000080, + 0xd800: 0x21000000, + 0xe800: 0x1000000, + 0xf800: 0x40080, + 0x10000: 0x40000, + 0x11000: 0x80, + 0x12000: 0x20000000, + 0x13000: 0x21000080, + 0x14000: 0x1000080, + 0x15000: 0x21040000, + 0x16000: 0x20040080, + 0x17000: 0x1000000, + 0x18000: 0x21040080, + 0x19000: 0x21000000, + 0x1a000: 0x1040000, + 0x1b000: 0x20040000, + 0x1c000: 0x40080, + 0x1d000: 0x20000080, + 0x1e000: 0x0, + 0x1f000: 0x1040080, + 0x10800: 0x21000080, + 0x11800: 0x1000000, + 0x12800: 0x1040000, + 0x13800: 0x20040080, + 0x14800: 0x20000000, + 0x15800: 0x1040080, + 0x16800: 0x80, + 0x17800: 0x21040000, + 0x18800: 0x40080, + 0x19800: 0x21040080, + 0x1a800: 0x0, + 0x1b800: 0x21000000, + 0x1c800: 0x1000080, + 0x1d800: 0x40000, + 0x1e800: 0x20040000, + 0x1f800: 0x20000080 + }, + { + 0x0: 0x10000008, + 0x100: 0x2000, + 0x200: 0x10200000, + 0x300: 0x10202008, + 0x400: 0x10002000, + 0x500: 0x200000, + 0x600: 0x200008, + 0x700: 0x10000000, + 0x800: 0x0, + 0x900: 0x10002008, + 0xa00: 0x202000, + 0xb00: 0x8, + 0xc00: 0x10200008, + 0xd00: 0x202008, + 0xe00: 0x2008, + 0xf00: 0x10202000, + 0x80: 0x10200000, + 0x180: 0x10202008, + 0x280: 0x8, + 0x380: 0x200000, + 0x480: 0x202008, + 0x580: 0x10000008, + 0x680: 0x10002000, + 0x780: 0x2008, + 0x880: 0x200008, + 0x980: 0x2000, + 0xa80: 0x10002008, + 0xb80: 0x10200008, + 0xc80: 0x0, + 0xd80: 0x10202000, + 0xe80: 0x202000, + 0xf80: 0x10000000, + 0x1000: 0x10002000, + 0x1100: 0x10200008, + 0x1200: 0x10202008, + 0x1300: 0x2008, + 0x1400: 0x200000, + 0x1500: 0x10000000, + 0x1600: 0x10000008, + 0x1700: 0x202000, + 0x1800: 0x202008, + 0x1900: 0x0, + 0x1a00: 0x8, + 0x1b00: 0x10200000, + 0x1c00: 0x2000, + 0x1d00: 0x10002008, + 0x1e00: 0x10202000, + 0x1f00: 0x200008, + 0x1080: 0x8, + 0x1180: 0x202000, + 0x1280: 0x200000, + 0x1380: 0x10000008, + 0x1480: 0x10002000, + 0x1580: 0x2008, + 0x1680: 0x10202008, + 0x1780: 0x10200000, + 0x1880: 0x10202000, + 0x1980: 0x10200008, + 0x1a80: 0x2000, + 0x1b80: 0x202008, + 0x1c80: 0x200008, + 0x1d80: 0x0, + 0x1e80: 0x10000000, + 0x1f80: 0x10002008 + }, + { + 0x0: 0x100000, + 0x10: 0x2000401, + 0x20: 0x400, + 0x30: 0x100401, + 0x40: 0x2100401, + 0x50: 0x0, + 0x60: 0x1, + 0x70: 0x2100001, + 0x80: 0x2000400, + 0x90: 0x100001, + 0xa0: 0x2000001, + 0xb0: 0x2100400, + 0xc0: 0x2100000, + 0xd0: 0x401, + 0xe0: 0x100400, + 0xf0: 0x2000000, + 0x8: 0x2100001, + 0x18: 0x0, + 0x28: 0x2000401, + 0x38: 0x2100400, + 0x48: 0x100000, + 0x58: 0x2000001, + 0x68: 0x2000000, + 0x78: 0x401, + 0x88: 0x100401, + 0x98: 0x2000400, + 0xa8: 0x2100000, + 0xb8: 0x100001, + 0xc8: 0x400, + 0xd8: 0x2100401, + 0xe8: 0x1, + 0xf8: 0x100400, + 0x100: 0x2000000, + 0x110: 0x100000, + 0x120: 0x2000401, + 0x130: 0x2100001, + 0x140: 0x100001, + 0x150: 0x2000400, + 0x160: 0x2100400, + 0x170: 0x100401, + 0x180: 0x401, + 0x190: 0x2100401, + 0x1a0: 0x100400, + 0x1b0: 0x1, + 0x1c0: 0x0, + 0x1d0: 0x2100000, + 0x1e0: 0x2000001, + 0x1f0: 0x400, + 0x108: 0x100400, + 0x118: 0x2000401, + 0x128: 0x2100001, + 0x138: 0x1, + 0x148: 0x2000000, + 0x158: 0x100000, + 0x168: 0x401, + 0x178: 0x2100400, + 0x188: 0x2000001, + 0x198: 0x2100000, + 0x1a8: 0x0, + 0x1b8: 0x2100401, + 0x1c8: 0x100401, + 0x1d8: 0x400, + 0x1e8: 0x2000400, + 0x1f8: 0x100001 + }, + { + 0x0: 0x8000820, + 0x1: 0x20000, + 0x2: 0x8000000, + 0x3: 0x20, + 0x4: 0x20020, + 0x5: 0x8020820, + 0x6: 0x8020800, + 0x7: 0x800, + 0x8: 0x8020000, + 0x9: 0x8000800, + 0xa: 0x20800, + 0xb: 0x8020020, + 0xc: 0x820, + 0xd: 0x0, + 0xe: 0x8000020, + 0xf: 0x20820, + 0x80000000: 0x800, + 0x80000001: 0x8020820, + 0x80000002: 0x8000820, + 0x80000003: 0x8000000, + 0x80000004: 0x8020000, + 0x80000005: 0x20800, + 0x80000006: 0x20820, + 0x80000007: 0x20, + 0x80000008: 0x8000020, + 0x80000009: 0x820, + 0x8000000a: 0x20020, + 0x8000000b: 0x8020800, + 0x8000000c: 0x0, + 0x8000000d: 0x8020020, + 0x8000000e: 0x8000800, + 0x8000000f: 0x20000, + 0x10: 0x20820, + 0x11: 0x8020800, + 0x12: 0x20, + 0x13: 0x800, + 0x14: 0x8000800, + 0x15: 0x8000020, + 0x16: 0x8020020, + 0x17: 0x20000, + 0x18: 0x0, + 0x19: 0x20020, + 0x1a: 0x8020000, + 0x1b: 0x8000820, + 0x1c: 0x8020820, + 0x1d: 0x20800, + 0x1e: 0x820, + 0x1f: 0x8000000, + 0x80000010: 0x20000, + 0x80000011: 0x800, + 0x80000012: 0x8020020, + 0x80000013: 0x20820, + 0x80000014: 0x20, + 0x80000015: 0x8020000, + 0x80000016: 0x8000000, + 0x80000017: 0x8000820, + 0x80000018: 0x8020820, + 0x80000019: 0x8000020, + 0x8000001a: 0x8000800, + 0x8000001b: 0x0, + 0x8000001c: 0x20800, + 0x8000001d: 0x820, + 0x8000001e: 0x20020, + 0x8000001f: 0x8020800 + } + ]; + + // Masks that select the SBOX input + var SBOX_MASK = [ + 0xf8000001, 0x1f800000, 0x01f80000, 0x001f8000, + 0x0001f800, 0x00001f80, 0x000001f8, 0x8000001f + ]; + + /** + * DES block cipher algorithm. + */ + var DES = C_algo.DES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + + // Select 56 bits according to PC1 + var keyBits = []; + for (var i = 0; i < 56; i++) { + var keyBitPos = PC1[i] - 1; + keyBits[i] = (keyWords[keyBitPos >>> 5] >>> (31 - keyBitPos % 32)) & 1; + } + + // Assemble 16 subkeys + var subKeys = this._subKeys = []; + for (var nSubKey = 0; nSubKey < 16; nSubKey++) { + // Create subkey + var subKey = subKeys[nSubKey] = []; + + // Shortcut + var bitShift = BIT_SHIFTS[nSubKey]; + + // Select 48 bits according to PC2 + for (var i = 0; i < 24; i++) { + // Select from the left 28 key bits + subKey[(i / 6) | 0] |= keyBits[((PC2[i] - 1) + bitShift) % 28] << (31 - i % 6); + + // Select from the right 28 key bits + subKey[4 + ((i / 6) | 0)] |= keyBits[28 + (((PC2[i + 24] - 1) + bitShift) % 28)] << (31 - i % 6); + } + + // Since each subkey is applied to an expanded 32-bit input, + // the subkey can be broken into 8 values scaled to 32-bits, + // which allows the key to be used without expansion + subKey[0] = (subKey[0] << 1) | (subKey[0] >>> 31); + for (var i = 1; i < 7; i++) { + subKey[i] = subKey[i] >>> ((i - 1) * 4 + 3); + } + subKey[7] = (subKey[7] << 5) | (subKey[7] >>> 27); + } + + // Compute inverse subkeys + var invSubKeys = this._invSubKeys = []; + for (var i = 0; i < 16; i++) { + invSubKeys[i] = subKeys[15 - i]; + } + }, + + encryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._subKeys); + }, + + decryptBlock: function (M, offset) { + this._doCryptBlock(M, offset, this._invSubKeys); + }, + + _doCryptBlock: function (M, offset, subKeys) { + // Get input + this._lBlock = M[offset]; + this._rBlock = M[offset + 1]; + + // Initial permutation + exchangeLR.call(this, 4, 0x0f0f0f0f); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeRL.call(this, 2, 0x33333333); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeLR.call(this, 1, 0x55555555); + + // Rounds + for (var round = 0; round < 16; round++) { + // Shortcuts + var subKey = subKeys[round]; + var lBlock = this._lBlock; + var rBlock = this._rBlock; + + // Feistel function + var f = 0; + for (var i = 0; i < 8; i++) { + f |= SBOX_P[i][((rBlock ^ subKey[i]) & SBOX_MASK[i]) >>> 0]; + } + this._lBlock = rBlock; + this._rBlock = lBlock ^ f; + } + + // Undo swap from last round + var t = this._lBlock; + this._lBlock = this._rBlock; + this._rBlock = t; + + // Final permutation + exchangeLR.call(this, 1, 0x55555555); + exchangeRL.call(this, 8, 0x00ff00ff); + exchangeRL.call(this, 2, 0x33333333); + exchangeLR.call(this, 16, 0x0000ffff); + exchangeLR.call(this, 4, 0x0f0f0f0f); + + // Set output + M[offset] = this._lBlock; + M[offset + 1] = this._rBlock; + }, + + keySize: 64/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + // Swap bits across the left and right words + function exchangeLR(offset, mask) { + var t = ((this._lBlock >>> offset) ^ this._rBlock) & mask; + this._rBlock ^= t; + this._lBlock ^= t << offset; + } + + function exchangeRL(offset, mask) { + var t = ((this._rBlock >>> offset) ^ this._lBlock) & mask; + this._lBlock ^= t; + this._rBlock ^= t << offset; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.DES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.DES.decrypt(ciphertext, key, cfg); + */ + C.DES = BlockCipher._createHelper(DES); + + /** + * Triple-DES block cipher algorithm. + */ + var TripleDES = C_algo.TripleDES = BlockCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + // Make sure the key length is valid (64, 128 or >= 192 bit) + if (keyWords.length !== 2 && keyWords.length !== 4 && keyWords.length < 6) { + throw new Error('Invalid key length - 3DES requires the key length to be 64, 128, 192 or >192.'); + } + + // Extend the key according to the keying options defined in 3DES standard + var key1 = keyWords.slice(0, 2); + var key2 = keyWords.length < 4 ? keyWords.slice(0, 2) : keyWords.slice(2, 4); + var key3 = keyWords.length < 6 ? keyWords.slice(0, 2) : keyWords.slice(4, 6); + + // Create DES instances + this._des1 = DES.createEncryptor(WordArray.create(key1)); + this._des2 = DES.createEncryptor(WordArray.create(key2)); + this._des3 = DES.createEncryptor(WordArray.create(key3)); + }, + + encryptBlock: function (M, offset) { + this._des1.encryptBlock(M, offset); + this._des2.decryptBlock(M, offset); + this._des3.encryptBlock(M, offset); + }, + + decryptBlock: function (M, offset) { + this._des3.decryptBlock(M, offset); + this._des2.encryptBlock(M, offset); + this._des1.decryptBlock(M, offset); + }, + + keySize: 192/32, + + ivSize: 64/32, + + blockSize: 64/32 + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.TripleDES.encrypt(message, key, cfg); + * var plaintext = CryptoJS.TripleDES.decrypt(ciphertext, key, cfg); + */ + C.TripleDES = BlockCipher._createHelper(TripleDES); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + /** + * RC4 stream cipher algorithm. + */ + var RC4 = C_algo.RC4 = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var key = this._key; + var keyWords = key.words; + var keySigBytes = key.sigBytes; + + // Init sbox + var S = this._S = []; + for (var i = 0; i < 256; i++) { + S[i] = i; + } + + // Key setup + for (var i = 0, j = 0; i < 256; i++) { + var keyByteIndex = i % keySigBytes; + var keyByte = (keyWords[keyByteIndex >>> 2] >>> (24 - (keyByteIndex % 4) * 8)) & 0xff; + + j = (j + S[i] + keyByte) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + } + + // Counters + this._i = this._j = 0; + }, + + _doProcessBlock: function (M, offset) { + M[offset] ^= generateKeystreamWord.call(this); + }, + + keySize: 256/32, + + ivSize: 0 + }); + + function generateKeystreamWord() { + // Shortcuts + var S = this._S; + var i = this._i; + var j = this._j; + + // Generate keystream word + var keystreamWord = 0; + for (var n = 0; n < 4; n++) { + i = (i + 1) % 256; + j = (j + S[i]) % 256; + + // Swap + var t = S[i]; + S[i] = S[j]; + S[j] = t; + + keystreamWord |= S[(S[i] + S[j]) % 256] << (24 - n * 8); + } + + // Update counters + this._i = i; + this._j = j; + + return keystreamWord; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4.decrypt(ciphertext, key, cfg); + */ + C.RC4 = StreamCipher._createHelper(RC4); + + /** + * Modified RC4 stream cipher algorithm. + */ + var RC4Drop = C_algo.RC4Drop = RC4.extend({ + /** + * Configuration options. + * + * @property {number} drop The number of keystream words to drop. Default 192 + */ + cfg: RC4.cfg.extend({ + drop: 192 + }), + + _doReset: function () { + RC4._doReset.call(this); + + // Drop + for (var i = this.cfg.drop; i > 0; i--) { + generateKeystreamWord.call(this); + } + } + }); + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RC4Drop.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RC4Drop.decrypt(ciphertext, key, cfg); + */ + C.RC4Drop = StreamCipher._createHelper(RC4Drop); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm + */ + var Rabbit = C_algo.Rabbit = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Swap endian + for (var i = 0; i < 4; i++) { + K[i] = (((K[i] << 8) | (K[i] >>> 24)) & 0x00ff00ff) | + (((K[i] << 24) | (K[i] >>> 8)) & 0xff00ff00); + } + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.Rabbit.encrypt(message, key, cfg); + * var plaintext = CryptoJS.Rabbit.decrypt(ciphertext, key, cfg); + */ + C.Rabbit = StreamCipher._createHelper(Rabbit); + }()); + + + (function () { + // Shortcuts + var C = CryptoJS; + var C_lib = C.lib; + var StreamCipher = C_lib.StreamCipher; + var C_algo = C.algo; + + // Reusable objects + var S = []; + var C_ = []; + var G = []; + + /** + * Rabbit stream cipher algorithm. + * + * This is a legacy version that neglected to convert the key to little-endian. + * This error doesn't affect the cipher's security, + * but it does affect its compatibility with other implementations. + */ + var RabbitLegacy = C_algo.RabbitLegacy = StreamCipher.extend({ + _doReset: function () { + // Shortcuts + var K = this._key.words; + var iv = this.cfg.iv; + + // Generate initial state values + var X = this._X = [ + K[0], (K[3] << 16) | (K[2] >>> 16), + K[1], (K[0] << 16) | (K[3] >>> 16), + K[2], (K[1] << 16) | (K[0] >>> 16), + K[3], (K[2] << 16) | (K[1] >>> 16) + ]; + + // Generate initial counter values + var C = this._C = [ + (K[2] << 16) | (K[2] >>> 16), (K[0] & 0xffff0000) | (K[1] & 0x0000ffff), + (K[3] << 16) | (K[3] >>> 16), (K[1] & 0xffff0000) | (K[2] & 0x0000ffff), + (K[0] << 16) | (K[0] >>> 16), (K[2] & 0xffff0000) | (K[3] & 0x0000ffff), + (K[1] << 16) | (K[1] >>> 16), (K[3] & 0xffff0000) | (K[0] & 0x0000ffff) + ]; + + // Carry bit + this._b = 0; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + + // Modify the counters + for (var i = 0; i < 8; i++) { + C[i] ^= X[(i + 4) & 7]; + } + + // IV setup + if (iv) { + // Shortcuts + var IV = iv.words; + var IV_0 = IV[0]; + var IV_1 = IV[1]; + + // Generate four subvectors + var i0 = (((IV_0 << 8) | (IV_0 >>> 24)) & 0x00ff00ff) | (((IV_0 << 24) | (IV_0 >>> 8)) & 0xff00ff00); + var i2 = (((IV_1 << 8) | (IV_1 >>> 24)) & 0x00ff00ff) | (((IV_1 << 24) | (IV_1 >>> 8)) & 0xff00ff00); + var i1 = (i0 >>> 16) | (i2 & 0xffff0000); + var i3 = (i2 << 16) | (i0 & 0x0000ffff); + + // Modify counter values + C[0] ^= i0; + C[1] ^= i1; + C[2] ^= i2; + C[3] ^= i3; + C[4] ^= i0; + C[5] ^= i1; + C[6] ^= i2; + C[7] ^= i3; + + // Iterate the system four times + for (var i = 0; i < 4; i++) { + nextState.call(this); + } + } + }, + + _doProcessBlock: function (M, offset) { + // Shortcut + var X = this._X; + + // Iterate the system + nextState.call(this); + + // Generate four keystream words + S[0] = X[0] ^ (X[5] >>> 16) ^ (X[3] << 16); + S[1] = X[2] ^ (X[7] >>> 16) ^ (X[5] << 16); + S[2] = X[4] ^ (X[1] >>> 16) ^ (X[7] << 16); + S[3] = X[6] ^ (X[3] >>> 16) ^ (X[1] << 16); + + for (var i = 0; i < 4; i++) { + // Swap endian + S[i] = (((S[i] << 8) | (S[i] >>> 24)) & 0x00ff00ff) | + (((S[i] << 24) | (S[i] >>> 8)) & 0xff00ff00); + + // Encrypt + M[offset + i] ^= S[i]; + } + }, + + blockSize: 128/32, + + ivSize: 64/32 + }); + + function nextState() { + // Shortcuts + var X = this._X; + var C = this._C; + + // Save old counter values + for (var i = 0; i < 8; i++) { + C_[i] = C[i]; + } + + // Calculate new counter values + C[0] = (C[0] + 0x4d34d34d + this._b) | 0; + C[1] = (C[1] + 0xd34d34d3 + ((C[0] >>> 0) < (C_[0] >>> 0) ? 1 : 0)) | 0; + C[2] = (C[2] + 0x34d34d34 + ((C[1] >>> 0) < (C_[1] >>> 0) ? 1 : 0)) | 0; + C[3] = (C[3] + 0x4d34d34d + ((C[2] >>> 0) < (C_[2] >>> 0) ? 1 : 0)) | 0; + C[4] = (C[4] + 0xd34d34d3 + ((C[3] >>> 0) < (C_[3] >>> 0) ? 1 : 0)) | 0; + C[5] = (C[5] + 0x34d34d34 + ((C[4] >>> 0) < (C_[4] >>> 0) ? 1 : 0)) | 0; + C[6] = (C[6] + 0x4d34d34d + ((C[5] >>> 0) < (C_[5] >>> 0) ? 1 : 0)) | 0; + C[7] = (C[7] + 0xd34d34d3 + ((C[6] >>> 0) < (C_[6] >>> 0) ? 1 : 0)) | 0; + this._b = (C[7] >>> 0) < (C_[7] >>> 0) ? 1 : 0; + + // Calculate the g-values + for (var i = 0; i < 8; i++) { + var gx = X[i] + C[i]; + + // Construct high and low argument for squaring + var ga = gx & 0xffff; + var gb = gx >>> 16; + + // Calculate high and low result of squaring + var gh = ((((ga * ga) >>> 17) + ga * gb) >>> 15) + gb * gb; + var gl = (((gx & 0xffff0000) * gx) | 0) + (((gx & 0x0000ffff) * gx) | 0); + + // High XOR low + G[i] = gh ^ gl; + } + + // Calculate new state values + X[0] = (G[0] + ((G[7] << 16) | (G[7] >>> 16)) + ((G[6] << 16) | (G[6] >>> 16))) | 0; + X[1] = (G[1] + ((G[0] << 8) | (G[0] >>> 24)) + G[7]) | 0; + X[2] = (G[2] + ((G[1] << 16) | (G[1] >>> 16)) + ((G[0] << 16) | (G[0] >>> 16))) | 0; + X[3] = (G[3] + ((G[2] << 8) | (G[2] >>> 24)) + G[1]) | 0; + X[4] = (G[4] + ((G[3] << 16) | (G[3] >>> 16)) + ((G[2] << 16) | (G[2] >>> 16))) | 0; + X[5] = (G[5] + ((G[4] << 8) | (G[4] >>> 24)) + G[3]) | 0; + X[6] = (G[6] + ((G[5] << 16) | (G[5] >>> 16)) + ((G[4] << 16) | (G[4] >>> 16))) | 0; + X[7] = (G[7] + ((G[6] << 8) | (G[6] >>> 24)) + G[5]) | 0; + } + + /** + * Shortcut functions to the cipher's object interface. + * + * @example + * + * var ciphertext = CryptoJS.RabbitLegacy.encrypt(message, key, cfg); + * var plaintext = CryptoJS.RabbitLegacy.decrypt(ciphertext, key, cfg); + */ + C.RabbitLegacy = StreamCipher._createHelper(RabbitLegacy); + }()); + + + return CryptoJS; + +})); \ No newline at end of file diff --git a/quickjs/src/main/assets/js/lib/http.js b/quickjs/src/main/assets/js/lib/http.js new file mode 100644 index 00000000..2cce4bd9 --- /dev/null +++ b/quickjs/src/main/assets/js/lib/http.js @@ -0,0 +1,17 @@ +let req = (url, options) => http(url, Object.assign({ + async: false +}, options)); + +function http(url, options = {}) { + if (options?.async === false) return _http(url, options) + return new Promise(resolve => _http(url, Object.assign({ + complete: res => resolve(res) + }, options))).catch(err => { + console.error(err.name, err.message, err.stack) + return { + ok: false, + status: 500, + url + } + }) +} \ No newline at end of file diff --git a/quickjs/src/main/assets/js/lib/similarity.js b/quickjs/src/main/assets/js/lib/similarity.js new file mode 100644 index 00000000..3dfc497d --- /dev/null +++ b/quickjs/src/main/assets/js/lib/similarity.js @@ -0,0 +1 @@ +function compareTwoStrings(first,second){if((first=first.replace(/\s+/g,""))===(second=second.replace(/\s+/g,"")))return 1;if(first.length<2||second.length<2)return 0;var firstBigrams=new Map;for(let i=0;iratings[bestMatchIndex].rating&&(bestMatchIndex=i)}return{ratings:ratings,bestMatch:ratings[bestMatchIndex],bestMatchIndex:bestMatchIndex}}function lcs(str1,str2){if(!str1||!str2)return{length:0,sequence:"",offset:0};for(var sequence="",str1Length=str1.length,str2Length=str2.length,num=new Array(str1Length),maxlen=0,lastSubsBegin=0,i=0;imaxlen&&(maxlen=num[i][j],lastSubsBegin===(thisSubsBegin=i-num[i][j]+1)?sequence+=str1[i]:(lastSubsBegin=thisSubsBegin,sequence="",sequence+=str1.substr(lastSubsBegin,i+1-lastSubsBegin))));return{length:maxlen,sequence:sequence,offset:thisSubsBegin}}function findBestLCS(mainString,targetStrings){var results=[];let bestMatchIndex=0;for(let i=0;iresults[bestMatchIndex].lcs.length&&(bestMatchIndex=i)}return{allLCS:results,bestMatch:results[bestMatchIndex],bestMatchIndex:bestMatchIndex}}export{compareTwoStrings,findBestMatch,findBestLCS}; \ No newline at end of file diff --git a/quickjs/src/main/assets/js/lib/spider.js b/quickjs/src/main/assets/js/lib/spider.js new file mode 100644 index 00000000..baaef84e --- /dev/null +++ b/quickjs/src/main/assets/js/lib/spider.js @@ -0,0 +1,10 @@ +import * as spider from '%s' + +if (!globalThis.__JS_SPIDER__) { + if (spider.__jsEvalReturn) { + globalThis.req = http + globalThis.__JS_SPIDER__ = spider.__jsEvalReturn() + } else if (spider.default) { + globalThis.__JS_SPIDER__ = typeof spider.default === 'function' ? spider.default() : spider.default + } +} \ No newline at end of file diff --git a/quickjs/src/main/java/com/fongmi/quickjs/Provider.java b/quickjs/src/main/java/com/fongmi/quickjs/Provider.java new file mode 100644 index 00000000..cd04c808 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/Provider.java @@ -0,0 +1,54 @@ +package com.fongmi.quickjs; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.whl.quickjs.android.QuickJSLoader; + +public class Provider extends ContentProvider { + + static { + try { + QuickJSLoader.init(); + } catch (Throwable ignored) { + } + } + + @Override + public boolean onCreate() { + return true; + } + + @Nullable + @Override + public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) { + return null; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return null; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) { + return null; + } + + @Override + public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } + + @Override + public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) { + return 0; + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/bean/Req.java b/quickjs/src/main/java/com/fongmi/quickjs/bean/Req.java new file mode 100644 index 00000000..af1191e6 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/bean/Req.java @@ -0,0 +1,89 @@ +package com.fongmi.quickjs.bean; + +import android.text.TextUtils; + +import com.github.catvod.utils.Json; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class Req { + + @SerializedName("buffer") + private Integer buffer; + @SerializedName("redirect") + private Integer redirect; + @SerializedName("timeout") + private Integer timeout; + @SerializedName("postType") + private String postType; + @SerializedName("method") + private String method; + @SerializedName("body") + private String body; + @SerializedName("data") + private JsonElement data; + @SerializedName("headers") + private JsonElement headers; + + public static Req objectFrom(String json) { + return new Gson().fromJson(json, Req.class); + } + + public int getBuffer() { + return buffer == null ? 0 : buffer; + } + + public Integer getRedirect() { + return redirect == null ? 1 : redirect; + } + + public Integer getTimeout() { + return timeout == null ? 10000 : timeout; + } + + public String getPostType() { + return TextUtils.isEmpty(postType) ? "json" : postType; + } + + public String getMethod() { + return TextUtils.isEmpty(method) ? "get" : method; + } + + public String getBody() { + return body; + } + + public JsonElement getData() { + return data; + } + + private JsonElement getHeaders() { + return headers; + } + + public boolean isRedirect() { + return getRedirect() == 1; + } + + public Map getHeader() { + return Json.toMap(getHeaders()); + } + + public String getCharset() { + Map header = getHeader(); + List keys = Arrays.asList("Content-Type", "content-type"); + for (String key : keys) if (header.containsKey(key)) return getCharset(Objects.requireNonNull(header.get(key))); + return "UTF-8"; + } + + private String getCharset(String value) { + for (String text : value.split(";")) if (text.contains("charset=")) return text.split("=")[1]; + return "UTF-8"; + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/bean/Res.java b/quickjs/src/main/java/com/fongmi/quickjs/bean/Res.java new file mode 100644 index 00000000..57d2f040 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/bean/Res.java @@ -0,0 +1,63 @@ +package com.fongmi.quickjs.bean; + +import android.text.TextUtils; + +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Util; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.annotations.SerializedName; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +public class Res { + + @SerializedName("code") + private Integer code; + @SerializedName("buffer") + private Integer buffer; + @SerializedName("content") + private String content; + @SerializedName("headers") + private JsonElement headers; + + public static Res objectFrom(String json) { + return new Gson().fromJson(json, Res.class); + } + + public int getCode() { + return code == null ? 200 : code; + } + + public int getBuffer() { + return buffer == null ? 0 : buffer; + } + + public String getContent() { + return TextUtils.isEmpty(content) ? "" : content; + } + + private JsonElement getHeaders() { + return headers; + } + + public Map getHeader() { + return Json.toMap(getHeaders()); + } + + public String getContentType() { + Map header = getHeader(); + List keys = Arrays.asList("Content-Type", "content-type"); + for (String key : keys) if (header.containsKey(key)) return Objects.requireNonNull(header.get(key)); + return "application/octet-stream"; + } + + public ByteArrayInputStream getStream() { + if (getBuffer() == 2) return new ByteArrayInputStream(Util.decode(getContent())); + return new ByteArrayInputStream(getContent().getBytes()); + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/crawler/Spider.java b/quickjs/src/main/java/com/fongmi/quickjs/crawler/Spider.java new file mode 100644 index 00000000..ef6e5594 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/crawler/Spider.java @@ -0,0 +1,240 @@ +package com.fongmi.quickjs.crawler; + +import android.content.Context; + +import com.fongmi.quickjs.bean.Res; +import com.fongmi.quickjs.method.Console; +import com.fongmi.quickjs.method.Global; +import com.fongmi.quickjs.method.Local; +import com.fongmi.quickjs.utils.Async; +import com.fongmi.quickjs.utils.JSUtil; +import com.fongmi.quickjs.utils.Module; +import com.github.catvod.utils.Asset; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.UriUtil; +import com.github.catvod.utils.Util; +import com.whl.quickjs.wrapper.JSArray; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.QuickJSContext; + +import org.json.JSONArray; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +import dalvik.system.DexClassLoader; +import java9.util.concurrent.CompletableFuture; + +public class Spider extends com.github.catvod.crawler.Spider { + + private final ExecutorService executor; + private final DexClassLoader dex; + private QuickJSContext ctx; + private JSObject jsObject; + private final String key; + private final String api; + private boolean cat; + + public Spider(String key, String api, DexClassLoader dex) throws Exception { + this.executor = Executors.newSingleThreadExecutor(); + this.key = key; + this.api = api; + this.dex = dex; + initializeJS(); + } + + private void submit(Runnable runnable) { + executor.submit(runnable); + } + + private Future submit(Callable callable) { + return executor.submit(callable); + } + + private Object call(String func, Object... args) throws Exception { + return CompletableFuture.supplyAsync(() -> Async.run(jsObject, func, args), executor).join().get(); + } + + @Override + public void init(Context context, String extend) throws Exception { + if (cat) call("init", submit(() -> cfg(extend)).get()); + else call("init", Json.isObj(extend) ? ctx.parse(extend) : extend); + } + + @Override + public String homeContent(boolean filter) throws Exception { + return (String) call("home", filter); + } + + @Override + public String homeVideoContent() throws Exception { + return (String) call("homeVod"); + } + + @Override + public String categoryContent(String tid, String pg, boolean filter, HashMap extend) throws Exception { + JSObject obj = submit(() -> JSUtil.toObject(ctx, extend)).get(); + return (String) call("category", tid, pg, filter, obj); + } + + @Override + public String detailContent(List ids) throws Exception { + return (String) call("detail", ids.get(0)); + } + + @Override + public String searchContent(String key, boolean quick) throws Exception { + return (String) call("search", key, quick); + } + + @Override + public String searchContent(String key, boolean quick, String pg) throws Exception { + return (String) call("search", key, quick, pg); + } + + @Override + public String playerContent(String flag, String id, List vipFlags) throws Exception { + JSArray array = submit(() -> JSUtil.toArray(ctx, vipFlags)).get(); + return (String) call("play", flag, id, array); + } + + @Override + public String liveContent(String url) throws Exception { + return (String) call("live", url); + } + + @Override + public boolean manualVideoCheck() throws Exception { + return (Boolean) call("sniffer"); + } + + @Override + public boolean isVideoFormat(String url) throws Exception { + return (Boolean) call("isVideo", url); + } + + @Override + public Object[] proxyLocal(Map params) throws Exception { + if ("catvod".equals(params.get("from"))) return proxy2(params); + else return submit(() -> proxy1(params)).get(); + } + + @Override + public String action(String action) throws Exception { + return (String) call("action", action); + } + + @Override + public void destroy() { + try { + call("destroy"); + } catch (Throwable e) { + e.printStackTrace(); + } + submit(() -> { + executor.shutdownNow(); + jsObject.release(); + ctx.destroy(); + }); + } + + private void initializeJS() throws Exception { + submit(() -> { + createCtx(); + createFun(); + createObj(); + return null; + }).get(); + } + + private void createCtx() { + ctx = QuickJSContext.create(); + ctx.setConsole(new Console()); + ctx.evaluate(Asset.read("js/lib/http.js")); + ctx.getGlobalObject().setProperty("local", Local.class); + ctx.setModuleLoader(new QuickJSContext.BytecodeModuleLoader() { + @Override + public String moduleNormalizeName(String baseModuleName, String moduleName) { + return UriUtil.resolve(baseModuleName, moduleName); + } + + @Override + public byte[] getModuleBytecode(String moduleName) { + return ctx.compileModule(Module.get().fetch(moduleName), moduleName); + } + }); + } + + private void createFun() { + try { + Global.create(ctx, executor); + Class clz = dex.loadClass("com.github.catvod.js.Function"); + clz.getDeclaredConstructor(QuickJSContext.class).newInstance(ctx); + } catch (Throwable e) { + e.printStackTrace(); + } + } + + private void createObj() { + String spider = "__JS_SPIDER__"; + String global = "globalThis." + spider; + String content = Module.get().fetch(api); + cat = content.contains("__jsEvalReturn"); + ctx.evaluateModule(content.replace(spider, global), api); + ctx.evaluateModule(String.format(Asset.read("js/lib/spider.js"), api)); + jsObject = (JSObject) ctx.getProperty(ctx.getGlobalObject(), spider); + } + + private JSObject cfg(String ext) { + JSObject cfg = ctx.createNewJSObject(); + cfg.setProperty("stype", 3); + cfg.setProperty("skey", key); + if (!Json.isObj(ext)) cfg.setProperty("ext", ext); + else cfg.setProperty("ext", (JSObject) ctx.parse(ext)); + return cfg; + } + + private Object[] proxy1(Map params) throws Exception { + JSObject object = JSUtil.toObject(ctx, params); + JSONArray array = new JSONArray(((JSArray) jsObject.getJSFunction("proxy").call(object)).stringify()); + Map headers = array.length() > 3 ? Json.toMap(array.optString(3)) : null; + boolean base64 = array.length() > 4 && array.optInt(4) == 1; + Object[] result = new Object[4]; + result[0] = array.optInt(0); + result[1] = array.optString(1); + result[2] = getStream(array.opt(2), base64); + result[3] = headers; + return result; + } + + private Object[] proxy2(Map params) throws Exception { + String url = params.get("url"); + String header = params.get("header"); + JSArray array = submit(() -> JSUtil.toArray(ctx, Arrays.asList(url.split("/")))).get(); + Object object = submit(() -> ctx.parse(header)).get(); + String json = (String) call("proxy", array, object); + Res res = Res.objectFrom(json); + Object[] result = new Object[3]; + result[0] = res.getCode(); + result[1] = res.getContentType(); + result[2] = res.getStream(); + return result; + } + + private ByteArrayInputStream getStream(Object o, boolean base64) { + if (o instanceof byte[]) { + return new ByteArrayInputStream((byte[]) o); + } else { + String content = o.toString(); + if (base64 && content.contains("base64,")) content = content.split("base64,")[1]; + return new ByteArrayInputStream(base64 ? Util.decode(content) : content.getBytes()); + } + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/method/Console.java b/quickjs/src/main/java/com/fongmi/quickjs/method/Console.java new file mode 100644 index 00000000..fc1cc63a --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/method/Console.java @@ -0,0 +1,29 @@ +package com.fongmi.quickjs.method; + +import com.orhanobut.logger.Logger; +import com.whl.quickjs.wrapper.QuickJSContext; + +public class Console implements QuickJSContext.Console { + + private static final String TAG = "quickjs"; + + @Override + public void log(String info) { + Logger.t(TAG).d(info); + } + + @Override + public void info(String info) { + Logger.t(TAG).i(info); + } + + @Override + public void warn(String info) { + Logger.t(TAG).w(info); + } + + @Override + public void error(String info) { + Logger.t(TAG).e(info); + } +} \ No newline at end of file diff --git a/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java b/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java new file mode 100644 index 00000000..b0ea8530 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/method/Global.java @@ -0,0 +1,175 @@ +package com.fongmi.quickjs.method; + +import androidx.annotation.Keep; +import androidx.annotation.NonNull; + +import com.fongmi.quickjs.bean.Req; +import com.fongmi.quickjs.utils.Connect; +import com.fongmi.quickjs.utils.Crypto; +import com.github.catvod.Proxy; +import com.github.catvod.utils.Trans; +import com.github.catvod.utils.UriUtil; +import com.orhanobut.logger.Logger; +import com.whl.quickjs.wrapper.JSFunction; +import com.whl.quickjs.wrapper.JSMethod; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.QuickJSContext; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.URLEncoder; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; + +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.Response; + +public class Global { + + private final ExecutorService executor; + private final QuickJSContext ctx; + private final Timer timer; + + public static Global create(QuickJSContext ctx, ExecutorService executor) { + return new Global(ctx, executor); + } + + private Global(QuickJSContext ctx, ExecutorService executor) { + this.executor = executor; + this.timer = new Timer(); + this.ctx = ctx; + setProperty(); + } + + private void setProperty() { + for (Method method : getClass().getMethods()) { + if (!method.isAnnotationPresent(JSMethod.class)) continue; + ctx.getGlobalObject().setProperty(method.getName(), args -> { + try { + return method.invoke(this, args); + } catch (Exception e) { + return null; + } + }); + } + } + + private void submit(Runnable runnable) { + if (!executor.isShutdown()) executor.submit(runnable); + } + + @Keep + @JSMethod + public String s2t(String text) { + return Trans.s2t(false, text); + } + + @Keep + @JSMethod + public String t2s(String text) { + return Trans.t2s(false, text); + } + + @Keep + @JSMethod + public Integer getPort() { + return Proxy.getPort(); + } + + @Keep + @JSMethod + public String getProxy(Boolean local) { + return Proxy.getUrl(local) + "?do=js"; + } + + @Keep + @JSMethod + public String js2Proxy(Boolean dynamic, Integer siteType, String siteKey, String url, JSObject headers) { + return getProxy(!dynamic) + String.format("&from=catvod&siteType=%s&siteKey=%s&header=%s&url=%s", siteType, siteKey, URLEncoder.encode(headers.stringify()), URLEncoder.encode(url)); + } + + @Keep + @JSMethod + public Object setTimeout(JSFunction func, Integer delay) { + func.hold(); + schedule(func, delay); + return null; + } + + @Keep + @JSMethod + public JSObject _http(String url, JSObject options) { + JSFunction complete = options.getJSFunction("complete"); + if (complete == null) return req(url, options); + Req req = Req.objectFrom(options.stringify()); + Connect.to(url, req).enqueue(getCallback(complete, req)); + return null; + } + + @Keep + @JSMethod + public JSObject req(String url, JSObject options) { + try { + Req req = Req.objectFrom(options.stringify()); + Response res = Connect.to(url, req).execute(); + return Connect.success(ctx, req, res); + } catch (Exception e) { + return Connect.error(ctx); + } + } + + @Keep + @JSMethod + public String joinUrl(String parent, String child) { + return UriUtil.resolve(parent, child); + } + + @Keep + @JSMethod + public String md5X(String text) { + String result = Crypto.md5(text); + Logger.t("md5X").d("text:%s\nresult:\n%s", text, result); + return result; + } + + @Keep + @JSMethod + public String aesX(String mode, boolean encrypt, String input, boolean inBase64, String key, String iv, boolean outBase64) { + String result = Crypto.aes(mode, encrypt, input, inBase64, key, iv, outBase64); + Logger.t("aesX").d("mode:%s\nencrypt:%s\ninBase64:%s\noutBase64:%s\nkey:%s\niv:%s\ninput:\n%s\nresult:\n%s", mode, encrypt, inBase64, outBase64, key, iv, input, result); + return result; + } + + @Keep + @JSMethod + public String rsaX(String mode, boolean pub, boolean encrypt, String input, boolean inBase64, String key, boolean outBase64) { + String result = Crypto.rsa(mode, pub, encrypt, input, inBase64, key, outBase64); + Logger.t("rsaX").d("mode:%s\npub:%s\nencrypt:%s\ninBase64:%s\noutBase64:%s\nkey:\n%s\ninput:\n%s\nresult:\n%s", mode, pub, encrypt, inBase64, outBase64, key, input, result); + return result; + } + + private Callback getCallback(JSFunction complete, Req req) { + return new Callback() { + @Override + public void onResponse(@NonNull Call call, @NonNull Response res) { + submit(() -> complete.call(Connect.success(ctx, req, res))); + } + + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + submit(() -> complete.call(Connect.error(ctx))); + } + }; + } + + private void schedule(JSFunction func, int delay) { + timer.schedule(new TimerTask() { + @Override + public void run() { + submit(func::call); + } + }, delay); + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/method/Local.java b/quickjs/src/main/java/com/fongmi/quickjs/method/Local.java new file mode 100644 index 00000000..facba06d --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/method/Local.java @@ -0,0 +1,33 @@ +package com.fongmi.quickjs.method; + +import android.text.TextUtils; + +import androidx.annotation.Keep; + +import com.github.catvod.utils.Prefers; +import com.whl.quickjs.wrapper.JSMethod; + +public class Local { + + private String getKey(String rule, String key) { + return "cache_" + (TextUtils.isEmpty(rule) ? "" : rule + "_") + key; + } + + @Keep + @JSMethod + public String get(String rule, String key) { + return Prefers.getString(getKey(rule, key)); + } + + @Keep + @JSMethod + public void set(String rule, String key, String value) { + Prefers.put(getKey(rule, key), value); + } + + @Keep + @JSMethod + public void delete(String rule, String key) { + Prefers.remove(getKey(rule, key)); + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/Async.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/Async.java new file mode 100644 index 00000000..7e2c0750 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/Async.java @@ -0,0 +1,50 @@ +package com.fongmi.quickjs.utils; + +import com.whl.quickjs.wrapper.JSCallFunction; +import com.whl.quickjs.wrapper.JSFunction; +import com.whl.quickjs.wrapper.JSObject; + +import java9.util.concurrent.CompletableFuture; + +public class Async { + + private final CompletableFuture future; + + public static CompletableFuture run(JSObject object, String name, Object[] args) { + return new Async().call(object, name, args); + } + + private Async() { + this.future = new CompletableFuture<>(); + } + + private CompletableFuture call(JSObject object, String name, Object[] args) { + JSFunction function = object.getJSFunction(name); + if (function == null) return empty(); + Object result = function.call(args); + if (result instanceof JSObject) then(result); + else future.complete(result); + function.release(); + return future; + } + + private CompletableFuture empty() { + future.complete(null); + return future; + } + + private void then(Object result) { + JSObject promise = (JSObject) result; + JSFunction then = promise.getJSFunction("then"); + if (then != null) then.call(callback); + if (then != null) then.release(); + } + + private final JSCallFunction callback = new JSCallFunction() { + @Override + public Object call(Object... args) { + future.complete(args[0]); + return null; + } + }; +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java new file mode 100644 index 00000000..74d8b801 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/Connect.java @@ -0,0 +1,101 @@ +package com.fongmi.quickjs.utils; + +import com.fongmi.quickjs.bean.Req; +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Json; +import com.github.catvod.utils.Util; +import com.google.common.net.HttpHeaders; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.QuickJSContext; + +import java.security.SecureRandom; +import java.util.List; +import java.util.Map; + +import okhttp3.Call; +import okhttp3.FormBody; +import okhttp3.Headers; +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class Connect { + + public static Call to(String url, Req req) { + OkHttpClient client = OkHttp.client(req.isRedirect(), req.getTimeout()); + return client.newCall(getRequest(url, req, Headers.of(req.getHeader()))); + } + + public static JSObject success(QuickJSContext ctx, Req req, Response res) { + try (res) { + JSObject jsObject = ctx.createNewJSObject(); + JSObject jsHeader = ctx.createNewJSObject(); + setHeader(ctx, res, jsHeader); + jsObject.setProperty("code", res.code()); + jsObject.setProperty("headers", jsHeader); + if (req.getBuffer() == 0) jsObject.setProperty("content", new String(res.body().bytes(), req.getCharset())); + if (req.getBuffer() == 1) jsObject.setProperty("content", JSUtil.toArray(ctx, res.body().bytes())); + if (req.getBuffer() == 2) jsObject.setProperty("content", Util.base64(res.body().bytes())); + if (req.getBuffer() == 3) jsObject.setProperty("content", res.body().bytes()); + return jsObject; + } catch (Exception e) { + return error(ctx); + } + } + + public static JSObject error(QuickJSContext ctx) { + JSObject jsObject = ctx.createNewJSObject(); + JSObject jsHeader = ctx.createNewJSObject(); + jsObject.setProperty("headers", jsHeader); + jsObject.setProperty("content", ""); + jsObject.setProperty("code", ""); + return jsObject; + } + + private static Request getRequest(String url, Req req, Headers headers) { + if (req.getMethod().equalsIgnoreCase("post")) { + return new Request.Builder().url(url).headers(headers).post(getPostBody(req, headers.get(HttpHeaders.CONTENT_TYPE))).build(); + } else if (req.getMethod().equalsIgnoreCase("header")) { + return new Request.Builder().url(url).headers(headers).head().build(); + } else { + return new Request.Builder().url(url).headers(headers).get().build(); + } + } + + private static RequestBody getPostBody(Req req, String contentType) { + if (req.getData() != null && "json".equals(req.getPostType())) return getJsonBody(req); + if (req.getData() != null && "form".equals(req.getPostType())) return getFormBody(req); + if (req.getData() != null && "form-data".equals(req.getPostType())) return getFormDataBody(req); + if (req.getBody() != null && contentType != null) return RequestBody.create(req.getBody(), MediaType.get(contentType)); + return RequestBody.create(new byte[0]); + } + + private static RequestBody getJsonBody(Req req) { + return RequestBody.create(req.getData().toString(), MediaType.get("application/json; charset=utf-8")); + } + + private static RequestBody getFormBody(Req req) { + FormBody.Builder builder = new FormBody.Builder(); + Map params = Json.toMap(req.getData()); + for (String key : params.keySet()) builder.add(key, params.get(key)); + return builder.build(); + } + + private static RequestBody getFormDataBody(Req req) { + String boundary = "--dio-boundary-" + new SecureRandom().nextInt(42949) + "" + new SecureRandom().nextInt(67296); + MultipartBody.Builder builder = new MultipartBody.Builder(boundary).setType(MultipartBody.FORM); + Map params = Json.toMap(req.getData()); + for (String key : params.keySet()) builder.addFormDataPart(key, params.get(key)); + return builder.build(); + } + + private static void setHeader(QuickJSContext ctx, Response res, JSObject object) { + for (Map.Entry> entry : res.headers().toMultimap().entrySet()) { + if (entry.getValue().size() == 1) object.setProperty(entry.getKey(), entry.getValue().get(0)); + if (entry.getValue().size() >= 2) object.setProperty(entry.getKey(), JSUtil.toArray(ctx, entry.getValue())); + } + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java new file mode 100644 index 00000000..44357c97 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/Crypto.java @@ -0,0 +1,93 @@ +package com.fongmi.quickjs.utils; + +import android.util.Base64; + +import com.github.catvod.utils.Util; + +import java.security.Key; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public class Crypto { + + public static String md5(String text) { + try { + return Util.md5(text); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String aes(String mode, boolean encrypt, String input, boolean inBase64, String key, String iv, boolean outBase64) { + try { + byte[] keyBuf = key.getBytes(); + if (keyBuf.length < 16) keyBuf = Arrays.copyOf(keyBuf, 16); + byte[] ivBuf = iv == null ? new byte[0] : iv.getBytes(); + if (ivBuf.length < 16) ivBuf = Arrays.copyOf(ivBuf, 16); + Cipher cipher = Cipher.getInstance(mode + "Padding"); + SecretKeySpec keySpec = new SecretKeySpec(keyBuf, "AES"); + if (iv == null) cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec); + else cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(ivBuf)); + byte[] inBuf = inBase64 ? Base64.decode(input.replaceAll("_", "/").replaceAll("-", "+"), Base64.DEFAULT) : input.getBytes("UTF-8"); + return outBase64 ? Base64.encodeToString(cipher.doFinal(inBuf), Base64.NO_WRAP) : new String(cipher.doFinal(inBuf), "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + public static String rsa(String mode, boolean pub, boolean encrypt, String input, boolean inBase64, String key, boolean outBase64) { + try { + Key rsaKey = generateKey(pub, key); + int len = getModulusLength(rsaKey); + byte[] outBytes = new byte[0]; + byte[] inBytes = inBase64 ? Base64.decode(input.replaceAll("_", "/").replaceAll("-", "+"), Base64.DEFAULT) : input.getBytes("UTF-8"); + Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); + cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, rsaKey); + int blockLen = encrypt ? len / 8 - 11 : len / 8; + int bufIdx = 0; + while (bufIdx < inBytes.length) { + int bufEndIdx = Math.min(bufIdx + blockLen, inBytes.length); + byte[] tmpInBytes = new byte[bufEndIdx - bufIdx]; + System.arraycopy(inBytes, bufIdx, tmpInBytes, 0, tmpInBytes.length); + byte[] tmpBytes = cipher.doFinal(tmpInBytes); + bufIdx = bufEndIdx; + outBytes = concatArrays(outBytes, tmpBytes); + } + return outBase64 ? Base64.encodeToString(outBytes, Base64.NO_WRAP) : new String(outBytes, "UTF-8"); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + private static Key generateKey(boolean pub, String key) throws Exception { + if (pub) key = key.replaceAll(System.lineSeparator(), "").replace("-----BEGIN PUBLIC KEY-----", "").replace("-----END PUBLIC KEY-----", ""); + else key = key.replaceAll(System.lineSeparator(), "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", ""); + return pub ? KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))) : KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(key, Base64.DEFAULT))); + } + + private static int getModulusLength(Key key) { + if (key instanceof PublicKey) return ((RSAPublicKey) key).getModulus().bitLength(); + else return ((RSAPrivateKey) key).getModulus().bitLength(); + } + + private static byte[] concatArrays(byte[] a, byte[] b) { + int aLen = a.length; + int bLen = b.length; + byte[] result = new byte[aLen + bLen]; + System.arraycopy(a, 0, result, 0, aLen); + System.arraycopy(b, 0, result, aLen, bLen); + return result; + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/JSUtil.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/JSUtil.java new file mode 100644 index 00000000..244f39d1 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/JSUtil.java @@ -0,0 +1,32 @@ +package com.fongmi.quickjs.utils; + +import com.whl.quickjs.wrapper.JSArray; +import com.whl.quickjs.wrapper.JSObject; +import com.whl.quickjs.wrapper.QuickJSContext; + +import java.util.List; +import java.util.Map; + +public class JSUtil { + + public static JSArray toArray(QuickJSContext ctx, List items) { + JSArray array = ctx.createNewJSArray(); + if (items == null || items.isEmpty()) return array; + for (int i = 0; i < items.size(); i++) array.set(items.get(i), i); + return array; + } + + public static JSArray toArray(QuickJSContext ctx, byte[] bytes) { + JSArray array = ctx.createNewJSArray(); + if (bytes == null || bytes.length == 0) return array; + for (int i = 0; i < bytes.length; i++) array.set((int) bytes[i], i); + return array; + } + + public static JSObject toObject(QuickJSContext ctx, Map map) { + JSObject obj = ctx.createNewJSObject(); + if (map == null || map.isEmpty()) return obj; + for (String s : map.keySet()) obj.setProperty(s, map.get(s)); + return obj; + } +} diff --git a/quickjs/src/main/java/com/fongmi/quickjs/utils/Module.java b/quickjs/src/main/java/com/fongmi/quickjs/utils/Module.java new file mode 100644 index 00000000..a7901999 --- /dev/null +++ b/quickjs/src/main/java/com/fongmi/quickjs/utils/Module.java @@ -0,0 +1,59 @@ +package com.fongmi.quickjs.utils; + +import android.net.Uri; + +import com.github.catvod.net.OkHttp; +import com.github.catvod.utils.Asset; +import com.github.catvod.utils.Path; + +import java.io.File; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ConcurrentHashMap; + +public class Module { + + private final ConcurrentHashMap cache; + + private static class Loader { + static volatile Module INSTANCE = new Module(); + } + + public static Module get() { + return Loader.INSTANCE; + } + + public Module() { + this.cache = new ConcurrentHashMap<>(); + } + + public String fetch(String name) { + if (cache.contains(name)) return cache.get(name); + if (name.startsWith("http")) cache.put(name, request(name)); + if (name.startsWith("assets")) cache.put(name, Asset.read(name)); + if (name.startsWith("lib/")) cache.put(name, Asset.read("js/" + name)); + return cache.get(name); + } + + private String request(String url) { + try { + Uri uri = Uri.parse(url); + byte[] data = OkHttp.bytes(url); + File file = Path.js(uri.getLastPathSegment()); + boolean cache = !"127.0.0.1".equals(uri.getHost()); + if (cache) new Thread(() -> Path.write(file, data)).start(); + return new String(data, StandardCharsets.UTF_8); + } catch (Exception e) { + return cache(url); + } + } + + private String cache(String url) { + try { + Uri uri = Uri.parse(url); + File file = Path.js(uri.getLastPathSegment()); + return file.exists() ? Path.read(file) : ""; + } catch (Exception e) { + return ""; + } + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..0480d6c2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,25 @@ +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + google() + } +} +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + google() + flatDir { dirs "$rootDir/app/libs" } + maven { url "https://jitpack.io" } + maven { + url "http://4thline.org/m2" + allowInsecureProtocol = true + } + } +} +include ':app' +include ':catvod' +include ':chaquo' +include ':quickjs' +rootProject.name = "TV" diff --git a/thunder/.gitignore b/thunder/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/thunder/.gitignore @@ -0,0 +1 @@ +/build diff --git a/thunder/build.gradle b/thunder/build.gradle new file mode 100644 index 00000000..7f894559 --- /dev/null +++ b/thunder/build.gradle @@ -0,0 +1,18 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.ghost.thunder' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + } +} + +dependencies { + implementation project(':catvod') +} \ No newline at end of file diff --git a/thunder/src/main/AndroidManifest.xml b/thunder/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/thunder/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/thunder/src/main/java/com/xunlei/downloadlib/Util.java b/thunder/src/main/java/com/xunlei/downloadlib/Util.java new file mode 100644 index 00000000..5d366ee6 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/Util.java @@ -0,0 +1,23 @@ +package com.xunlei.downloadlib; + +import java.text.DecimalFormat; +import java.util.Arrays; +import java.util.List; + +public class Util { + + private static final List VIDEO = Arrays.asList("avi", "flv", "mkv", "mov", "mp4", "mpeg", "mpe", "mpg", "wmv"); + private static final List AUDIO = Arrays.asList("aac", "ape", "flac", "mp3", "m4a", "ogg"); + private static final String[] UNITS = new String[]{"bytes", "KB", "MB", "GB", "TB"}; + private static final long MINIMAL = 30 * 1024 * 1024; + + public static boolean isMedia(String ext, long size) { + return (VIDEO.contains(ext) || AUDIO.contains(ext)) && size > MINIMAL; + } + + public static String size(long size) { + if (size <= 0) return ""; + int group = (int) (Math.log10(size) / Math.log10(1024)); + return "[" + new DecimalFormat("###0.#").format(size / Math.pow(1024, group)) + " " + UNITS[group] + "] "; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/XLDownloadManager.java b/thunder/src/main/java/com/xunlei/downloadlib/XLDownloadManager.java new file mode 100644 index 00000000..4509ee4a --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/XLDownloadManager.java @@ -0,0 +1,152 @@ +package com.xunlei.downloadlib; + +import android.content.Context; +import android.os.Build; + +import com.github.catvod.Init; +import com.github.catvod.utils.Prefers; +import com.xunlei.downloadlib.android.XLUtil; +import com.xunlei.downloadlib.parameter.BtIndexSet; +import com.xunlei.downloadlib.parameter.BtSubTaskDetail; +import com.xunlei.downloadlib.parameter.BtTaskParam; +import com.xunlei.downloadlib.parameter.EmuleTaskParam; +import com.xunlei.downloadlib.parameter.GetDownloadLibVersion; +import com.xunlei.downloadlib.parameter.GetFileName; +import com.xunlei.downloadlib.parameter.GetTaskId; +import com.xunlei.downloadlib.parameter.InitParam; +import com.xunlei.downloadlib.parameter.MagnetTaskParam; +import com.xunlei.downloadlib.parameter.P2spTaskParam; +import com.xunlei.downloadlib.parameter.ThunderUrlInfo; +import com.xunlei.downloadlib.parameter.TorrentInfo; +import com.xunlei.downloadlib.parameter.XLTaskInfo; +import com.xunlei.downloadlib.parameter.XLTaskLocalUrl; + +public class XLDownloadManager { + + private XLLoader loader; + private Context context; + + public XLDownloadManager() { + this.context = Init.context(); + this.loader = new XLLoader(); + this.init(); + } + + public void init() { + InitParam param = new InitParam(context.getFilesDir().getPath()); + loader.init(param.getSoKey(), "com.android.providers.downloads", param.mAppVersion, "", getPeerId(), getGuid(), param.mStatSavePath, param.mStatCfgSavePath, 0, param.mPermissionLevel, param.mQueryConfOnInit); + getDownloadLibVersion(new GetDownloadLibVersion()); + setOSVersion(Build.VERSION.INCREMENTAL + "_alpha"); + setLocalProperty("PhoneModel", Build.MODEL); + setStatReportSwitch(false); + setSpeedLimit(-1, -1); + } + + public void release() { + if (loader != null) loader.unInit(); + context = null; + loader = null; + } + + private String getPeerId() { + String uuid = Prefers.getString("phoneId5"); + if (uuid.isEmpty()) Prefers.put("phoneId5", uuid = XLUtil.getPeerId()); + return uuid; + } + + private String getGuid() { + return XLUtil.getGuid(); + } + + public void releaseTask(long taskId) { + loader.releaseTask(taskId); + } + + public void startTask(long taskId) { + loader.startTask(taskId); + } + + public void stopTask(long taskId) { + loader.stopTask(taskId); + } + + public void getTaskInfo(long taskId, int i, XLTaskInfo taskInfo) { + loader.getTaskInfo(taskId, i, taskInfo); + } + + public void getLocalUrl(String filePath, XLTaskLocalUrl localUrl) { + loader.getLocalUrl(filePath, localUrl); + } + + public void getDownloadLibVersion(GetDownloadLibVersion version) { + loader.getDownloadLibVersion(version); + } + + public void setStatReportSwitch(boolean value) { + loader.setStatReportSwitch(value); + } + + private void setLocalProperty(String key, String value) { + loader.setLocalProperty(key, value); + } + + public void setOSVersion(String str) { + loader.setMiUiVersion(str); + } + + public void setOriginUserAgent(long taskId, String userAgent) { + loader.setOriginUserAgent(taskId, userAgent); + } + + public void setDownloadTaskOrigin(long taskId, String str) { + loader.setDownloadTaskOrigin(taskId, str); + } + + public void setTaskGsState(long j, int i, int i2) { + loader.setTaskGsState(j, i, i2); + } + + public int createP2spTask(P2spTaskParam param, GetTaskId taskId) { + return loader.createP2spTask(param.mUrl, param.mRefUrl, param.mCookie, param.mUser, param.mPass, param.mFilePath, param.mFileName, param.mCreateMode, param.mSeqId, taskId); + } + + public int createBtMagnetTask(MagnetTaskParam param, GetTaskId taskId) { + return loader.createBtMagnetTask(param.mUrl, param.mFilePath, param.mFileName, taskId); + } + + public int createEmuleTask(EmuleTaskParam param, GetTaskId taskId) { + return loader.createEmuleTask(param.mUrl, param.mFilePath, param.mFileName, param.mCreateMode, param.mSeqId, taskId); + } + + public int createBtTask(BtTaskParam param, GetTaskId taskId) { + return loader.createBtTask(param.mTorrentPath, param.mFilePath, param.mMaxConcurrent, param.mCreateMode, param.mSeqId, taskId); + } + + public void getTorrentInfo(TorrentInfo info) { + loader.getTorrentInfo(info.getFile().getAbsolutePath(), info); + } + + public void getBtSubTaskInfo(long taskId, int index, BtSubTaskDetail detail) { + loader.getBtSubTaskInfo(taskId, index, detail); + } + + public void deselectBtSubTask(long taskId, BtIndexSet btIndexSet) { + loader.deselectBtSubTask(taskId, btIndexSet); + } + + public String parserThunderUrl(String url) { + ThunderUrlInfo thunderUrlInfo = new ThunderUrlInfo(); + loader.parserThunderUrl(url, thunderUrlInfo); + return thunderUrlInfo.mUrl; + } + + public String getFileNameFromUrl(String url) { + GetFileName getFileName = new GetFileName(); + loader.getFileNameFromUrl(url, getFileName); + return getFileName.getFileName(); + } + + public void setSpeedLimit(long min, long max) { + loader.setSpeedLimit(min, max); + } +} \ No newline at end of file diff --git a/thunder/src/main/java/com/xunlei/downloadlib/XLLoader.java b/thunder/src/main/java/com/xunlei/downloadlib/XLLoader.java new file mode 100644 index 00000000..9bc3de57 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/XLLoader.java @@ -0,0 +1,67 @@ +package com.xunlei.downloadlib; + +import com.xunlei.downloadlib.parameter.BtIndexSet; +import com.xunlei.downloadlib.parameter.BtSubTaskDetail; +import com.xunlei.downloadlib.parameter.GetDownloadLibVersion; +import com.xunlei.downloadlib.parameter.GetFileName; +import com.xunlei.downloadlib.parameter.GetTaskId; +import com.xunlei.downloadlib.parameter.ThunderUrlInfo; +import com.xunlei.downloadlib.parameter.TorrentInfo; +import com.xunlei.downloadlib.parameter.XLTaskInfo; +import com.xunlei.downloadlib.parameter.XLTaskLocalUrl; + +class XLLoader { + + public XLLoader() { + System.loadLibrary("xl_stat"); + System.loadLibrary("xl_thunder_sdk"); + } + + public native int createBtMagnetTask(String str, String str2, String str3, GetTaskId getTaskId); + + public native int createBtTask(String str, String str2, int i, int i2, int i3, GetTaskId getTaskId); + + public native int createEmuleTask(String str, String str2, String str3, int i, int i2, GetTaskId getTaskId); + + public native int createP2spTask(String str, String str2, String str3, String str4, String str5, String str6, String str7, int i, int i2, GetTaskId getTaskId); + + public native int deselectBtSubTask(long j, BtIndexSet btIndexSet); + + public native int getBtSubTaskInfo(long j, int i, BtSubTaskDetail btSubTaskDetail); + + public native int getDownloadLibVersion(GetDownloadLibVersion getDownloadLibVersion); + + public native int getFileNameFromUrl(String str, GetFileName getFileName); + + public native int getLocalUrl(String str, XLTaskLocalUrl xLTaskLocalUrl); + + public native int getTaskInfo(long j, int i, XLTaskInfo xLTaskInfo); + + public native int getTorrentInfo(String str, TorrentInfo torrentInfo); + + public native int init(String str, String str2, String str3, String str4, String str5, String str6, String str7, String str8, int i, int i2, int i3); + + public native int parserThunderUrl(String str, ThunderUrlInfo thunderUrlInfo); + + public native int releaseTask(long j); + + public native int setDownloadTaskOrigin(long j, String str); + + public native int setLocalProperty(String str, String str2); + + public native int setMiUiVersion(String str); + + public native int setOriginUserAgent(long j, String str); + + public native int setStatReportSwitch(boolean z); + + public native int setSpeedLimit(long j, long j2); + + public native int setTaskGsState(long j, int i, int i2); + + public native int startTask(long j); + + public native int stopTask(long j); + + public native int unInit(); +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/XLTaskHelper.java b/thunder/src/main/java/com/xunlei/downloadlib/XLTaskHelper.java new file mode 100644 index 00000000..a0aa5b7e --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/XLTaskHelper.java @@ -0,0 +1,166 @@ +package com.xunlei.downloadlib; + +import com.github.catvod.utils.Path; +import com.xunlei.downloadlib.parameter.BtIndexSet; +import com.xunlei.downloadlib.parameter.BtSubTaskDetail; +import com.xunlei.downloadlib.parameter.BtTaskParam; +import com.xunlei.downloadlib.parameter.EmuleTaskParam; +import com.xunlei.downloadlib.parameter.GetTaskId; +import com.xunlei.downloadlib.parameter.MagnetTaskParam; +import com.xunlei.downloadlib.parameter.P2spTaskParam; +import com.xunlei.downloadlib.parameter.TorrentFileInfo; +import com.xunlei.downloadlib.parameter.TorrentInfo; +import com.xunlei.downloadlib.parameter.XLConstant; +import com.xunlei.downloadlib.parameter.XLTaskInfo; +import com.xunlei.downloadlib.parameter.XLTaskLocalUrl; + +import java.io.File; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class XLTaskHelper { + + private XLDownloadManager manager; + private AtomicInteger seq; + + private static class Loader { + static volatile XLTaskHelper INSTANCE = new XLTaskHelper(); + } + + public synchronized static XLTaskHelper get() { + return Loader.INSTANCE; + } + + private synchronized AtomicInteger getSeq() { + return seq = seq == null ? new AtomicInteger(0) : seq; + } + + private synchronized XLDownloadManager getManager() { + return manager = manager == null ? new XLDownloadManager() : manager; + } + + private synchronized GetTaskId startTask(GetTaskId taskId, int index) { + getManager().setTaskGsState(taskId.getTaskId(), index, 2); + getManager().startTask(taskId.getTaskId()); + return taskId; + } + + public synchronized GetTaskId parse(String url, File savePath) { + if (url.startsWith("file://")) return new GetTaskId(url, savePath); + if (url.startsWith("thunder://")) url = getManager().parserThunderUrl(url); + String fileName = getManager().getFileNameFromUrl(url); + GetTaskId taskId = new GetTaskId(savePath, fileName, url); + if (!url.startsWith("magnet:?")) return taskId; + MagnetTaskParam param = new MagnetTaskParam(); + param.setFilePath(savePath.getAbsolutePath()); + param.setFileName(fileName); + param.setUrl(url); + int code = getManager().createBtMagnetTask(param, taskId); + if (code != XLConstant.XLErrorCode.NO_ERROR) return taskId; + return startTask(taskId, 0); + } + + public synchronized GetTaskId addThunderTask(String url, File savePath) { + String fileName = getManager().getFileNameFromUrl(url); + GetTaskId taskId = new GetTaskId(savePath, fileName, url); + if (url.startsWith("ftp://")) { + P2spTaskParam param = new P2spTaskParam(); + param.setFilePath(savePath.getAbsolutePath()); + param.setSeqId(getSeq().incrementAndGet()); + param.setFileName(fileName); + param.setCreateMode(1); + param.setUrl(url); + param.setCookie(""); + param.setRefUrl(""); + param.setUser(""); + param.setPass(""); + int code = getManager().createP2spTask(param, taskId); + if (code != XLConstant.XLErrorCode.NO_ERROR) return taskId; + } else if (url.startsWith("ed2k://")) { + EmuleTaskParam param = new EmuleTaskParam(); + param.setFilePath(savePath.getAbsolutePath()); + param.setSeqId(getSeq().incrementAndGet()); + param.setFileName(fileName); + param.setCreateMode(1); + param.setUrl(url); + int code = getManager().createEmuleTask(param, taskId); + if (code != XLConstant.XLErrorCode.NO_ERROR) return taskId; + } + getManager().setDownloadTaskOrigin(taskId.getTaskId(), "out_app/out_app_paste"); + getManager().setOriginUserAgent(taskId.getTaskId(), "AndroidDownloadManager/4.4.4 (Linux; U; Android 4.4.4; Build/KTU84Q)"); + return startTask(taskId, 0); + } + + public synchronized GetTaskId addTorrentTask(File torrent, File savePath, int index) { + TorrentInfo torrentInfo = getTorrentInfo(torrent); + TorrentFileInfo[] fileInfos = torrentInfo.mSubFileInfo; + BtTaskParam taskParam = new BtTaskParam(); + taskParam.setCreateMode(1); + taskParam.setMaxConcurrent(3); + taskParam.setSeqId(getSeq().incrementAndGet()); + taskParam.setFilePath(savePath.getAbsolutePath()); + taskParam.setTorrentPath(torrent.getAbsolutePath()); + GetTaskId taskId = new GetTaskId(savePath); + int code = getManager().createBtTask(taskParam, taskId); + if (code != XLConstant.XLErrorCode.NO_ERROR) return taskId; + if (fileInfos.length > 1) { + List list = new CopyOnWriteArrayList<>(); + for (TorrentFileInfo fileInfo : fileInfos) { + if (fileInfo.mFileIndex != index) { + list.add(fileInfo.mFileIndex); + } + } + BtIndexSet btIndexSet = new BtIndexSet(list.size()); + for (int i = 0; i < list.size(); i++) btIndexSet.mIndexSet[i] = list.get(i); + getManager().deselectBtSubTask(taskId.getTaskId(), btIndexSet); + } + return startTask(taskId, index); + } + + public synchronized TorrentInfo getTorrentInfo(File file) { + TorrentInfo torrentInfo = new TorrentInfo(file); + getManager().getTorrentInfo(torrentInfo); + return torrentInfo; + } + + public synchronized String getLocalUrl(File file) { + XLTaskLocalUrl localUrl = new XLTaskLocalUrl(); + getManager().getLocalUrl(file.getAbsolutePath(), localUrl); + return localUrl.mStrUrl; + } + + public synchronized void deleteTask(GetTaskId taskId) { + new Thread(() -> deleteFile(taskId.getSavePath())).start(); + stopTask(taskId); + } + + private synchronized void deleteFile(File dir) { + if (dir.isDirectory()) for (File file : Path.list(dir)) deleteFile(file); + if (!dir.getAbsolutePath().endsWith(".torrent")) dir.delete(); + } + + public synchronized void stopTask(GetTaskId taskId) { + getManager().stopTask(taskId.getTaskId()); + getManager().releaseTask(taskId.getTaskId()); + } + + public synchronized XLTaskInfo getTaskInfo(GetTaskId taskId) { + XLTaskInfo taskInfo = new XLTaskInfo(); + if (taskId.getSaveFile().exists()) taskInfo.setTaskStatus(2); + else getManager().getTaskInfo(taskId.getTaskId(), 1, taskInfo); + return taskInfo; + } + + public synchronized BtSubTaskDetail getBtSubTaskInfo(GetTaskId taskId, int index) { + BtSubTaskDetail subTaskDetail = new BtSubTaskDetail(); + getManager().getBtSubTaskInfo(taskId.getTaskId(), index, subTaskDetail); + return subTaskDetail; + } + + public synchronized void release() { + if (manager != null) manager.release(); + manager = null; + seq = null; + } +} \ No newline at end of file diff --git a/thunder/src/main/java/com/xunlei/downloadlib/android/XLUtil.java b/thunder/src/main/java/com/xunlei/downloadlib/android/XLUtil.java new file mode 100644 index 00000000..ac67468b --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/android/XLUtil.java @@ -0,0 +1,47 @@ +package com.xunlei.downloadlib.android; + +import android.util.Base64; + +import java.security.SecureRandom; +import java.util.UUID; + +public class XLUtil { + + public static String getMAC() { + return random("ABCDEF0123456", 12).toUpperCase(); + } + + public static String getIMEI() { + return random("0123456", 15); + } + + public static String getPeerId() { + String uuid = UUID.randomUUID().toString().replace("-", ""); + uuid = uuid.substring(0, 12).toUpperCase() + "004V"; + return uuid; + } + + private static String random(String base, int length) { + SecureRandom random = new SecureRandom(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < length; i++) sb.append(base.charAt(random.nextInt(base.length()))); + return sb.toString(); + } + + public static String getGuid() { + return getIMEI() + "_" + getMAC(); + } + + public static String generateAppKey(String str, short s, byte b) { + int length = str.length(); + int i = length + 1; + byte[] bArr = new byte[(i + 2 + 1)]; + byte[] bytes = str.getBytes(); + System.arraycopy(bytes, 0, bArr, 0, bytes.length); + bArr[length] = 0; + bArr[i] = (byte) (s & 255); + bArr[length + 2] = (byte) ((s >> 8) & 255); + bArr[length + 3] = b; + return new String(Base64.encode(bArr, 0)).trim(); + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtIndexSet.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtIndexSet.java new file mode 100644 index 00000000..072d0d58 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtIndexSet.java @@ -0,0 +1,10 @@ +package com.xunlei.downloadlib.parameter; + +public class BtIndexSet { + + public int[] mIndexSet; + + public BtIndexSet(int i) { + this.mIndexSet = new int[i]; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtSubTaskDetail.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtSubTaskDetail.java new file mode 100644 index 00000000..86c12b92 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtSubTaskDetail.java @@ -0,0 +1,8 @@ +package com.xunlei.downloadlib.parameter; + +public class BtSubTaskDetail { + + public int mFileIndex; + public boolean mIsSelect; + public XLTaskInfo mTaskInfo = new XLTaskInfo(); +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtTaskParam.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtTaskParam.java new file mode 100644 index 00000000..703203a3 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/BtTaskParam.java @@ -0,0 +1,30 @@ +package com.xunlei.downloadlib.parameter; + +public class BtTaskParam { + + public int mSeqId; + public int mCreateMode; + public int mMaxConcurrent; + public String mFilePath; + public String mTorrentPath; + + public void setSeqId(int seqId) { + this.mSeqId = seqId; + } + + public void setCreateMode(int createMode) { + this.mCreateMode = createMode; + } + + public void setMaxConcurrent(int maxConcurrent) { + this.mMaxConcurrent = maxConcurrent; + } + + public void setFilePath(String filePath) { + this.mFilePath = filePath; + } + + public void setTorrentPath(String torrentPath) { + this.mTorrentPath = torrentPath; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/EmuleTaskParam.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/EmuleTaskParam.java new file mode 100644 index 00000000..8168746b --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/EmuleTaskParam.java @@ -0,0 +1,30 @@ +package com.xunlei.downloadlib.parameter; + +public class EmuleTaskParam { + + public int mCreateMode; + public String mFileName; + public String mFilePath; + public int mSeqId; + public String mUrl; + + public void setFileName(String str) { + this.mFileName = str; + } + + public void setFilePath(String str) { + this.mFilePath = str; + } + + public void setUrl(String str) { + this.mUrl = str; + } + + public void setCreateMode(int i) { + this.mCreateMode = i; + } + + public void setSeqId(int i) { + this.mSeqId = i; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/ErrorCode.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/ErrorCode.java new file mode 100644 index 00000000..e05eb6e5 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/ErrorCode.java @@ -0,0 +1,28 @@ +package com.xunlei.downloadlib.parameter; + +public class ErrorCode { + + public static String get(int code) { + switch (code) { + case 9125: + case 111120: + return "檔案名稱太長"; + case 9301: + case 111085: + return "儲存空間不足"; + case 9304: + case 114001: + case 114004: + case 114005: + case 114006: + case 114007: + case 114011: + case 111154: + return "版權限制"; + case 114101: + return "已失效"; + default: + return "ErrorCode=" + code; + } + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetDownloadLibVersion.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetDownloadLibVersion.java new file mode 100644 index 00000000..3dfbaabf --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetDownloadLibVersion.java @@ -0,0 +1,6 @@ +package com.xunlei.downloadlib.parameter; + +public class GetDownloadLibVersion { + + public String mVersion; +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetFileName.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetFileName.java new file mode 100644 index 00000000..06e82b82 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetFileName.java @@ -0,0 +1,10 @@ +package com.xunlei.downloadlib.parameter; + +public class GetFileName { + + public String mFileName; + + public String getFileName() { + return mFileName; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetTaskId.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetTaskId.java new file mode 100644 index 00000000..7ef857c8 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/GetTaskId.java @@ -0,0 +1,52 @@ +package com.xunlei.downloadlib.parameter; + +import com.github.catvod.utils.Path; + +import java.io.File; + +public class GetTaskId { + + public long mTaskId; + public File mSavePath; + public String mFileName; + public String mRealUrl; + + public GetTaskId(File savePath) { + this.mSavePath = savePath; + } + + public GetTaskId(String url, File savePath) { + File file = new File(url.substring(7)); + File dest = new File(savePath, file.getName()); + Path.copy(file, dest); + this.mFileName = file.getName(); + this.mSavePath = savePath; + this.mRealUrl = url; + } + + public GetTaskId(File savePath, String fileName, String realUrl) { + this.mSavePath = savePath; + this.mFileName = fileName; + this.mRealUrl = realUrl; + } + + public long getTaskId() { + return this.mTaskId; + } + + public File getSavePath() { + return mSavePath; + } + + public String getFileName() { + return mFileName; + } + + public String getRealUrl() { + return mRealUrl; + } + + public File getSaveFile() { + return new File(getSavePath(), getFileName()); + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/InitParam.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/InitParam.java new file mode 100644 index 00000000..da217833 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/InitParam.java @@ -0,0 +1,33 @@ +package com.xunlei.downloadlib.parameter; + +import android.util.Base64; + +import com.xunlei.downloadlib.android.XLUtil; + +import java.nio.charset.StandardCharsets; + +public class InitParam { + + public String mAppKey; + public String mAppVersion; + public String mStatSavePath; + public String mStatCfgSavePath; + public int mQueryConfOnInit; + public int mPermissionLevel; + + public InitParam(String path) { + this.mAppKey = "xzNjAwMQ^^yb==0^852^083dbcff^cee25055f125a2fde"; + this.mAppVersion = "21.01.07.800002"; + this.mPermissionLevel = 1; + this.mQueryConfOnInit = 0; + this.mStatSavePath = path; + this.mStatCfgSavePath = path; + } + + public String getSoKey() { + String[] split = mAppKey.split("=="); + String replace = split[0].replace('^', '='); + String str = new String(Base64.decode(replace.substring(2, replace.length() - 2), 0), StandardCharsets.UTF_8); + return XLUtil.generateAppKey("com.android.providers.downloads", Short.parseShort(str.split(";")[0]), (byte) 1); + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/MagnetTaskParam.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/MagnetTaskParam.java new file mode 100644 index 00000000..8f6cade9 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/MagnetTaskParam.java @@ -0,0 +1,20 @@ +package com.xunlei.downloadlib.parameter; + +public class MagnetTaskParam { + + public String mFileName; + public String mFilePath; + public String mUrl; + + public void setUrl(String str) { + this.mUrl = str; + } + + public void setFileName(String str) { + this.mFileName = str; + } + + public void setFilePath(String str) { + this.mFilePath = str; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/P2spTaskParam.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/P2spTaskParam.java new file mode 100644 index 00000000..4e636f81 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/P2spTaskParam.java @@ -0,0 +1,50 @@ +package com.xunlei.downloadlib.parameter; + +public class P2spTaskParam { + + public String mCookie; + public int mCreateMode; + public String mFileName; + public String mFilePath; + public String mPass; + public String mRefUrl; + public int mSeqId; + public String mUrl; + public String mUser; + + public void setUrl(String str) { + this.mUrl = str; + } + + public void setFileName(String str) { + this.mFileName = str; + } + + public void setFilePath(String str) { + this.mFilePath = str; + } + + public void setCookie(String str) { + this.mCookie = str; + } + + public void setRefUrl(String str) { + this.mRefUrl = str; + } + + public void setUser(String str) { + this.mUser = str; + } + + public void setPass(String str) { + this.mPass = str; + } + + public void setCreateMode(int i) { + this.mCreateMode = i; + } + + public void setSeqId(int i) { + this.mSeqId = i; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/ThunderUrlInfo.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/ThunderUrlInfo.java new file mode 100644 index 00000000..b9de8326 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/ThunderUrlInfo.java @@ -0,0 +1,6 @@ +package com.xunlei.downloadlib.parameter; + +public class ThunderUrlInfo { + + public String mUrl; +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/TorrentFileInfo.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/TorrentFileInfo.java new file mode 100644 index 00000000..73dccb7c --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/TorrentFileInfo.java @@ -0,0 +1,67 @@ +package com.xunlei.downloadlib.parameter; + +import android.text.TextUtils; + +import com.xunlei.downloadlib.Util; + +import java.io.File; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +public class TorrentFileInfo { + + public boolean isSelected; + public String mFileName; + public String mSubPath; + public long mFileSize; + public int mFileIndex; + public int mRealIndex; + public File mFile; + + public String getFileName() { + return TextUtils.isEmpty(mFileName) ? "" : mFileName; + } + + public long getFileSize() { + return mFileSize; + } + + public int getFileIndex() { + return mFileIndex; + } + + public File getFile() { + return mFile; + } + + public TorrentFileInfo file(File file) { + this.mFile = file; + return this; + } + + public String getSize() { + return Util.size(mFileSize); + } + + public String getPlayUrl() { + return "magnet://" + getFile().getAbsolutePath() + "?name=" + getFileName() + "&index=" + getFileIndex(); + } + + public String getExt() { + return getFileName().contains(".") ? getFileName().substring(getFileName().lastIndexOf(".") + 1).toLowerCase() : ""; + } + + public static class Sorter implements Comparator { + + public static List sort(List items) { + if (items.size() > 1) Collections.sort(items, new Sorter()); + return items; + } + + @Override + public int compare(TorrentFileInfo o1, TorrentFileInfo o2) { + return o1.getFileName().compareTo(o2.getFileName()); + } + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/TorrentInfo.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/TorrentInfo.java new file mode 100644 index 00000000..1aa3aa45 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/TorrentInfo.java @@ -0,0 +1,36 @@ +package com.xunlei.downloadlib.parameter; + +import com.xunlei.downloadlib.Util; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class TorrentInfo { + + public TorrentFileInfo[] mSubFileInfo; + public String mMultiFileBaseFolder; + public boolean mIsMultiFiles; + public String mInfoHash; + public int mFileCount; + public File mFile; + + public TorrentInfo(File file) { + this.mFile = file; + } + + public File getFile() { + return mFile; + } + + private TorrentFileInfo[] getSubFileInfo() { + return mSubFileInfo == null ? new TorrentFileInfo[0] : mSubFileInfo; + } + + public List getMedias() { + List items = new ArrayList<>(); + for (TorrentFileInfo item : getSubFileInfo()) if (Util.isMedia(item.getExt(), item.getFileSize())) items.add(item.file(getFile())); + TorrentFileInfo.Sorter.sort(items); + return items; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLConstant.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLConstant.java new file mode 100644 index 00000000..cb122274 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLConstant.java @@ -0,0 +1,197 @@ +package com.xunlei.downloadlib.parameter; + +public class XLConstant { + + public enum XLManagerStatus { + MANAGER_UNINIT, + MANAGER_INIT_FAIL, + MANAGER_RUNNING + } + + public static class XLErrorCode { + + public static final int ADD_RESOURCE_ERROR = 9122; + public static final int ALREADY_INIT = 9101; + public static final int APPKEY_CHECKER_ERROR = 9901; + public static final int APPNAME_APPKEY_ERROR = 9116; + public static final int ASYN_FILE_E_BASE = 111300; + public static final int ASYN_FILE_E_EMPTY_FILE = 111305; + public static final int ASYN_FILE_E_FILE_CLOSING = 111308; + public static final int ASYN_FILE_E_FILE_NOT_OPEN = 111303; + public static final int ASYN_FILE_E_FILE_REOPEN = 111304; + public static final int ASYN_FILE_E_FILE_SIZE_LESS = 111306; + public static final int ASYN_FILE_E_OP_BUSY = 111302; + public static final int ASYN_FILE_E_OP_NONE = 111301; + public static final int ASYN_FILE_E_TOO_MUCH_DATA = 111307; + public static final int BAD_DIR_PATH = 111083; + public static final int BT_SUB_TASK_NOT_SELECT = 9306; + public static final int BUFFER_OVERFLOW = 111039; + public static final int COMMON_ERRCODE_BASE = 111024; + public static final int CONF_MGR_ERRCODE_BASE = 111159; + public static final int CONTINUE_NO_NAME = 9115; + public static final int CORRECT_CDN_ERROR = 111180; + public static final int CORRECT_TIMES_TOO_MUCH = 111179; + public static final int CREATE_FILE_FAIL = 111139; + public static final int CREATE_THREAD_ERROR = 9117; + public static final int DATA_MGR_ERRCODE_BASE = 111119; + public static final int DISK_FULL = 9110; + public static final int DISPATCHER_ERRCODE_BASE = 111118; + public static final int DNS_INVALID_ADDR = 111078; + public static final int DNS_NO_SERVER = 111077; + public static final int DOWNLOAD_MANAGER_ERROR = 9900; + public static final int DYNAMIC_PARAM_FAIL = 9114; + public static final int ERROR_INVALID_INADDR = 111050; + public static final int ERR_DPLAY_ALL_SEND_COMPLETE = 118000; + public static final int ERR_DPLAY_BROKEN_SOCKET_RECV = 118307; + public static final int ERR_DPLAY_BROKEN_SOCKET_SEND = 118306; + public static final int ERR_DPLAY_CLIENT_ACTIVE_DISCONNECT = 118001; + public static final int ERR_DPLAY_DO_DOWNLOAD_FAIL = 118305; + public static final int ERR_DPLAY_DO_READFILE_FAIL = 118311; + public static final int ERR_DPLAY_EV_SEND_TIMTOUT = 118310; + public static final int ERR_DPLAY_HANDLE_DOWNLOAD_FAILED = 118302; + public static final int ERR_DPLAY_NOT_FOUND = 118005; + public static final int ERR_DPLAY_PLAY_FILE_NOT_EXIST = 118304; + public static final int ERR_DPLAY_RECV_STATE_INVALID = 118308; + public static final int ERR_DPLAY_SEND_FAILED = 118300; + public static final int ERR_DPLAY_SEND_RANGE_INVALID = 118301; + public static final int ERR_DPLAY_SEND_STATE_INVALID = 118309; + public static final int ERR_DPLAY_TASK_FINISH_CANNT_DOWNLOAD = 118004; + public static final int ERR_DPLAY_TASK_FINISH_CONTINUE = 118003; + public static final int ERR_DPLAY_TASK_FINISH_DESTROY = 118002; + public static final int ERR_DPLAY_UNKNOW_HTTP_METHOD = 118303; + public static final int ERR_INVALID_ADDRESS_FAMILY = 116001; + public static final int ERR_P2P_ALLOC_MEM_ERR = 11313; + public static final int ERR_P2P_BROKER_CONNECT = 11308; + public static final int ERR_P2P_CONNECT_FAILED = 11311; + public static final int ERR_P2P_CONNECT_UPLOAD_SLOW = 11312; + public static final int ERR_P2P_HANDSHAKE_RESP_FAIL = 11303; + public static final int ERR_P2P_INVALID_COMMAND = 11309; + public static final int ERR_P2P_INVALID_PARAM = 11310; + public static final int ERR_P2P_NOT_SUPPORT_UDT = 11307; + public static final int ERR_P2P_REMOTE_UNKNOWN_MY_CMD = 11306; + public static final int ERR_P2P_REQUEST_RESP_FAIL = 11304; + public static final int ERR_P2P_SEND_HANDSHAKE = 11314; + public static final int ERR_P2P_UPLOAD_OVER_MAX = 11305; + public static final int ERR_P2P_VERSION_NOT_SUPPORT = 11301; + public static final int ERR_P2P_WAITING_CLOSE = 11302; + public static final int ERR_PTL_GET_PEERSN_FAILED = 112600; + public static final int ERR_PTL_PEER_OFFLINE = 112500; + public static final int ERR_PTL_PROTOCOL_NOT_SUPPORT = 112400; + public static final int FILE_CANNOT_TRUNCATE = 111084; + public static final int FILE_CFG_ERASE_ERROR = 111130; + public static final int FILE_CFG_MAGIC_ERROR = 111131; + public static final int FILE_CFG_READ_ERROR = 111132; + public static final int FILE_CFG_READ_HEADER_ERROR = 111134; + public static final int FILE_CFG_RESOLVE_ERROR = 111135; + public static final int FILE_CFG_TRY_FIX = 111129; + public static final int FILE_CFG_WRITE_ERROR = 111133; + public static final int FILE_CREATING = 111145; + public static final int FILE_EXISTED = 9109; + public static final int FILE_INVALID_PARA = 111144; + public static final int FILE_NAME_TOO_LONG = 9125; + public static final int FILE_NOT_EXIST = 111143; + public static final int FILE_PATH_TOO_LONG = 111120; + public static final int FILE_SIZE_NOT_BELIEVE = 111141; + public static final int FILE_SIZE_TOO_SMALL = 111142; + public static final int FILE_TOO_BIG = 111086; + public static final int FIL_INFO_INVALID_DATA = 111146; + public static final int FIL_INFO_RECVED_DATA = 111147; + public static final int FULL_PATH_NAME_OCCUPIED = 9128; + public static final int FULL_PATH_NAME_TOO_LONG = 9127; + public static final int FUNCTION_NOT_SUPPORT = 9123; + public static final int HTTP_HUB_CLIENT_E_BASE = 115100; + public static final int HTTP_SERVER_NOT_START = 9400; + public static final int INDEX_NOT_READY = 9303; + public static final int INSUFFICIENT_DISK_SPACE = 111085; + public static final int INVALID_ARGUMENT = 111041; + public static final int INVALID_ITERATOR = 111038; + public static final int INVALID_SOCKET_DESCRIPTOR = 111048; + public static final int INVALID_TIMER_INDEX = 111074; + public static final int IP6_ERRCODE_BASE = 116000; + public static final int IP6_INVALID_IN6ADDR = 116002; + public static final int IP6_NOT_SUPPORT_SSL = 116003; + public static final int MAP_DUPLICATE_KEY = 111036; + public static final int MAP_KEY_NOT_FOUND = 111037; + public static final int MAP_UNINIT = 111035; + public static final int NET_BROKEN_PIPE = 111170; + public static final int NET_CONNECTION_REFUSED = 111171; + public static final int NET_CONNECT_SSL_ERR = 111169; + public static final int NET_NORMAL_CLOSE = 111175; + public static final int NET_OP_CANCEL = 111173; + public static final int NET_REACTOR_ERRCODE_BASE = 111168; + public static final int NET_SSL_GET_FD_ERROR = 111172; + public static final int NET_UNKNOWN_ERROR = 111174; + public static final int NOT_FULL_PATH_NAME = 9404; + public static final int NOT_IMPLEMENT = 111057; + public static final int NO_ENOUGH_BUFFER = 9301; + public static final int NO_ERROR = 9000; + public static final int ONE_PATH_LEVEL_NAME_TOO_LONG = 9126; + public static final int OPEN_FILE_ERR = 111128; + public static final int OPEN_OLD_FILE_FAIL = 111140; + public static final int OUT_OF_FIXED_MEMORY = 111032; + public static final int OUT_OF_MEMORY = 111026; + public static final int P2P_PIPE_ERRCODE_BASE = 11300; + public static final int PARAM_ERROR = 9112; + public static final int PAUSE_TASK_WRITE_CFG_ERR = 117000; + public static final int PAUSE_TASK_WRITE_DATA_TIMEOUT = 117001; + public static final int PRIOR_TASK_FINISH = 9308; + public static final int PRIOR_TASK_NO_INDEX = 9307; + public static final int QUEUE_NO_ROOM = 111033; + public static final int READ_FILE_ERR = 111126; + public static final int REDIRECT_TOO_MUCH = 111181; + public static final int RES_QUERY_E_BASE = 115000; + public static final int SCHEMA_NOT_SUPPORT = 9113; + public static final int SDK_NOT_INIT = 9102; + public static final int SETTINGS_ERR_CFG_FILE_NOT_EXIST = 111162; + public static final int SETTINGS_ERR_INVALID_FILE_NAME = 111161; + public static final int SETTINGS_ERR_INVALID_ITEM_NAME = 111164; + public static final int SETTINGS_ERR_INVALID_ITEM_VALUE = 111165; + public static final int SETTINGS_ERR_INVALID_LINE = 111163; + public static final int SETTINGS_ERR_ITEM_NOT_FOUND = 111167; + public static final int SETTINGS_ERR_LIST_EMPTY = 111166; + public static final int SETTINGS_ERR_UNKNOWN = 111160; + public static final int TARGET_THREAD_STOPING = 111025; + public static final int TASK_ALREADY_EXIST = 9103; + public static final int TASK_ALREADY_RUNNING = 9106; + public static final int TASK_ALREADY_STOPPED = 9105; + public static final int TASK_CONTROL_STRATEGY = 9501; + public static final int TASK_FAILURE_ALL_SUBTASK_FAILED = 114009; + public static final int TASK_FAILURE_BTHUB_NO_RECORD = 114008; + public static final int TASK_FAILURE_CANNOT_START_SUBTASK = 114003; + public static final int TASK_FAILURE_EMULE_NO_RECORD = 114101; + public static final int TASK_FAILURE_GET_TORRENT_FAILED = 114006; + public static final int TASK_FAILURE_NO_DATA_PIPE = 111136; + public static final int TASK_FAILURE_PARSE_TORRENT_FAILED = 114005; + public static final int TASK_FAILURE_PART_SUBTASK_FAILED = 114011; + public static final int TASK_FAILURE_QUERY_BT_HUB_FAILED = 114004; + public static final int TASK_FAILURE_QUERY_EMULE_HUB_FAILED = 114001; + public static final int TASK_FAILURE_SAVE_TORRENT_FAILED = 114007; + public static final int TASK_FAILURE_SUBTASK_FAILED = 114002; + public static final int TASK_FAILURE_THEONLY_SUBTASK_FAILED = 114010; + public static final int TASK_FAIL_LONG_TIME_NO_RECV_DATA = 111176; + public static final int TASK_FILE_NAME_EMPTY = 9401; + public static final int TASK_FILE_NOT_VEDIO = 9402; + public static final int TASK_FILE_SIZE_TOO_LARGE = 111177; + public static final int TASK_FINISH = 9118; + public static final int TASK_NOT_EXIST = 9104; + public static final int TASK_NOT_IDLE = 9120; + public static final int TASK_NOT_RUNNING = 9119; + public static final int TASK_NOT_START = 9107; + public static final int TASK_NO_FILE_NAME = 9129; + public static final int TASK_NO_INDEX_NO_ORIGIN = 111148; + public static final int TASK_ORIGIN_NONEXISTENCE = 111149; + public static final int TASK_RETRY_ALWAY_FAIL = 111178; + public static final int TASK_STILL_RUNNING = 9108; + public static final int TASK_TYPE_NOT_SUPPORT = 9121; + public static final int TASK_UNKNOWN_ERROR = 9403; + public static final int TASK_USE_TOO_MUCH_MEM = 111031; + public static final int THUNDER_URL_PARSE_ERROR = 9305; + public static final int TOO_MUCH_TASK = 9111; + public static final int TORRENT_IMCOMPLETE = 9304; + public static final int TORRENT_PARSE_ERROR = 9302; + public static final int URL_IS_TOO_LONG = 111047; + public static final int URL_PARSER_ERROR = 111046; + public static final int VIDEO_CACHE_FINISH = 9410; + public static final int WRITE_FILE_ERR = 111127; + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLTaskInfo.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLTaskInfo.java new file mode 100644 index 00000000..d208c32a --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLTaskInfo.java @@ -0,0 +1,51 @@ +package com.xunlei.downloadlib.parameter; + +public class XLTaskInfo { + + public String mCid; + public String mFileName; + public String mGcid; + public int mAddedHighSourceState; + public int mAdditionalResCount; + public int mAdditionalResType; + public int mDcdnState; + public int mErrorCode; + public int mInfoLen; + public int mLanPeerResState; + public int mOriginErrcode; + public int mQueryIndexStatus; + public int mTaskStatus; + public long mAdditionalResDCDNBytes; + public long mAdditionalResDCDNSpeed; + public long mAdditionalResPeerBytes; + public long mAdditionalResPeerSpeed; + public long mAdditionalResVipRecvBytes; + public long mAdditionalResVipSpeed; + public long mCheckedSize; + public long mDownloadFileCount; + public long mDownloadSize; + public long mDownloadSpeed; + public long mFileSize; + public long mOriginRecvBytes; + public long mOriginSpeed; + public long mP2PRecvBytes; + public long mP2PSpeed; + public long mP2SRecvBytes; + public long mP2SSpeed; + public long mScdnRecvBytes; + public long mScdnSpeed; + public long mTaskId; + public long mTotalFileCount; + + public int getTaskStatus() { + return mTaskStatus; + } + + public void setTaskStatus(int taskStatus) { + this.mTaskStatus = taskStatus; + } + + public String getErrorMsg() { + return ErrorCode.get(mErrorCode); + } +} diff --git a/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLTaskLocalUrl.java b/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLTaskLocalUrl.java new file mode 100644 index 00000000..3554e3a1 --- /dev/null +++ b/thunder/src/main/java/com/xunlei/downloadlib/parameter/XLTaskLocalUrl.java @@ -0,0 +1,6 @@ +package com.xunlei.downloadlib.parameter; + +public class XLTaskLocalUrl { + + public String mStrUrl; +} diff --git a/thunder/src/main/jniLibs/arm64-v8a/libxl_stat.so b/thunder/src/main/jniLibs/arm64-v8a/libxl_stat.so new file mode 100644 index 00000000..2d8e3d43 Binary files /dev/null and b/thunder/src/main/jniLibs/arm64-v8a/libxl_stat.so differ diff --git a/thunder/src/main/jniLibs/arm64-v8a/libxl_thunder_sdk.so b/thunder/src/main/jniLibs/arm64-v8a/libxl_thunder_sdk.so new file mode 100644 index 00000000..14230530 Binary files /dev/null and b/thunder/src/main/jniLibs/arm64-v8a/libxl_thunder_sdk.so differ diff --git a/thunder/src/main/jniLibs/armeabi-v7a/libxl_stat.so b/thunder/src/main/jniLibs/armeabi-v7a/libxl_stat.so new file mode 100644 index 00000000..f3eaddf2 Binary files /dev/null and b/thunder/src/main/jniLibs/armeabi-v7a/libxl_stat.so differ diff --git a/thunder/src/main/jniLibs/armeabi-v7a/libxl_thunder_sdk.so b/thunder/src/main/jniLibs/armeabi-v7a/libxl_thunder_sdk.so new file mode 100644 index 00000000..25dac438 Binary files /dev/null and b/thunder/src/main/jniLibs/armeabi-v7a/libxl_thunder_sdk.so differ diff --git a/tvbus/.gitignore b/tvbus/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/tvbus/.gitignore @@ -0,0 +1 @@ +/build diff --git a/tvbus/build.gradle b/tvbus/build.gradle new file mode 100644 index 00000000..d66838cb --- /dev/null +++ b/tvbus/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.tvbus.engine' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + ndk { abiFilters "armeabi-v7a" } + } +} + +dependencies { + implementation project(':catvod') +} \ No newline at end of file diff --git a/tvbus/src/main/AndroidManifest.xml b/tvbus/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/tvbus/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/tvbus/src/main/java/com/tvbus/engine/Listener.java b/tvbus/src/main/java/com/tvbus/engine/Listener.java new file mode 100644 index 00000000..d6a54c75 --- /dev/null +++ b/tvbus/src/main/java/com/tvbus/engine/Listener.java @@ -0,0 +1,16 @@ +package com.tvbus.engine; + +public interface Listener { + + void onInited(String result); + + void onStart(String result); + + void onPrepared(String result); + + void onInfo(String result); + + void onStop(String result); + + void onQuit(String result); +} diff --git a/tvbus/src/main/java/com/tvbus/engine/TVCore.java b/tvbus/src/main/java/com/tvbus/engine/TVCore.java new file mode 100644 index 00000000..ac9e5849 --- /dev/null +++ b/tvbus/src/main/java/com/tvbus/engine/TVCore.java @@ -0,0 +1,164 @@ +package com.tvbus.engine; + +import android.content.Context; + +import com.github.catvod.Init; + +public class TVCore implements Runnable { + + private final Thread thread; + private final long handle; + + public TVCore(String path) { + System.load(path); + handle = initialise(); + thread = new Thread(this); + } + + public TVCore listener(Listener listener) { + try { + setListener(handle, listener); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore play(int port) { + try { + setPlayPort(handle, port); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore serv(int port) { + try { + setServPort(handle, port); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore mode(int mode) { + try { + setRunningMode(handle, mode); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore auth(String str) { + try { + if (!str.isEmpty()) setAuthUrl(handle, str); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore domain(String str) { + try { + if (!str.isEmpty()) setDomainSuffix(handle, str); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore broker(String str) { + try { + if (!str.isEmpty()) setMKBroker(handle, str); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore name(String str) { + try { + if (!str.isEmpty()) setUsername(handle, str); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore pass(String str) { + try { + if (!str.isEmpty()) setPassword(handle, str); + return this; + } catch (Throwable ignored) { + return this; + } + } + + public TVCore init() { + thread.start(); + return this; + } + + public void start(String url) { + try { + start(handle, url); + } catch (Throwable ignored) { + } + } + + public void stop() { + try { + stop(handle); + } catch (Throwable ignored) { + } + } + + public void quit() { + try { + quit(handle); + thread.interrupt(); + } catch (Throwable ignored) { + } + } + + @Override + public void run() { + try { + init(handle, Init.context()); + run(handle); + } catch (Throwable ignored) { + } + } + + private native long initialise(); + + private native int init(long handle, Context context); + + private native int run(long handle); + + private native void start(long handle, String url); + + private native void stop(long handle); + + private native void quit(long handle); + + private native void setServPort(long handle, int iPort); + + private native void setPlayPort(long handle, int iPort); + + private native void setRunningMode(long handle, int mode); + + private native void setAuthUrl(long handle, String str); + + private native void setDomainSuffix(long handle, String str); + + private native void setMKBroker(long handle, String str); + + private native void setPassword(long handle, String str); + + private native void setUsername(long handle, String str); + + private native void setListener(long handle, Listener listener); +} \ No newline at end of file diff --git a/zlive/.gitignore b/zlive/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/zlive/.gitignore @@ -0,0 +1 @@ +/build diff --git a/zlive/build.gradle b/zlive/build.gradle new file mode 100644 index 00000000..d9e51d52 --- /dev/null +++ b/zlive/build.gradle @@ -0,0 +1,19 @@ +plugins { + id 'com.android.library' +} + +android { + namespace 'com.east.android.zlive' + + compileSdk 35 + + defaultConfig { + minSdk 21 + targetSdk 28 + ndk { abiFilters "armeabi-v7a" } + } +} + +dependencies { + api 'net.java.dev.jna:jna:5.2.0' +} \ No newline at end of file diff --git a/zlive/src/main/AndroidManifest.xml b/zlive/src/main/AndroidManifest.xml new file mode 100644 index 00000000..568741e5 --- /dev/null +++ b/zlive/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/zlive/src/main/java/com/east/android/zlive/ZLive.java b/zlive/src/main/java/com/east/android/zlive/ZLive.java new file mode 100644 index 00000000..fb9e3498 --- /dev/null +++ b/zlive/src/main/java/com/east/android/zlive/ZLive.java @@ -0,0 +1,13 @@ +package com.east.android.zlive; + +import com.sun.jna.Library; +import com.sun.jna.Native; + +public interface ZLive extends Library { + + ZLive INSTANCE = Native.load("core", ZLive.class); + + void OnLiveStart(long port); + + void OnLiveStop(); +} \ No newline at end of file diff --git a/zlive/src/main/jniLibs/armeabi-v7a/libcore.so b/zlive/src/main/jniLibs/armeabi-v7a/libcore.so new file mode 100644 index 00000000..9a57a25f Binary files /dev/null and b/zlive/src/main/jniLibs/armeabi-v7a/libcore.so differ diff --git a/zlive/src/main/jniLibs/armeabi-v7a/libjnidispatch.so b/zlive/src/main/jniLibs/armeabi-v7a/libjnidispatch.so new file mode 100644 index 00000000..1cbf8a3c Binary files /dev/null and b/zlive/src/main/jniLibs/armeabi-v7a/libjnidispatch.so differ