[
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\n/local.properties\n/.idea/caches\n/.idea/libraries\n/.idea/modules.xml\n/.idea/workspace.xml\n/.idea/navEditor.xml\n/.idea/assetWizardSettings.xml\n.DS_Store\n/build\n/captures\n.externalNativeBuild\n.cxx\nlocal.properties\n*.log\n.idea\n.kotlin\n/coilimageloader\n/mojito\n/SketchImageViewLoader\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU AFFERO GENERAL PUBLIC LICENSE\n                       Version 3, 19 November 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU Affero General Public License is a free, copyleft license for\nsoftware and other kinds of works, specifically designed to ensure\ncooperation with the community in the case of network server software.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nour General Public Licenses are intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  Developers that use our General Public Licenses protect your rights\nwith two steps: (1) assert copyright on the software, and (2) offer\nyou this License which gives you legal permission to copy, distribute\nand/or modify the software.\n\n  A secondary benefit of defending all users' freedom is that\nimprovements made in alternate versions of the program, if they\nreceive widespread use, become available for other developers to\nincorporate.  Many developers of free software are heartened and\nencouraged by the resulting cooperation.  However, in the case of\nsoftware used on network servers, this result may fail to come about.\nThe GNU General Public License permits making a modified version and\nletting the public access it on a server without ever releasing its\nsource code to the public.\n\n  The GNU Affero General Public License is designed specifically to\nensure that, in such cases, the modified source code becomes available\nto the community.  It requires the operator of a network server to\nprovide the source code of the modified version running there to the\nusers of that server.  Therefore, public use of a modified version, on\na publicly accessible server, gives the public access to the source\ncode of the modified version.\n\n  An older license, called the Affero General Public License and\npublished by Affero, was designed to accomplish similar goals.  This is\na different license, not a version of the Affero GPL, but Affero has\nreleased a new version of the Affero GPL which permits relicensing under\nthis license.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU Affero General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Remote Network Interaction; Use with the GNU General Public License.\n\n  Notwithstanding any other provision of this License, if you modify the\nProgram, your modified version must prominently offer all users\ninteracting with it remotely through a computer network (if your version\nsupports such interaction) an opportunity to receive the Corresponding\nSource of your version by providing access to the Corresponding Source\nfrom a network server at no charge, through some standard or customary\nmeans of facilitating copying of software.  This Corresponding Source\nshall include the Corresponding Source for any work covered by version 3\nof the GNU General Public License that is incorporated pursuant to the\nfollowing paragraph.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the work with which it is combined will remain governed by version\n3 of the GNU General Public License.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU Affero General Public License from time to time.  Such new versions\nwill be similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU Affero General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU Affero General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU Affero General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU Affero General Public License as published\n    by the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU Affero General Public License for more details.\n\n    You should have received a copy of the GNU Affero General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If your software can interact with users remotely through a computer\nnetwork, you should also make sure that it provides a way for users to\nget its source.  For example, if your program is a web application, its\ninterface could display a \"Source\" link that leads users to an archive\nof the code.  There are many ways you could offer source, and different\nsolutions will be better for different programs; see section 13 for the\nspecific requirements.\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU AGPL, see\n<https://www.gnu.org/licenses/>.\n"
  },
  {
    "path": "README.md",
    "content": "# c001apk-compose\n\ntest only\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/release\n/schemas"
  },
  {
    "path": "app/build.gradle.kts",
    "content": "import com.android.build.gradle.internal.api.ApkVariantOutputImpl\nimport org.jetbrains.kotlin.konan.properties.Properties\nimport java.io.ByteArrayOutputStream\n\nplugins {\n    alias(libs.plugins.android.application)\n    alias(libs.plugins.google.dagger.hilt.android)\n    alias(libs.plugins.google.devtools.ksp)\n    alias(libs.plugins.google.protobuf)\n    alias(libs.plugins.jetbrains.kotlin.android)\n    alias(libs.plugins.jetbrains.kotlin.plugin.compose)\n    alias(libs.plugins.kotlin.parcelize)\n}\n\nfun String.execute(currentWorkingDir: File = file(\"./\")): String {\n    val byteOut = ByteArrayOutputStream()\n    rootProject.exec {\n        workingDir = currentWorkingDir\n        commandLine = split(\"\\\\s\".toRegex())\n        standardOutput = byteOut\n    }\n    return String(byteOut.toByteArray()).trim()\n}\n\nval gitCommitCount = \"git rev-list HEAD --count\".execute().toInt()\nval gitCommitHash = \"git rev-parse --verify --short HEAD\".execute()\n\nandroid {\n    namespace = \"com.example.c001apk.compose\"\n    compileSdk = 35\n\n    defaultConfig {\n        applicationId = \"com.example.c001apk.compose\"\n        minSdk = 24\n        targetSdk = 35\n        versionCode = gitCommitCount\n        versionName = gitCommitHash\n\n        testInstrumentationRunner = \"androidx.test.runner.AndroidJUnitRunner\"\n\n        vectorDrawables {\n            useSupportLibrary = true\n        }\n    }\n\n    val localProperties = Properties().also {\n        val properties = rootProject.file(\"local.properties\")\n        if (properties.exists())\n            it.load(properties.inputStream())\n    }\n\n    val config = localProperties.getProperty(\"KEYSTORE_PATH\")?.let {\n        signingConfigs.create(\"release\") {\n            storeFile = file(it)\n            storePassword = localProperties.getProperty(\"KEYSTORE_PASSWORD\")\n            keyAlias = localProperties.getProperty(\"KEY_ALIAS\")\n            keyPassword = localProperties.getProperty(\"KEY_PASSWORD\")\n            enableV2Signing = true\n            enableV3Signing = true\n        }\n    }\n\n    buildTypes {\n        all {\n            signingConfig = config ?: signingConfigs[\"debug\"]\n        }\n        release {\n            isMinifyEnabled = true\n            isShrinkResources = true\n            proguardFiles(\n                getDefaultProguardFile(\"proguard-android-optimize.txt\"),\n                \"proguard-rules.pro\"\n            )\n        }\n    }\n\n    compileOptions {\n        sourceCompatibility = JavaVersion.VERSION_17\n        targetCompatibility = JavaVersion.VERSION_17\n    }\n\n    kotlinOptions {\n        jvmTarget = \"17\"\n    }\n\n    buildFeatures {\n        compose = true\n        viewBinding = true\n        buildConfig = true\n    }\n\n    composeOptions {\n        kotlinCompilerExtensionVersion = \"1.5.15\"\n    }\n\n    packagingOptions.resources.excludes += setOf(\n        \"META-INF/**\",\n        \"okhttp3/**\",\n        \"kotlin/**\",\n        \"org/**\",\n        \"**.properties\",\n        \"**.bin\",\n        \"**/*.proto\"\n    )\n\n    dependenciesInfo.includeInApk = false\n\n    applicationVariants.configureEach {\n        outputs.configureEach {\n            if (baseName == \"release\")\n                (this as? ApkVariantOutputImpl)?.outputFileName =\n                    \"c001apk-compose_$versionName($versionCode).apk\"\n        }\n    }\n}\n\nksp {\n    arg(\"room.incremental\", \"true\")\n    arg(\"room.expandProjection\", \"true\")\n    arg(\"room.schemaLocation\", \"$projectDir/schemas\")\n}\n\nprotobuf {\n    protoc {\n        artifact = libs.google.protobuf.protoc.get().toString()\n    }\n\n    generateProtoTasks {\n        all().forEach { task ->\n            task.builtins {\n                register(\"java\") {\n                    option(\"lite\")\n                }\n            }\n        }\n    }\n}\n\ndependencies {\n\n    androidTestImplementation(libs.androidx.junit)\n    androidTestImplementation(libs.androidx.espresso.core)\n    androidTestImplementation(platform(libs.androidx.compose.bom))\n    androidTestImplementation(libs.androidx.ui.test.junit4)\n    debugImplementation(libs.androidx.ui.tooling)\n    debugImplementation(libs.androidx.ui.test.manifest)\n    debugImplementation(libs.leakcanary.android)\n    testImplementation(libs.junit)\n\n    implementation(libs.androidx.activity.compose)\n    implementation(libs.androidx.appcompat)\n    implementation(libs.androidx.compose.material.icons.extended)\n    implementation(libs.androidx.compose.navigation)\n    implementation(libs.androidx.constraintlayout.compose)\n    implementation(libs.androidx.core.ktx)\n    implementation(libs.androidx.datastore.core)\n    implementation(libs.androidx.exifinterface)\n    implementation(libs.androidx.hilt.navigation.compose)\n    implementation(libs.androidx.lifecycle.livedata.ktx)\n    implementation(libs.androidx.lifecycle.runtime.compose)\n    implementation(libs.androidx.lifecycle.runtime.ktx)\n    implementation(libs.androidx.lifecycle.viewModel.compose)\n    implementation(libs.androidx.material3)\n    implementation(libs.androidx.material3.window.size.android)\n    implementation(libs.androidx.room.ktx)\n    ksp(libs.androidx.room.compiler)\n    implementation(libs.androidx.room.runtime)\n    implementation(libs.androidx.ui)\n    implementation(libs.androidx.ui.graphics)\n    implementation(libs.androidx.ui.tooling.preview)\n    implementation(platform(libs.androidx.compose.bom))\n    implementation(libs.androidx.webkit)\n    implementation(libs.androidx.material3.adaptive.navigation.suite)\n\n    implementation(libs.google.accompanist.drawablepainter)\n    implementation(libs.google.android.material)\n    implementation(libs.google.dagger.hilt.android)\n    ksp(libs.google.dagger.hilt.android.compiler)\n    implementation(libs.google.protobuf.kotlin.lite)\n\n    implementation(libs.squareup.okhttp3.logging.interceptor)\n    implementation(libs.squareup.retrofit)\n    implementation(libs.squareup.retrofit.converter.gson)\n\n    implementation(libs.coil.compose)\n    implementation(libs.coil.gif)\n    implementation(libs.jp.wasabeef.transformers.coil)\n    implementation(libs.me.zhanghai.android.appiconloader.coil)\n\n    implementation(libs.jbcrypt)\n    implementation(libs.jsoup)\n    implementation(libs.toolbar.compose)\n    implementation(libs.oss.android.sdk)\n    implementation(libs.material.kolor)\n\n    implementation(project(\":mojito\"))\n    implementation(project(\":SketchImageViewLoader\"))\n    implementation(project(\":coilimageLoader\"))\n\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "-optimizationpasses 5\n\n# Keep DataStore fields\n-keepclassmembers class * extends com.google.protobuf.GeneratedMessageLite* {\n   <fields>;\n}\n\n-dontusemixedcaseclassnames\n-verbose\n\n# Preserve some attributes that may be required for reflection.\n-keepattributes *Annotation*,Signature,InnerClasses,EnclosingMethod\n\n# -keep class * extends androidx.fragment.app.Fragment{}\n\n# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native\n-keepclasseswithmembernames class * {\n    native <methods>;\n}\n\n# Keep setters in Views so that animations can still work.\n# -keepclassmembers public class * extends android.view.View {\n#     void set*(***);\n#     *** get*();\n# }\n\n# We want to keep methods in Activity that could be used in the XML attribute onClick.\n# -keepclassmembers class * extends android.app.Activity {\n#     public void *(android.view.View);\n# }\n\n# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations\n-keepclassmembers enum * {\n    public static **[] values();\n    public static ** valueOf(java.lang.String);\n}\n\n-keepclassmembers class * implements android.os.Parcelable {\n    public static final android.os.Parcelable$Creator CREATOR;\n}\n\n-keepclassmembers class **.R$* {\n    public static <fields>;\n}\n\n# Preserve annotated Javascript interface methods.\n-keepclassmembers class * {\n    @android.webkit.JavascriptInterface <methods>;\n}\n\n# The support libraries contains references to newer platform versions.\n# Don't warn about those in case this app is linking against an older\n# platform version. We know about them, and they are safe.\n-dontnote android.support.**\n-dontwarn android.support.**\n\n-dontwarn javax.annotation.**\n\n# Understand the @Keep support annotation.\n-keep class androidx.annotation.Keep\n-keep @androidx.annotation.Keep class * {*;}\n\n-keepclasseswithmembers class * {\n    @androidx.annotation.Keep <methods>;\n}\n\n-keepclasseswithmembers class * {\n    @androidx.annotation.Keep <fields>;\n}\n\n-keepclasseswithmembers class * {\n    @androidx.annotation.Keep <init>(...);\n}\n\n-assumenosideeffects class kotlin.jvm.internal.Intrinsics {\n    static void checkParameterIsNotNull(java.lang.Object, java.lang.String);\n    static void checkExpressionValueIsNotNull(java.lang.Object, java.lang.String);\n    static void checkNotNullExpressionValue(java.lang.Object, java.lang.String);\n    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String, java.lang.String);\n    static void checkReturnedValueIsNotNull(java.lang.Object, java.lang.String);\n    static void checkFieldIsNotNull(java.lang.Object, java.lang.String, java.lang.String);\n    static void checkFieldIsNotNull(java.lang.Object, java.lang.String);\n    static void checkNotNull(java.lang.Object, java.lang.String);\n    static void checkNotNullParameter(java.lang.Object, java.lang.String);\n}\n\n-dontwarn org.xmlpull.v1.XmlPullParser\n-dontwarn org.xmlpull.v1.XmlSerializer\n-keep class org.xmlpull.v1.* {*;}\n\n## Android architecture components: Lifecycle\n# LifecycleObserver's empty constructor is considered to be unused by proguard\n-keepclassmembers class * implements androidx.lifecycle.LifecycleObserver {\n    <init>(...);\n}\n# ViewModel's empty constructor is considered to be unused by proguard\n-keepclassmembers class * extends androidx.lifecycle.ViewModel {\n    <init>(...);\n}\n# keep methods annotated with @OnLifecycleEvent even if they seem to be unused\n# (Mostly for LiveData.LifecycleBoundObserver.onStateChange(), but who knows)\n-keepclassmembers class * {\n    @androidx.lifecycle.OnLifecycleEvent *;\n}\n\n# Gson uses generic type information stored in a class file when working with fields. Proguard\n# removes such information by default, so configure it to keep all of it.\n-keepattributes Signature,InnerClasses\n-keepattributes SourceFile,LineNumberTable\n-renamesourcefileattribute SourceFile\n\n# R8 full mode\n# Once\n#-keep,allowobfuscation,allowshrinking class jonathanfinerty.once.PersistedMap\n\n# TODO: Waiting for new retrofit release to remove these rules\n-keep,allowobfuscation,allowshrinking interface retrofit2.Call\n-keep,allowobfuscation,allowshrinking class retrofit2.Response\n-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation\n\n# org.apache.commons:commons-compress\n-keep,allowoptimization class org.apache.commons.compress.archivers.zip.**\n\n# Retrofit\n-dontnote retrofit2.Platform\n-keepattributes Signature\n-keepattributes Exceptions\n\n# okhttp\n-dontwarn okio.**\n\n##---------------Begin: proguard configuration for Gson  ----------\n# Gson uses generic type information stored in a class file when working with fields. Proguard\n# removes such information by default, so configure it to keep all of it.\n-keepattributes Signature\n\n# For using GSON @Expose annotation\n-keepattributes *Annotation*\n\n# Gson specific classes\n-dontwarn sun.misc.**\n#-keep class com.google.gson.stream.** { *; }\n\n# Application classes that will be serialized/deserialized over Gson\n-keep class com.example.c001apk.compose.logic.model.** { <fields>; }\n\n# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,\n# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)\n-keep class * extends com.google.gson.TypeAdapter\n-keep class * implements com.google.gson.TypeAdapterFactory\n-keep class * implements com.google.gson.JsonSerializer\n-keep class * implements com.google.gson.JsonDeserializer\n\n# Prevent R8 from leaving Data object members always null\n-keepclassmembers,allowobfuscation class * {\n  @com.google.gson.annotations.SerializedName <fields>;\n}\n\n# Retain generic signatures of TypeToken and its subclasses with R8 version 3.0 and higher.\n-keep,allowobfuscation,allowshrinking class com.google.gson.reflect.TypeToken\n-keep,allowobfuscation,allowshrinking class * extends com.google.gson.reflect.TypeToken\n\n##---------------End: proguard configuration for Gson  ----------\n\n# Retrofit\n-dontwarn retrofit2.**\n-dontwarn org.codehaus.mojo.**\n-keep class retrofit2.** { *; }\n-keepattributes Signature\n-keepattributes Exceptions\n-keepattributes *Annotation*\n-keepattributes RuntimeVisibleAnnotations\n-keepattributes RuntimeInvisibleAnnotations\n-keepattributes RuntimeVisibleParameterAnnotations\n-keepattributes RuntimeInvisibleParameterAnnotations\n-keepattributes EnclosingMethod\n-keepclasseswithmembers class * {\n@retrofit2.* <methods>;\n}\n-keepclasseswithmembers interface * {\n@retrofit2.* <methods>;\n}\n\n-keep class com.alibaba.sdk.android.oss.** { *; }\n-dontwarn okio.**\n-dontwarn org.apache.commons.codec.binary.**\n"
  },
  {
    "path": "app/src/androidTest/java/com/example/c001apk/compose/ExampleInstrumentedTest.kt",
    "content": "package com.example.c001apk.compose\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun useAppContext() {\n        // Context of the app under test.\n        val appContext = InstrumentationRegistry.getInstrumentation().targetContext\n        assertEquals(\"com.example.c001apk.compose\", appContext.packageName)\n    }\n}"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission\n        android:name=\"android.permission.QUERY_ALL_PACKAGES\"\n        tools:ignore=\"QueryAllPackagesPermission\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n\n    <application\n        android:name=\".C001Application\"\n        android:allowBackup=\"true\"\n        android:dataExtractionRules=\"@xml/data_extraction_rules\"\n        android:enableOnBackInvokedCallback=\"true\"\n        android:fullBackupContent=\"@xml/backup_rules\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:roundIcon=\"@mipmap/ic_launcher_round\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/Theme.C001apkCompose\"\n        android:usesCleartextTraffic=\"true\"\n        tools:targetApi=\"31\">\n\n        <activity\n            android:name=\".ui.feed.reply.ReplyActivity\"\n            android:exported=\"false\"\n            android:launchMode=\"singleTop\"\n            android:theme=\"@style/AppThemeTranslucent\"\n            android:windowSoftInputMode=\"adjustResize\" />\n\n        <activity\n            android:name=\".ui.main.MainActivity\"\n            android:alwaysRetainTaskState=\"true\"\n            android:exported=\"true\"\n            android:label=\"@string/app_name\"\n            android:launchMode=\"singleTask\"\n            android:theme=\"@style/Theme.C001apkCompose\"\n            android:windowSoftInputMode=\"adjustResize\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data\n                    android:host=\"live\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"u\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"feed\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"collection\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"apk\"\n                    android:pathPattern=\"/.*\\\\..*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"com.coolapk.market\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/.*\"\n                    android:scheme=\"coolmarket\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"http\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"coolapk1s.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/apk/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/game/.*\\\\..*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/feed/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/web/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/faxian/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/t/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/u/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/n/.*\"\n                    android:scheme=\"https\" />\n                <data\n                    android:host=\"www.coolapk1s.com\"\n                    android:pathPattern=\"/album/.*\"\n                    android:scheme=\"https\" />\n            </intent-filter>\n        </activity>\n\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.fileprovider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_provider_paths\" />\n        </provider>\n\n    </application>\n\n</manifest>"
  },
  {
    "path": "app/src/main/assets/devicemodel.txt",
    "content": "1503-M02\n1503-A01\n1505-A01\n1505-A02\n1603-A03\n1605-A01\n1605-A02\n1607-A01\n1801-A01\n1707-A01\n1713-A01\n1807-A01\n1809-A01\n1803-A01\n1711-A01\n1501-M02\n1501-A02\n1603-A02\n1701-M01\n1515-A01\n1509-M02\n1509-A00\n1703-M01\n8681-M01\n8681-M02\n8681-A01\n8692-M02\n8692-A00\nH30-T00\nH30-T10\nH30-U10\nH30-L01\nH30-L01M\nH30-L02\nH30-C00\nG750-T00\nG750-U00\nG750-T20\nH60-L01\nH60-L02\nH60-L03\nH60-L11\nH60-L12\nH60-L21\nPE-TL00M\nPE-TL10\nPE-TL20\nPE-UL00\nPE-CL00\nPLK-AL10\nPLK-TL01H\nPLK-TL00\nPLK-UL00\nPLK-CL00\nATH-AL00\nATH-TL00H\nATH-TL00\nATH-UL00\nATH-CL00\nKNT-AL10\nKNT-AL20\nKNT-TL10\nKNT-UL10\nFRD-AL00\nFRD-AL10\nFRD-TL00\nFRD-DL00\nPRA-AL00\nPRA-AL00X\nPRA-TL10\nEDI-AL10\nEDI-DL00\nNTS-AL00\nDUK-AL20\nDUK-TL30\nJMM-AL00\nJMM-AL10\nJMM-TL00\nJMM-TL10\nSTF-AL00\nSTF-AL10\nSTF-TL10\nLLD-AL00\nLLD-AL10\nLLD-TL10\nLLD-AL20\nLLD-AL30\nBKL-AL00\nBKL-AL20\nBKL-TL10\nCOL-AL00\nCOL-AL10\nCOL-TL00\nCOL-TL10\nHRY-AL00\nHRY-AL00a\nHRY-TL00\nCOR-AL00\nCOR-AL10\nCOR-TL10\nRVL-AL09\nJSN-AL00\nJSN-AL00a\nJSM-TL00\nARE-AL00\nARE-AL10\nARE-TL00\nTNY-AL00\nTNY-TL00\nPCT-AL10\nPCT-TL10\nHRY-AL00T\nHRY-AL00Ta\nHRY-TL00T\nYAL-AL00\nYAL-TL00\nYAL-AL10\nYAL-TL10\nHol-T00\nHol-U10\nG750-T01\nG621-TL00M\nG621-TL00\nG620S-UL00\nC8817D\nChe1-CL20\nChe1-CL00\nChe2-TL00\nChe2-TL00H\nChe2-TL00M\nCHE-TL00H\nCHE-TL00\nChe2-UL00\nCHM-TL00H\nCHM-TL00\nCHM-UL00\nCHM-CL00\nSCL-AL00\nSCL-TL00H\nSCL-TL00\nSCL-CL00\nKIW-AL00\nKIW-TL00H\nKIW-TL00\nKIW-UL00\nKIW-CL00\nNEM-AL10\nNEM-TL00H\nNEM-TL00\nNEM-UL00\nCAM-AL00\nCAM-TL00H\nCAM-TL00\nCAM-UL00\nCAM-CL00\nCUN-AL00\nCUN-TL00\nBLN-AL10\nBLN-AL20\nBLN-AL30\nBLN-AL40\nBLN-TL00\nBLN-TL10\nDLI-AL10\nDLI-TL20\nMYA-AL10\nMYA-TL10\nBND-AL00\nBND-AL10\nBND-TL10\nLND-AL30\nLND-AL40\nLND-TL30\nLND-TL40\nAUM-AL00\nAUM-AL20\nAUM-TL00\nAUM-TL20\nDUA-AL00\nDUA-TL00\nBKK-AL00\nBKK-AL10\nBKK-TL00\nJAT-AL00\nJAT-TL00\nKSA-AL00\nKSA-TL00\n7D-501u\n7D-503L\n7D-503LT\nGEM-703L\nGEM-703LT\nS8-701w\nS8-701u\nT1-821w\nT1-823L\nJDN-W09\nJDN-AL00\nJDN2-W09HN\nJDN2-AL00HN\nAGS2-W09HN\nAGS2-AL00HN\nT1-701u\nT1-701ua\nBGO-DL09\nT1-A21w\nT1-A23L\nKOB-W09\nKOB-L09\nAGS-W09\nAGS-L09\nBG2-W09\nHDN-W09\nHDN-L09\nHDL-W09\nHDL-AL09\nHUAWEI MT1-T00\nHUAWEI MT1-U06\nHUAWEI MT2-U071\nHUAWEI MT2-C00\nHUAWEI MT2-L02\nHUAWEI MT2-L05\nHUAWEI MT7-TL00\nHUAWEI MT7-TL10\nHUAWEI MT7-UL00\nHUAWEI MT7-CL00\nHUAWEI CRR-TL00\nHUAWEI CRR-UL00\nHUAWEI CRR-UL20\nHUAWEI CRR-CL00\nHUAWEI CRR-CL20\nHUAWEI NXT-AL10\nHUAWEI NXT-TL00\nHUAWEI NXT-DL00\nHUAWEI NXT-CL00\nMHA-AL00\nMHA-TL00\nLON-AL00\nALP-AL00\nALP-TL00\nBLA-AL00\nBLA-TL00\nNEO-AL00\nHMA-AL00\nHMA-TL00\nLYA-AL00\nLYA-AL10\nLYA-TL00\nEVR-AL00\nEVR-TL00\nEVR-AN00\nLYA-AL00P\nHUAWEI U9200\nHUAWEI U9200E\nHUAWEI U9200S\nHUAWEI P2-0000\nHUAWEI P6-T00\nHUAWEI P6-T00V\nHUAWEI P6-U06\nHUAWEI P6-C00\nHUAWEI P6 S-U06\nHUAWEI P7-L00\nHUAWEI P7-L05\nHUAWEI P7-L07\nHUAWEI P7-L09\nHUAWEI GRA-TL00\nHUAWEI GRA-UL00\nHUAWEI GRA-UL10\nHUAWEI GRA-CL00\nHUAWEI GRA-CL10\nHUAWEI ALE-TL00\nHUAWEI ALE-UL00\nHUAWEI ALE-CL00\nDAV-703L\nDAV-713L\nEVA-AL00\nEVA-AL10\nEVA-TL00\nEVA-DL00\nEVA-CL00\nVIE-AL10\nVTR-AL00\nVTR-TL00\nVKY-AL00\nVKY-TL00\nEML-AL00\nEML-TL00\nCLT-AL00\nCLT-AL01\nCLT-AL00l\nCLT-TL00\nCLT-TL01\nELE-AL00\nELE-TL00\nVOG-AL00\nVOG-AL10\nVOG-TL00\nHUAWEI CAZ-AL00\nHUAWEI CAZ-AL10\nHUAWEI CAZ-TL10\nHUAWEI CAZ-TL20\nWAS-AL00\nWAS-TL10\nPIC-AL00\nPIC-TL00\nBAC-AL00\nBAC-TL00\nHWI-AL00\nHWI-TL00\nANE-AL00\nANE-TL00\nPAR-AL00\nPAR-TL00\nINE-AL00\nINE-TL00\nVCE-AL00\nVCE-TL00\nMAR-AL00\nMAR-TL00\nSEA-AL00\nSEA-TL00\nSEA-AL10\nSEA-TL10\nGLK-AL00\nGLK-TL00\nHUAWEI G6-T00\nHUAWEI G6-U00\nHUAWEI G6-C00\nHUAWEI G7-TL00\nHUAWEI G7-UL00\nHUAWEI RIO-TL00\nHUAWEI RIO-UL00\nHUAWEI VNS-AL00\nHUAWEI VNS-TL00\nHUAWEI VNS-DL00\nHUAWEI VNS-CL00\nHUAWEI MLA-TL00\nHUAWEI MLA-TL10\nHUAWEI MLA-UL00\nHUAWEI A199\nHUAWEI B199\nHUAWEI C199\nHUAWEI C199s\nHUAWEI RIO-AL00\nHUAWEI RIO-CL00\nHUAWEI MLA-AL00\nHUAWEI MLA-AL10\nRNE-AL00\nSNE-AL00\nPOT-AL00\nHUAWEI TIT-AL00\nHUAWEI TIT-TL00\nHUAWEI TIT-CL00\nHUAWEI TIT-CL10\nHUAWEI TAG-AL00\nHUAWEI TAG-TL00\nHUAWEI TAG-CL00\nNCE-AL00\nNCE-AL10\nNCE-TL00\nNCE-TL10\nDIG-AL00\nDIG-TL10\nSLA-AL00\nSLA-TL10\nTRT-AL00\nTRT-AL00A\nTRT-TL10\nTRT-TL10A\nFIG-AL00\nFIG-AL10\nFIG-TL00\nFIG-TL10\nLDN-AL00\nLDN-AL10\nLDN-AL20\nLDN-TL00\nLDN-TL10\nLDN-TL20\nFLA-AL00\nFLA-AL10\nFLA-TL00\nFLA-TL10\nATU-AL10\nATU-TL10\nDRA-AL00\nDRA-TL00\nDUB-AL00\nDUB-AL00a\nDUB-TL00\nDUB-TL00a\nJKM-AL00\nJKM-AL00a\nJKM-AL00b\nJKM-TL00\nARS-AL00\nARS-TL00\nPOT-AL00\nPOT-AL00a\nPOT-TL00a\nMRD-AL00\nMRD-TL00\nS8-301W\nS8-301U\nS8-303L\nHUAWEI M2-801W\nHUAWEI M2-803L\nHUAWEI M2-A01W\nHUAWEI M2-A01L\nPLE-703L\nPLE-703LT\nFDR-A01w\nFDR-A03L\nBTV-W09\nBTV-DL09\nCPN-W09\nCPN-AL00\nBAH-W09\nBAH-AL00\nBZK-W00\nBZK-L00\nBZA-W00\nBZA-L00\nSHT-W09\nSHT-AL09\nCMR-W09\nCMR-AL09\nCMR-W19\nCMR-AL19\nBAH2-W09\nBAH2-AL10\nJDN2-W09\nJDN2-AL00\nMON-W19\nMON-AL19\nBZT-W09\nBZT-AL00\nBZT-AL10\nAGS2-W09\nAGS2-AL00\nVRD-W09\nVRD-AL09\nSCM-W09\nSCM-AL09\nHUAWEI U8860\nHUAWEI C8860E\nHUAWEI T8950\nHUAWEI U8950D\nHUAWEI C8950D\nHUAWEI U9508\nHUAWEI HN3-U01\nHUAWEI U9500\nHUAWEI U9500E\nHUAWEI U9510\nHUAWEI U9510E\nHUAWEI T9510E\nHUAWEI D2-5000\nHUAWEI D2-6070\nHUAWEI D2-0082\nHUAWEI D2-2010\nLenovo L78011\nLenovo L78012\nLenovo L78031\nLenovo L78032\nLenovo L78071\nLenovo L78051\nLenovo L78121\nLenovo L38111\nLenovo K520\nLenovo K520t\nLenovo L58041\nLenovo L58091\nLenovo K350t\nLenovo L38012\nLenovo L38011\nLenovo L38021\nLenovo L38031\nLenovo L38041\nLenovo L38082\nLenovo L18011\nLenovo K320t\nLetv X600\nLetv X608\nLetv X500\nLetv X501\nLetv X508\nLetv X502\nLetv X800\nLetv X800+\nLetv X900\nLetv X900+\nLetv X906\nLe X620\nLe X621\nLe X625\nLe X520\nLe X521\nLe X528\nLe X529\nLe X820\nLe X822\nLetv X910\nLEX622\nLEX626\nLEX623\nLEX720\nLEX722\nLEX728\nLEX726\nLEX651\nLEX650\nLEX658\nLEX652\nLEX656\nLEX850\nM8\nM8SE\nM9\nM030\nM031\nM032\nM040\nM045\nM351\nM353\nM355\nM356\nM460\nM460A\nM461\nM460H\nM462\nM462U\nM462H\nM575\nM575M\nM575U\nM575H\nM685Q\nM685M\nM685U\nM685C\nM685H\nM576\nM576U\nM576H\nM570Q\nM570M\nM570C\nM570H\nM570Q-S\nM686\nM686G\nM686H\nM792Q-L\nM792M-L\nM792C-L\nM792H\nM792Q\nM792C\nM793Q\nM793H\nM881Q\nM881M\nM881H\nM891Q\nM891H\nM871Q\nM871H\nM882Q\nM882H\nM892Q\nM872Q\nM872H\nM971Q\nM971H\nM926Q\nM926H\nM852Q\nM852H\nM813Q\nM813H\nM816Q\nM816H\nM810H\nM818H\nM819H\nM822Q\nM822H\nM923Q\nM923H\nM463M\nM463U\nM463C\nM463H\nM571\nM571M\nM571U\nM571C\nM571H\nM681Q\nL681Q\nM681M\nL681M\nM681C\nL681C\nM681H\nL681H\nM621Q\nM621M\nM621C\nM621C-S\nM621H\nM721Q\nM721M\nM721C\nM721H\nA680Q\nA680M\nA680H\nM741A\nM741Y\nM851Q\nM851M\nM682Q\nM465M\nM465A\nM578\nM578A\nM578M\nM578MA\nM578U\nM578C\nM578CA\nM578CE\nM578H\nM688Q\nM688M\nM688U\nM688C\nY685Q\nY685M\nY685C\nY685H\nM611A\nM611Y\nM611D\nM611H\nM612Q\nM612M\nM612C\nM612H\nM711Q\nM711M\nM711C\nM711H\nM712Q\nM712Q-B\nM712M\nM712C\nM712H\nM811Q\nM811H\nS685Q\nS685M\nS685C\nS685H\nU680A\nU680Y\nU680D\nU680H\nU685Q\nU685M\nU685C\nU685H\nM57A\nM57AM\nM57AU\nM57AC\nM710M\nM710H\nL47M1-AA\nL40M2-AA\nL49M2-AA\nL55M2-AA\nL48M3-AA\nL48M3-AR\nL55M4-AA\nL55M4-AR\nL60M4-AA\nL60M4-AR\nL70M4-AA\nL43M3-AA\nL43M3-AR\nL48M3-AF\nL55M5-AA\nL60M5-AA\nL65M5-AA\nL65M4-AQ\nL32M5-AZ\nL32M5-AZ\nL40M5-AD\nL43M5-AZ\nL43M5-AD\nL43M5-5A\nL43M5-5A\nL49M5-AZ\nL50M5-AD\nL50M5-5A\nL50M5-5A\nL55M5-AZ\nL55M5-AD\nL55M5-5A\nL58M5-4A\nL65M5-AZ\nL65M5-AD\nL65M5-5A\nL49M5-AB\nL55M5-AB\nL65M5-AB\nL65M5-4\nL75M5-AB\nL32M5-AD\nL40M5-4C\nL40M5-4C\nL43M5-AX\nL50M5-AD\nL55M5-AZ\nL65M5-4C\nL32M5-AD\nL43M5-AU\nL43M5-5S\nL50M5-AD\nL50M5-5S\nL55M5-AD\nL55M5-5S\nL55M5-AQ\nL58M5-4C\nL65M5-AD\nL65M5-5S\nL65M5-AD\nL75M5-4S\nL43M5-4X\nL55M5-AD\nL65M5-4X\nL32M5-AD\nL43M5-FA\nL55M5-AZ\nL55M5-EC\nL65M5-EA\nL55M5-AB\nL65M5-4\nL65M5-BH\nMDZ-18-DA\nMDZ-19-DA\nMDZ-05-AA\nMDZ-06-AA\nMDZ-06-AB\nMDZ-09-AA\nMDZ-09-AK\nMDZ-15-AA\nMDZ-16-AA\nMDZ-18-AA\nMDZ-19-AA\nMDZ-21-AA\nMDZ-20-AA\nMDZ-23-AA\nXT1085\nXT1079\nXT1077\nXT1115\nXT1570\nXT1561\nXT1581\nXT1635-03\nXT1662\nXT1650-05\nXT1710-08\nXT1710-11\nXT1799-1\nXT1799-2\nXT1789-05\nXT1925-10\nXT1929-15\nXT1924-9\nXT1943-1\nXT1942-1\nXT1941-2\nXT1965-6\nTA-1000\nTA-1054\nTA-1041\nTA-1062\nTA-1042\nTA-1094\nTA-1109\nTA-1099\nTA-1131\nTA-1172\nTA-1117\nNX501\nNX401\nNX402\nNX503A\nNX503J\nNX403A\nNX506J\nNX507J\nNX507H\nNX505J\nNX505H\nNX508J\nNX508H\nNX511J\nNX511H\nNX510J\nNX512H\nNX512J\nNX518J\nNX531J\nNX529J\nNX523J\nNX535J\nNX549J\nNX563J\nNX591J\nNX569J\nNX569H\nNX595J\nNX589J\nNX606J\nNX611J\nNX609J\nNX619J\nNX629J\nNX601J\nNX616J\nNX612J\nNX513J\nNX513H\nNX551J\nNX573J\nNX907J\nNX541J\nNX575J\nNX608J\nNX617J\nONE A0001\nONE A1001\nONE A2001\nONE A2003\nONE A2005\nONE E1001\nONE E1000\nONE E1003\nONE E1005\nONEPLUS A3000\nONEPLUS A3003\nONEPLUS A3010\nONEPLUS A3003\nONEPLUS A3000\nONEPLUS A5000\nONEPLUS A5010\nONEPLUS A6000\nONEPLUS A6003\nONEPLUS A6010\nONEPLUS A6013\nGM1900\nGM1901\nGM1903\nGM1905\nGM1910\nGM1911\nGM1913\nGM1915\nGM1917\nGM1920\nONE A0001\nONE A1001\nONE A2003\nONE A2005\nONE A2001\nONE E1003\nONE E1005\nONE E1000\nONE E1001\nONEPLUS A3003\nONEPLUS A3000\nONEPLUS A3003\nONEPLUS A3000\nONEPLUS A3010\nONEPLUS A5000\nONEPLUS A5010\nONEPLUS A6003\nONEPLUS A6000\nONEPLUS A6013\nONEPLUS A6010\nGM1901\nGM1903\nGM1905\nGM1900\nGM1911\nGM1913\nGM1915\nGM1917\nGM1910\nGM1920\nPACM00\nPACT00\nPAAM00\nPAAT00\nPBCM10\nPBCT10\nPBEM00\nPBET00\nPBDM00\nPBDT00\nPAFM00\nPAFT00\nPAHM00\nPAFT10\nPCAM00\nPCAT00\nPCCM00\nPCCT00\nPCDM10\nPCDT10\nPADM00\nPADT00\nPBAM00\nPBBM30\nPBAT00\nPBBT30\nPBFM00\nPBFT00\nPBBM00\nPBBT00\nPCDM00\nPCDT00\nPCAM10\nPCAT10\nPCEM00\nPCET00\nPBCM30\nPCGM00\nPCGT00\nGT-I9000\nGT-I9008\nGT-I9008L\nSCH-i909\nGT-I9100\nGT-I9100G\nGT-I9108\nSCH-I919\nGT-I9300\nGT-I9308\nSCH-I939\nSCH-I939D\nGT-I9300I\nGT-I9308I\nSCH-I939I\nGT-I8190N\nGT-I9500\nGT-I9502\nGT-I9508\nSCH-I959\nGT-I9507V\nGT-I9508V\nSM-C101\nSM-G9006V\nSM-G9008V\nSM-G9009D\nSM-G9006W\nSM-G9008W\nSM-G9009W\nSM-G9200\nSM-G9208\nSM-G9209\nSM-G9250\nSM-G9280\nSM-G9300\nSM-G9308\nSM-G9350\nSM-G9500\nSM-G9508\nSM-G9550\nSM-G9600/DS\nSM-G9608/DS\nSM-G9650/DS\nSM-G8750\nSM-G9700\nSM-G9708\nSM-G9730\nSM-G9738\nSM-G9750\nSM-G9758\nGT-I9220\nGT-I9228\nSCH-I889\nGT-N7100\nGT-N7102\nGT-N7102i\nGT-N7108\nGT-N7108D\nSCH-N719\nSM-N9002\nSM-N9006\nSM-N9008\nSM-N9008V\nSM-N9008S\nSM-N9009\nSM-N7506V\nSM-N7508V\nSM-N7509V\nSM-N9100\nSM-N9106W\nSM-N9108V\nSM-N9109W\nSM-N9150\nSM-N9200\nSM-N9208\nSM-N9300\nSM-N9500\nSM-N9508\nSM-N9600\nSM-N9608\nSM-A3000\nSM-A3009\nSM-A5000\nSM-A5009\nSM-A7000\nSM-A7009\nSM-A8000\nSM-A5100\nSM-A5108\nSM-A7100\nSM-A7108\nSM-A9000\nSM-A9100\nSM-G8850\nSM-G8858\nSM-A6050\nSM-A6058\nSM-G6200\nSM-G8870\nSM-A9200\nSM-A3050\nSM-A3058\nSM-A6060\nSM-A7050\nSM-A8050\nSM-F9000\nSM-C5000\nSM-C5010\nSM-C5018\nSM-C7000\nSM-C7010\nSM-C7018\nSM-C7100\nSM-C7108\nSM-C9000\nSM-C9008\nSM-J3109\nSM-J5008\nSM-J7008\nSM-J3110\nSM-J3119\nSM-J3119S\nSM-J5108\nSM-J7108\nSM-J7109\nSM-J3300\nSM-J3308\nSM-G5500\nSM-G6000\nSM-G5700\nSM-G5510\nSM-G5520\nSM-G5528\nSM-G6100\nSM-G1600\nSM-G1650\nSM-G8508S\nSM-E7000\nSM-E7009\nSM701\nSM705\nSM801\nSM901\nSM919\nYQ601\nYQ603\nYQ605\nYQ607\nOD103\nOD105\nOS105\nOS103\nOC105\nOC106\nDE106\nOE106\nV1814A\nV1814T\nV1809A\nV1809T\nV1816A\nV1816T\nV1829A\nV1829T\nV1838A\nV1838T\nV1836A\nV1836T\nV1821A\nV1821T\nV1801A0\nV1730DA\nV1730DT\nV1730EA\nV1813BA\nV1813BT\nV1813A\nV1813T\nV1813A\nV1813T\nV1730GA\nV1911A\nV1911T\nV1731CA\nV1732A\nV1732T\nV1732A\nV1732T\nV1818CA\nV1818CT\nV1818A\nV1818T\nV1818CA\nV1818CT\nV1818CA\nV1818CT\nV1813A\nV1813T\nV1901A\nV1901T\nV1824BA\nV1824A\nV1914A\nV1831A\nV1831T\nV1832A\nV1832T\nV1818A\nMI-ONE PLUS\nMI-ONE C1\nMI-ONE\n2012051\n2012053\n2012052\n2012061\n2012062\n2013012\n2013021\n2012121\n2013061\n2013062\n2013063\n2014215\n2014218\n2014216\n2014719\n2014716\n2014726\n2015015\n2015561\n2015562\n2015911\n2015201\n2015628\n2015105\n2015711\n2016070\n2016089\nMDE2\nMDT2\nMCE16\nMCT1\nM1804D2SE\nM1804D2ST\nM1804D2SC\nM1803E1A\nM1803E1T\nM1803E1C\nM1807E8S\nM1807E8A\nM1805E2A\nM1808D2TE\nM1808D2TT\nM1808D2TC\nM1808D2TG\nM1902F1A\nM1902F1T\nM1902F1C\nM1902F1G\nM1903F2A\nM1903F2G\nM1903F10G\nM1903F11G\n2014616\n2014619\n2014618\n2014617\n2015011\n2015021\n2015022\n2015501\n2015211\n2015212\n2015213\nMCE8\nMCT8\n2016080\nMDE5\nMDT5\nMDE5S\nM1803D5XE\nM1803D5XA\nM1803D5XT\nM1803D5XC\nM1810E5E\nM1810E5A\nM1810E5T\nM1810E5EC\nM1810E5GG\n2016001\n2016002\n2016007\nMDE40\nMDT4\nMDI40\nM1804E4A\nM1804E4T\nM1804E4C\nM1901F9E\nM1901F9T\nMDG2\nMDI2\nM1804D2SG\nM1804D2SI\nM1805D1SG\nA0101\n2015716\nMCE91\nM1806D9W\nM1806D9E\nM1806D9PW\nM1806D9PE\n2013022\n2013023\n2013029\n2013028\n2014011\n2014501\n2014813\n2014112\n2014811\n2014812\n2014821\n2014817\n2014818\n2014819\n2014502\n2014512\n2014055\n2014816\n2015811\n2015815\n2015812\n2015810\n2015817\n2015819\n2015818\n2015816\n2016030\n2016031\n2016032\n2016037\n2016036\n2016035\n2016033\n2016090\n2016060\n2016111\n2016112\n2016117\n2016116\nMAE136\nMAT136\nMAG138\nMAI132\nMDE1\nMDT1\nMDG1\nMDI1\nMEE7\nMET7\nMEG7\nMEI7\nMCE3B\nMCT3B\nMCG3B\nMCI3B\nM1804C3DE\nM1804C3DT\nM1804C3DC\nM1804C3DG\nM1804C3DH\nM1804C3DI\nM1805D1SE\nM1805D1ST\nM1805D1SC\nM1805D1SI\nM1804C3CE\nM1804C3CT\nM1804C3CC\nM1804C3CG\nM1804C3CH\nM1804C3CI\nM1810F6LE\nM1810F6LT\nM1810F6LC\nM1810F6LG\nM1810F6LH\nM1810F6LI\nM1903C3EE\nM1903C3ET\nM1903C3EC\nM1903C3EG\nM1903C3EH\nM1903C3EI\n2014018\n2013121\n2014017\n2013122\n2014022\n2014021\n2014715\n2014712\n2014915\n2014912\n2014916\n2014911\n2014910\n2015052\n2015051\n2015712\n2015055\n2015056\n2015617\n2015611\n2015112\n2015115\n2015116\n2015161\n2016050\n2016051\n2016101\n2016130\n2016100\n2016102\nMBE6A5\nMBT6A5\nMEE7S\nMET7S\nMEC7S\nM1803E7SG\nM1803E7SH\nMEI7S\nMDE6\nMDT6\nMDG6\nMDI6\nMDE6S\nMDT6S\nMDG6S\nMDI6S\nM1806E7TG\nM1806E7TH\nM1806E7TI\nM1901F7E\nM1901F7T\nM1901F7C\nM1901F7G\nM1901F7H\nM1901F7I\nM1901F7BE\nM1901F7S\nM1803E6E\nM1803E6T\nM1803E6C\nM1803E6G\nM1803E6H\nM1803E6I\nM1810F6G\nM1810F6I\n2016020\n2016021\nM1903F10A\nM1903F10T\nM1903F10C\nM1903F10I\nM1903F11A\nM1903F11T\nM1903F11C\nM1903F11I\nM1903C3GG\nM1903C3GH\nM1903C3GI\nSKR-A0\nAWM-A0\nSKW-A0\nM1805E10A"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/C001Application.kt",
    "content": "package com.example.c001apk.compose\n\nimport android.app.Application\nimport coil.Coil\nimport coil.ImageLoader\nimport com.example.c001apk.compose.util.dp\nimport dagger.hilt.android.HiltAndroidApp\nimport me.zhanghai.android.appiconloader.coil.AppIconFetcher\nimport me.zhanghai.android.appiconloader.coil.AppIconKeyer\nimport net.mikaelzero.coilimageloader.CoilImageLoader\nimport net.mikaelzero.mojito.Mojito\nimport net.mikaelzero.mojito.view.sketch.SketchImageLoadFactory\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/29\n */\nlateinit var c001Application: C001Application\n\n@HiltAndroidApp\nclass C001Application : Application() {\n\n    override fun onCreate() {\n        super.onCreate()\n\n        c001Application = this\n\n        Coil.setImageLoader(\n            ImageLoader.Builder(this)\n                .crossfade(true)\n                .components {\n                    add(AppIconKeyer())\n                    add(AppIconFetcher.Factory(48.dp, false, this@C001Application))\n                }\n                .build()\n        )\n\n        Mojito.initialize(\n            CoilImageLoader.with(this),\n            SketchImageLoadFactory()\n        )\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/constant/Constants.kt",
    "content": "package com.example.c001apk.compose.constant\n\nimport com.example.c001apk.compose.util.CookieUtil.isDarkMode\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nobject Constants {\n    const val REQUEST_WITH = \"XMLHttpRequest\"\n    const val LOCALE = \"zh-CN\"\n    const val APP_ID = \"com.coolapk.market\"\n    var DARK_MODE = if (isDarkMode) \"1\" else \"0\"\n    const val CHANNEL = \"coolapk\"\n    const val MODE = \"universal\"\n    const val APP_LABEL = \"token://com.coolapk.market/dcf01e569c1e3db93a3d0fcf191a622c\"\n    const val VERSION_NAME = \"13.4.1\"\n    const val API_VERSION = \"13\"\n    const val VERSION_CODE = \"2312121\"\n\n    const val PREFIX_COOLMARKET = \"coolmarket://\"\n    const val PREFIX_HTTP = \"http\"\n    const val PREFIX_APP = \"/apk/\"\n    const val PREFIX_GAME = \"/game/\"\n    const val PREFIX_FEED = \"/feed/\"\n    const val PREFIX_PRODUCT = \"/product/\"\n    const val PREFIX_TOPIC = \"/t/\"\n    const val PREFIX_USER = \"/u/\"\n    const val PREFIX_CAROUSEL = \"/page?url=\"\n    const val PREFIX_CAROUSEL1 = \"#/\" // \"#/feed/\", \"#/topic/\", \"#/article/\"\n    const val PREFIX_USER_LIST = \"/user/\"\n    const val PREFIX_DYH = \"/dyh/\"\n    const val PREFIX_COLLECTION = \"/collection/\"\n    const val SUFFIX_THUMBNAIL = \".s.jpg\"\n    const val SUFFIX_GIF = \".gif\"\n\n    const val UTF8 = \"UTF-8\"\n    const val EMPTY_STRING = \"\"\n    const val LOADING_FAILED = \"FAILED\"\n    const val WEB_LOGIN_FAILED = \"网页登录失败\"\n    const val URL_LOGIN = \"https://account.coolapk.com/auth/login?type=mobile\"\n    const val URL_SOURCE_CODE = \"https://github.com/bggRGjQaUbCoE/c001apk-compose\"\n\n    val entityTypeList =\n        listOf(\n            \"feed\",\n            \"apk\",\n            \"product\",\n            \"user\",\n            \"topic\",\n            \"notification\",\n            \"productBrand\",\n            \"contacts\",\n            \"recentHistory\",\n            \"feed_reply\",\n            \"message\",\n            \"collection\",\n        )\n    val entityTemplateList =\n        listOf(\n            \"imageCarouselCard_1\",\n            \"iconLinkGridCard\",\n            \"iconMiniScrollCard\",\n            \"iconMiniGridCard\",\n            \"imageSquareScrollCard\",\n            \"titleCard\",\n            \"iconScrollCard\",\n            \"imageTextScrollCard\",\n            \"iconTabLinkGridCard\",\n            \"verticalColumnsFullPageCard\",\n            \"noMoreDataCard\",\n            \"time\",\n        )\n\n    val seedColors = listOf(\n        0xFF6650A4,\n        0xFFF44336,\n        0xFFE91E63,\n        0xFF9C27B0,\n        0xFF3F51B5,\n        0xFF2196F3,\n        0xFF03A9F4,\n        0xFF00BCD4,\n        0xFF009688,\n        0xFF4FAF50,\n        0xFF8BC3A4,\n        0xFFCDDC39,\n        0xFFFFEB3B,\n        0xFFFFC107,\n        0xFFFF9800,\n        0xFFFF5722,\n        0xFF795548,\n        0xFF607D8F,\n        0xFFFF9CA8,\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/di/DataStoreModule.kt",
    "content": "package com.example.c001apk.compose.di\n\nimport android.content.Context\nimport androidx.datastore.core.DataStore\nimport androidx.datastore.core.DataStoreFactory\nimport androidx.datastore.dataStoreFile\nimport com.example.c001apk.compose.logic.datastore.UserPreferencesCompat\nimport com.example.c001apk.compose.logic.datastore.UserPreferencesSerializer\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Singleton\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject DataStoreModule {\n    @Provides\n    @Singleton\n    fun providesUserPreferencesDataStore(\n        @ApplicationContext context: Context,\n        userPreferencesSerializer: UserPreferencesSerializer\n    ): DataStore<UserPreferencesCompat> =\n        DataStoreFactory.create(\n            serializer = userPreferencesSerializer\n        ) {\n            context.dataStoreFile(\"user_preferences.pb\")\n        }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/di/DatabaseModule.kt",
    "content": "package com.example.c001apk.compose.di\n\nimport android.content.Context\nimport androidx.room.Room\nimport com.example.c001apk.compose.logic.dao.HistoryFavoriteDao\nimport com.example.c001apk.compose.logic.dao.HomeMenuDao\nimport com.example.c001apk.compose.logic.dao.RecentAtUserDao\nimport com.example.c001apk.compose.logic.dao.StringEntityDao\nimport com.example.c001apk.compose.logic.database.HistoryFavoriteDatabase\nimport com.example.c001apk.compose.logic.database.HomeMenuDatabase\nimport com.example.c001apk.compose.logic.database.RecentAtUserDatabase\nimport com.example.c001apk.compose.logic.database.StringEntityDatabase\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.android.qualifiers.ApplicationContext\nimport dagger.hilt.components.SingletonComponent\nimport javax.inject.Qualifier\nimport javax.inject.Singleton\n\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class UserBlackList\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class TopicBlackList\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class SearchHistory\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class RecentEmoji\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class BrowseHistory\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class FeedFavorite\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject DatabaseModule {\n\n    @RecentEmoji\n    @Singleton\n    @Provides\n    fun provideRecentEmojiDao(@RecentEmoji stringEntityDatabase: StringEntityDatabase): StringEntityDao {\n        return stringEntityDatabase.stringEntityDao()\n    }\n\n    @RecentEmoji\n    @Singleton\n    @Provides\n    fun provideRecentEmojiDatabase(@ApplicationContext context: Context): StringEntityDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            StringEntityDatabase::class.java, \"recent_emoji\"\n        )\n            .build()\n    }\n\n    @UserBlackList\n    @Singleton\n    @Provides\n    fun provideUserBlackListDao(@UserBlackList stringEntityDatabase: StringEntityDatabase): StringEntityDao {\n        return stringEntityDatabase.stringEntityDao()\n    }\n\n    @UserBlackList\n    @Singleton\n    @Provides\n    fun provideUserBlackListDatabase(@ApplicationContext context: Context): StringEntityDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            StringEntityDatabase::class.java, \"user_blacklist\"\n        )\n            .build()\n    }\n\n    @TopicBlackList\n    @Singleton\n    @Provides\n    fun provideTopicBlackListDao(@TopicBlackList stringEntityDatabase: StringEntityDatabase): StringEntityDao {\n        return stringEntityDatabase.stringEntityDao()\n    }\n\n    @TopicBlackList\n    @Singleton\n    @Provides\n    fun provideTopicBlackListDatabase(@ApplicationContext context: Context): StringEntityDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            StringEntityDatabase::class.java, \"topic_blacklist\"\n        )\n            .build()\n    }\n\n    @SearchHistory\n    @Singleton\n    @Provides\n    fun provideSearchHistoryDao(@SearchHistory stringEntityDatabase: StringEntityDatabase): StringEntityDao {\n        return stringEntityDatabase.stringEntityDao()\n    }\n\n    @SearchHistory\n    @Singleton\n    @Provides\n    fun provideSearchHistoryDatabase(@ApplicationContext context: Context): StringEntityDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            StringEntityDatabase::class.java, \"search_history\"\n        )\n            .build()\n    }\n\n    @BrowseHistory\n    @Singleton\n    @Provides\n    fun provideBrowseHistoryDao(@BrowseHistory browseHistoryDatabase: HistoryFavoriteDatabase): HistoryFavoriteDao {\n        return browseHistoryDatabase.historyFavoriteDao()\n    }\n\n    @BrowseHistory\n    @Singleton\n    @Provides\n    fun provideBrowseHistoryDatabase(@ApplicationContext context: Context): HistoryFavoriteDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            HistoryFavoriteDatabase::class.java, \"browse_history\"\n        ).build()\n    }\n\n    @FeedFavorite\n    @Singleton\n    @Provides\n    fun provideFeedFavoriteDao(@FeedFavorite feedFavoriteDatabase: HistoryFavoriteDatabase): HistoryFavoriteDao {\n        return feedFavoriteDatabase.historyFavoriteDao()\n    }\n\n    @FeedFavorite\n    @Singleton\n    @Provides\n    fun provideFeedFavoriteDatabase(@ApplicationContext context: Context): HistoryFavoriteDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            HistoryFavoriteDatabase::class.java, \"feed_favorite\"\n        )\n            .build()\n    }\n\n    @Singleton\n    @Provides\n    fun provideHomeMenuDao(homeMenuDatabase: HomeMenuDatabase): HomeMenuDao {\n        return homeMenuDatabase.homeMenuDao()\n    }\n\n    @Singleton\n    @Provides\n    fun provideHomeMenuDatabase(@ApplicationContext context: Context): HomeMenuDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            HomeMenuDatabase::class.java, \"home_menu\"\n        )\n            .build()\n    }\n\n    @Singleton\n    @Provides\n    fun provideRecentAtUserDao(recentAtUserDatabase: RecentAtUserDatabase): RecentAtUserDao {\n        return recentAtUserDatabase.recentAtUserDao()\n    }\n\n    @Singleton\n    @Provides\n    fun provideRecentAtUserDatabase(@ApplicationContext context: Context): RecentAtUserDatabase {\n        return Room.databaseBuilder(\n            context.applicationContext,\n            RecentAtUserDatabase::class.java, \"recent_at_user\"\n        )\n            .build()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/di/NetworkModule.kt",
    "content": "package com.example.c001apk.compose.di\n\nimport com.example.c001apk.compose.BuildConfig\nimport com.example.c001apk.compose.logic.network.ApiService\nimport com.example.c001apk.compose.util.AddCookiesInterceptor\nimport com.example.c001apk.compose.util.LoginCookiesInterceptor\nimport dagger.Module\nimport dagger.Provides\nimport dagger.hilt.InstallIn\nimport dagger.hilt.components.SingletonComponent\nimport okhttp3.OkHttpClient\nimport okhttp3.logging.HttpLoggingInterceptor\nimport retrofit2.Retrofit\nimport retrofit2.converter.gson.GsonConverterFactory\nimport javax.inject.Qualifier\nimport javax.inject.Singleton\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class Api1Service\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class Api1ServiceNoRedirect\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class Api2Service\n\n@Qualifier\n@Retention(AnnotationRetention.BINARY)\nannotation class AccountService\n\n@Module\n@InstallIn(SingletonComponent::class)\nobject NetworkModule {\n\n    private const val API_BASE_URL = \"https://api.coolapk.com\"\n    private const val API2_BASE_URL = \"https://api2.coolapk.com\"\n    private const val ACCOUNT_BASE_URL = \"https://account.coolapk.com\"\n\n    @Api1Service\n    @Singleton\n    @Provides\n    fun provideApi1Service(@Api1Service retrofit: Retrofit): ApiService {\n        return retrofit.create(ApiService::class.java)\n    }\n\n    @Api1ServiceNoRedirect\n    @Singleton\n    @Provides\n    fun provideApi1ServiceNo(@Api1ServiceNoRedirect retrofit: Retrofit): ApiService {\n        return retrofit.create(ApiService::class.java)\n    }\n\n    @Api2Service\n    @Singleton\n    @Provides\n    fun provideApi2Service(@Api2Service retrofit: Retrofit): ApiService {\n        return retrofit.create(ApiService::class.java)\n    }\n\n    @AccountService\n    @Singleton\n    @Provides\n    fun provideAccountService(@AccountService retrofit: Retrofit): ApiService {\n        return retrofit.create(ApiService::class.java)\n    }\n\n    @Api1Service\n    @Singleton\n    @Provides\n    fun provideApi1ServiceRetrofit(@Api1Service okHttpClient: OkHttpClient): Retrofit {\n        return Retrofit.Builder()\n            .baseUrl(API_BASE_URL)\n            .addConverterFactory(GsonConverterFactory.create())\n            .client(okHttpClient)\n            .build()\n    }\n\n    @Api1ServiceNoRedirect\n    @Singleton\n    @Provides\n    fun provideApi1ServiceNoRetrofit(@Api1ServiceNoRedirect okHttpClient: OkHttpClient): Retrofit {\n        return Retrofit.Builder()\n            .baseUrl(API_BASE_URL)\n            .addConverterFactory(GsonConverterFactory.create())\n            .client(okHttpClient)\n            .build()\n    }\n\n    @Api2Service\n    @Singleton\n    @Provides\n    fun provideApi2ServiceRetrofit(@Api1Service okHttpClient: OkHttpClient): Retrofit {\n        return Retrofit.Builder()\n            .baseUrl(API2_BASE_URL)\n            .addConverterFactory(GsonConverterFactory.create())\n            .client(okHttpClient)\n            .build()\n    }\n\n    @AccountService\n    @Singleton\n    @Provides\n    fun provideAccountServiceRetrofit(@AccountService okHttpClient: OkHttpClient): Retrofit {\n        return Retrofit.Builder()\n            .baseUrl(ACCOUNT_BASE_URL)\n            .addConverterFactory(GsonConverterFactory.create())\n            .client(okHttpClient)\n            .build()\n    }\n\n    @Api1Service\n    @Singleton\n    @Provides\n    fun provideOkHttpClient(): OkHttpClient {\n        return OkHttpClient.Builder()\n            .addInterceptor(AddCookiesInterceptor)\n            .addInterceptor(\n                HttpLoggingInterceptor().setLevel(\n                    if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY\n                    else HttpLoggingInterceptor.Level.NONE\n                )\n            )\n            .followRedirects(true)\n            .build()\n    }\n\n    @Api1ServiceNoRedirect\n    @Singleton\n    @Provides\n    fun provideNoOkHttpClient(): OkHttpClient {\n        return OkHttpClient.Builder()\n            .addInterceptor(AddCookiesInterceptor)\n            .addInterceptor(\n                HttpLoggingInterceptor().setLevel(\n                    if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY\n                    else HttpLoggingInterceptor.Level.NONE\n                )\n            )\n            .followRedirects(false)\n            .build()\n    }\n\n    @AccountService\n    @Singleton\n    @Provides\n    fun provideAccountServiceOkHttpClient(): OkHttpClient {\n        return OkHttpClient.Builder()\n            .addInterceptor(LoginCookiesInterceptor)\n            .addInterceptor(\n                HttpLoggingInterceptor().setLevel(\n                    if (BuildConfig.DEBUG) HttpLoggingInterceptor.Level.BODY\n                    else HttpLoggingInterceptor.Level.NONE\n                )\n            )\n            .build()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/dao/HistoryFavoriteDao.kt",
    "content": "package com.example.c001apk.compose.logic.dao\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.Query\nimport com.example.c001apk.compose.logic.model.FeedEntity\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface HistoryFavoriteDao {\n\n    @Insert\n    suspend fun insert(data: FeedEntity)\n\n    @Query(\"SELECT * FROM FeedEntity ORDER BY time DESC\")\n    suspend fun loadAllList(): List<FeedEntity>\n\n    @Query(\"SELECT * FROM FeedEntity ORDER BY time DESC\")\n    fun loadAllListLive(): LiveData<List<FeedEntity>>\n\n    @Query(\"SELECT * FROM FeedEntity ORDER BY time DESC\")\n    fun loadAllListFlow(): Flow<List<FeedEntity>>\n\n    @Query(\"SELECT 1 FROM FeedEntity WHERE id = :id LIMIT 1\")\n    suspend fun isExist(id: String): Boolean\n\n    @Query(\"DELETE FROM FeedEntity WHERE id = :id\")\n    suspend fun delete(id: String)\n\n    @Query(\"DELETE FROM FeedEntity\")\n    suspend fun deleteAll()\n\n    @Query(\"DELETE FROM FeedEntity WHERE uid = :uid\")\n    suspend fun deleteByUid(uid:String)\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/dao/HomeMenuDao.kt",
    "content": "package com.example.c001apk.compose.logic.dao\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Update\nimport com.example.c001apk.compose.logic.model.HomeMenu\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface HomeMenuDao {\n\n    @Insert\n    suspend fun insert(menu: HomeMenu)\n\n    @Insert\n    suspend fun insertList(list: List<HomeMenu>)\n\n    @Query(\"SELECT * FROM HomeMenu ORDER BY position ASC\")\n    suspend fun loadAllList(): List<HomeMenu>\n\n    @Query(\"SELECT * FROM HomeMenu ORDER BY position ASC\")\n    fun loadAllListLive(): LiveData<List<HomeMenu>>\n\n    @Query(\"SELECT * FROM HomeMenu ORDER BY position ASC\")\n    fun loadAllListFlow(): Flow<List<HomeMenu>>\n\n    @Query(\"SELECT 1 FROM HomeMenu WHERE title = :title LIMIT 1\")\n    suspend fun isExist(title: String): Boolean\n\n    @Query(\"DELETE FROM HomeMenu WHERE title = :title\")\n    suspend fun delete(title: String)\n\n    @Query(\"DELETE FROM HomeMenu\")\n    suspend fun deleteAll()\n\n    @Update\n    suspend fun updateList(list:List<HomeMenu>)\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/dao/RecentAtUserDao.kt",
    "content": "package com.example.c001apk.compose.logic.dao\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.example.c001apk.compose.logic.model.RecentAtUser\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface RecentAtUserDao {\n\n    @Insert\n    suspend fun insert(data: RecentAtUser)\n\n    @Insert\n    suspend fun insertList(list: List<RecentAtUser>)\n\n    @Query(\"SELECT * FROM RecentAtUser ORDER BY id DESC\")\n    suspend fun loadAllList(): List<RecentAtUser>\n\n    @Query(\"SELECT * FROM RecentAtUser ORDER BY id DESC\")\n    fun loadAllListLive(): LiveData<List<RecentAtUser>>\n\n    @Query(\"SELECT * FROM RecentAtUser ORDER BY id DESC\")\n    fun loadAllListFlow(): Flow<List<RecentAtUser>>\n\n    @Query(\"SELECT 1 FROM RecentAtUser WHERE username = :username LIMIT 1\")\n    suspend fun isExist(username: String): Boolean\n\n    @Transaction\n    @Query(\"SELECT 1 FROM RecentAtUser WHERE :username LIKE '%' || username || '%' LIMIT 1\")\n    suspend fun isContain(username: String): Boolean\n\n    @Query(\"DELETE FROM RecentAtUser WHERE username = :username\")\n    suspend fun delete(username: String)\n\n    @Delete\n    suspend fun delete(data: RecentAtUser)\n\n    @Query(\"DELETE FROM RecentAtUser\")\n    suspend fun deleteAll()\n\n    @Query(\"UPDATE RecentAtUser SET id = :newId WHERE username = :username\")\n    suspend fun updateUser(username: String, newId: Long)\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/dao/StringEntityDao.kt",
    "content": "package com.example.c001apk.compose.logic.dao\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface StringEntityDao {\n\n    @Insert\n    suspend fun insert(data: StringEntity)\n\n    @Insert\n    suspend fun insertList(list: List<StringEntity>)\n\n    @Query(\"SELECT * FROM StringEntity ORDER BY id DESC\")\n    suspend fun loadAllList(): List<StringEntity>\n\n    @Query(\"SELECT * FROM StringEntity ORDER BY id DESC\")\n    fun loadAllListLive(): LiveData<List<StringEntity>>\n\n    @Query(\"SELECT * FROM StringEntity ORDER BY id DESC\")\n    fun loadAllListFlow(): Flow<List<StringEntity>>\n\n    @Query(\"SELECT 1 FROM StringEntity WHERE data = :data LIMIT 1\")\n    suspend fun isExist(data: String): Boolean\n\n    @Transaction\n    @Query(\"SELECT 1 FROM StringEntity WHERE :data LIKE '%' || data || '%' LIMIT 1\")\n    suspend fun isContain(data: String): Boolean\n\n    @Query(\"DELETE FROM StringEntity WHERE data = :data\")\n    suspend fun delete(data: String)\n\n    @Delete\n    suspend fun delete(data: StringEntity)\n\n    @Query(\"DELETE FROM StringEntity\")\n    suspend fun deleteAll()\n\n    @Query(\"UPDATE StringEntity SET id = :newId WHERE data = :data\")\n    suspend fun updateHistory(data: String, newId: Long)\n\n    @Query(\"UPDATE StringEntity SET id = :newId , data = :newData WHERE data = :oldData\")\n    suspend fun updateEmoji(oldData: String, newData: String, newId: Long)\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/database/HistoryFavoriteDatabase.kt",
    "content": "package com.example.c001apk.compose.logic.database\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport com.example.c001apk.compose.logic.dao.HistoryFavoriteDao\nimport com.example.c001apk.compose.logic.model.FeedEntity\n\n\n@Database(version = 1, entities = [FeedEntity::class])\nabstract class HistoryFavoriteDatabase : RoomDatabase() {\n    abstract fun historyFavoriteDao(): HistoryFavoriteDao\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/database/HomeMenuDatabase.kt",
    "content": "package com.example.c001apk.compose.logic.database\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport com.example.c001apk.compose.logic.dao.HomeMenuDao\nimport com.example.c001apk.compose.logic.model.HomeMenu\n\n@Database(version = 1, entities = [HomeMenu::class])\nabstract class HomeMenuDatabase : RoomDatabase() {\n    abstract fun homeMenuDao(): HomeMenuDao\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/database/RecentAtUserDatabase.kt",
    "content": "package com.example.c001apk.compose.logic.database\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport com.example.c001apk.compose.logic.dao.RecentAtUserDao\nimport com.example.c001apk.compose.logic.model.RecentAtUser\n\n@Database(version = 1, entities = [RecentAtUser::class])\nabstract class RecentAtUserDatabase : RoomDatabase() {\n    abstract fun recentAtUserDao(): RecentAtUserDao\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/database/StringEntityDatabase.kt",
    "content": "package com.example.c001apk.compose.logic.database\n\nimport androidx.room.Database\nimport androidx.room.RoomDatabase\nimport com.example.c001apk.compose.logic.dao.StringEntityDao\nimport com.example.c001apk.compose.logic.model.StringEntity\n\n@Database(version = 1, entities = [StringEntity::class])\nabstract class StringEntityDatabase : RoomDatabase() {\n    abstract fun stringEntityDao(): StringEntityDao\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/datastore/UserPreferencesCompat.kt",
    "content": "package com.example.c001apk.compose.logic.datastore\n\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.runtime.Composable\nimport com.example.c001apk.compose.FollowType\nimport com.example.c001apk.compose.ThemeMode\nimport com.example.c001apk.compose.ThemeType\nimport com.example.c001apk.compose.UserPreferences\nimport com.example.c001apk.compose.constant.Constants.API_VERSION\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.VERSION_CODE\nimport com.example.c001apk.compose.constant.Constants.VERSION_NAME\n\ndata class UserPreferencesCompat(\n    val themeMode: ThemeMode,\n    val materialYou: Boolean,\n    val pureBlack: Boolean,\n    val fontScale: Float,\n    val contentScale: Float,\n    val szlmId: String,\n    val imageQuality: Int,\n    val imageFilter: Boolean,\n    val openInBrowser: Boolean,\n    val showSquare: Boolean,\n    val recordHistory: Boolean,\n    val showEmoji: Boolean,\n    val checkUpdate: Boolean,\n    val checkCount: Boolean,\n    val versionName: String,\n    val apiVersion: String,\n    val versionCode: String,\n    val manufacturer: String,\n    val brand: String,\n    val model: String,\n    val buildNumber: String,\n    val sdkInt: String,\n    val androidVersion: String,\n    val userAgent: String,\n    val xAppDevice: String,\n    val xAppToken: String,\n    val isLogin: Boolean,\n    val userAvatar: String,\n    val username: String,\n    val level: String,\n    val experience: String,\n    val nextLevelExperience: String,\n    val uid: String,\n    val token: String,\n    val followType: FollowType,\n    val recentIds: String,\n    val checkCountPeriod: Int,\n    val installTime: String,\n    val themeType: ThemeType,\n    val seedColor: String,\n    val paletteStyle: Int,\n) {\n    constructor(original: UserPreferences) : this(\n        themeMode = original.themeMode,\n        materialYou = original.materialYou,\n        pureBlack = original.pureBlack,\n        fontScale = original.fontScale,\n        contentScale = original.contentScale,\n        szlmId = original.szlmId,\n        imageQuality = original.imageQuality,\n        imageFilter = original.imageFilter,\n        openInBrowser = original.openInBrowser,\n        showSquare = original.showSquare,\n        recordHistory = original.recordHistory,\n        showEmoji = original.showEmoji,\n        checkUpdate = original.checkUpdate,\n        checkCount = original.checkCount,\n        versionName = original.versionName,\n        apiVersion = original.apiVersion,\n        versionCode = original.versionCode,\n        manufacturer = original.manufacturer,\n        brand = original.brand,\n        model = original.model,\n        buildNumber = original.buildNumber,\n        sdkInt = original.sdkInt,\n        androidVersion = original.androidVersion,\n        userAgent = original.userAgent,\n        xAppDevice = original.xAppDevice,\n        xAppToken = original.xAppToken,\n        isLogin = original.isLogin,\n        userAvatar = original.userAvatar,\n        username = original.username,\n        level = original.level,\n        experience = original.experience,\n        nextLevelExperience = original.nextLevelExperience,\n        uid = original.uid,\n        token = original.token,\n        followType = original.followType,\n        recentIds = original.recentIds,\n        checkCountPeriod = original.checkCountPeriod,\n        installTime = original.installTime,\n        themeType = original.themeType,\n        seedColor = original.seedColor,\n        paletteStyle = original.paletteStyle,\n    )\n\n    @Composable\n    fun isDarkMode() = when (themeMode) {\n        ThemeMode.ALWAYS_OFF -> false\n        ThemeMode.ALWAYS_ON -> true\n        else -> isSystemInDarkTheme()\n    }\n\n    fun toProto(): UserPreferences = UserPreferences.newBuilder()\n        .setThemeMode(themeMode)\n        .setMaterialYou(materialYou)\n        .setPureBlack(pureBlack)\n        .setFontScale(fontScale)\n        .setContentScale(contentScale)\n        .setSzlmId(szlmId)\n        .setImageQuality(imageQuality)\n        .setImageFilter(imageFilter)\n        .setOpenInBrowser(openInBrowser)\n        .setShowSquare(showSquare)\n        .setRecordHistory(recordHistory)\n        .setShowEmoji(showEmoji)\n        .setCheckUpdate(checkUpdate)\n        .setCheckCount(checkCount)\n        .setVersionName(versionName)\n        .setApiVersion(apiVersion)\n        .setVersionCode(versionCode)\n        .setManufacturer(manufacturer)\n        .setBrand(brand)\n        .setModel(model)\n        .setBuildNumber(buildNumber)\n        .setSdkInt(sdkInt)\n        .setAndroidVersion(androidVersion)\n        .setUserAgent(userAgent)\n        .setXAppDevice(xAppDevice)\n        .setXAppToken(xAppToken)\n        .setIsLogin(isLogin)\n        .setUserAvatar(userAvatar)\n        .setUsername(username)\n        .setLevel(level)\n        .setExperience(experience)\n        .setNextLevelExperience(nextLevelExperience)\n        .setUid(uid)\n        .setToken(token)\n        .setFollowType(followType)\n        .setRecentIds(recentIds)\n        .setCheckCountPeriod(checkCountPeriod)\n        .setInstallTime(installTime)\n        .setThemeType(themeType)\n        .setSeedColor(seedColor)\n        .setPaletteStyle(paletteStyle)\n        .build()\n\n    companion object {\n        fun default() = UserPreferencesCompat(\n            themeMode = ThemeMode.FOLLOW_SYSTEM,\n            materialYou = true,\n            pureBlack = false,\n            fontScale = 1.00f,\n            contentScale = 1.00f,\n            szlmId = EMPTY_STRING,\n            imageQuality = 0,\n            imageFilter = true,\n            openInBrowser = false,\n            showSquare = true,\n            recordHistory = true,\n            showEmoji = true,\n            checkUpdate = true,\n            checkCount = true,\n            versionName = VERSION_NAME,\n            apiVersion = API_VERSION,\n            versionCode = VERSION_CODE,\n            manufacturer = EMPTY_STRING,\n            brand = EMPTY_STRING,\n            model = EMPTY_STRING,\n            buildNumber = EMPTY_STRING,\n            sdkInt = EMPTY_STRING,\n            androidVersion = EMPTY_STRING,\n            userAgent = EMPTY_STRING,\n            xAppDevice = EMPTY_STRING,\n            xAppToken = EMPTY_STRING,\n            isLogin = false,\n            userAvatar = EMPTY_STRING,\n            username = EMPTY_STRING,\n            level = EMPTY_STRING,\n            experience = EMPTY_STRING,\n            nextLevelExperience = EMPTY_STRING,\n            uid = EMPTY_STRING,\n            token = EMPTY_STRING,\n            followType = FollowType.ALL,\n            recentIds = EMPTY_STRING,\n            checkCountPeriod = 5,\n            installTime = EMPTY_STRING,\n            themeType = ThemeType.Default,\n            seedColor = EMPTY_STRING,\n            paletteStyle = 0,\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/datastore/UserPreferencesDataSource.kt",
    "content": "package com.example.c001apk.compose.logic.datastore\n\nimport androidx.datastore.core.DataStore\nimport com.example.c001apk.compose.FollowType\nimport com.example.c001apk.compose.ThemeMode\nimport com.example.c001apk.compose.ThemeType\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\n\nclass UserPreferencesDataSource @Inject constructor(\n    private val userPreferences: DataStore<UserPreferencesCompat>\n) {\n    val data get() = userPreferences.data\n\n    suspend fun setThemeMode(value: ThemeMode) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(themeMode = value) }\n    }\n\n    suspend fun setMaterialYou(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(materialYou = value) }\n    }\n\n    suspend fun setPureBlack(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(pureBlack = value) }\n    }\n\n    suspend fun setFontScale(value: Float) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(fontScale = value) }\n    }\n\n    suspend fun setContentScale(value: Float) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(contentScale = value) }\n    }\n\n    suspend fun setSZLMId(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(szlmId = value) }\n    }\n\n    suspend fun setImageQuality(value: Int) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(imageQuality = value) }\n    }\n\n    suspend fun setImageFilter(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(imageFilter = value) }\n    }\n\n    suspend fun setOpenInBrowser(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(openInBrowser = value) }\n    }\n\n    suspend fun setShowSquare(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(showSquare = value) }\n    }\n\n    suspend fun setRecordHistory(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(recordHistory = value) }\n    }\n\n    suspend fun setShowEmoji(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(showEmoji = value) }\n    }\n\n    suspend fun setCheckUpdate(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(checkUpdate = value) }\n    }\n\n    suspend fun setCheckCount(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(checkCount = value) }\n    }\n\n    suspend fun setVersionName(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(versionName = value) }\n    }\n\n    suspend fun setApiVersion(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(apiVersion = value) }\n    }\n\n    suspend fun setVersionCode(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(versionCode = value) }\n    }\n\n    suspend fun setManufacturer(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(manufacturer = value) }\n    }\n\n    suspend fun setBrand(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(brand = value) }\n    }\n\n    suspend fun setModel(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(model = value) }\n    }\n\n    suspend fun setBuildNumber(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(buildNumber = value) }\n    }\n\n    suspend fun setSdkInt(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(sdkInt = value) }\n    }\n\n    suspend fun setAndroidVersion(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(androidVersion = value) }\n    }\n\n    suspend fun setUserAgent(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(userAgent = value) }\n    }\n\n    suspend fun setXAppDevice(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(xAppDevice = value) }\n    }\n\n    suspend fun setXAppToken(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(xAppToken = value) }\n    }\n\n    suspend fun setIsLogin(value: Boolean) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(isLogin = value) }\n    }\n\n    suspend fun setUserAvatar(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(userAvatar = value) }\n    }\n\n    suspend fun setUsername(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(username = value) }\n    }\n\n    suspend fun setLevel(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(level = value) }\n    }\n\n    suspend fun setExperience(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(experience = value) }\n    }\n\n    suspend fun setNextLevelExperience(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(nextLevelExperience = value) }\n    }\n\n    suspend fun setUid(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(uid = value) }\n    }\n\n    suspend fun setToken(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(token = value) }\n    }\n\n    suspend fun setFollowType(value: FollowType) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(followType = value) }\n    }\n\n    suspend fun setRecentIds(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(recentIds = value) }\n    }\n\n    suspend fun setCheckCountPeriod(value: Int) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(checkCountPeriod = value) }\n    }\n\n    suspend fun setInstallTime(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(installTime = value) }\n    }\n\n    suspend fun setThemeType(value: ThemeType) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(themeType = value) }\n    }\n\n    suspend fun setSeedColor(value: String) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(seedColor = value) }\n    }\n\n    suspend fun setPaletteStyle(value: Int) = withContext(Dispatchers.IO) {\n        userPreferences.updateData { it.copy(paletteStyle = value) }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/datastore/UserPreferencesSerializer.kt",
    "content": "package com.example.c001apk.compose.logic.datastore\n\nimport androidx.datastore.core.CorruptionException\nimport androidx.datastore.core.Serializer\nimport com.example.c001apk.compose.UserPreferences\nimport com.google.protobuf.InvalidProtocolBufferException\nimport java.io.InputStream\nimport java.io.OutputStream\nimport javax.inject.Inject\n\nclass UserPreferencesSerializer @Inject constructor() : Serializer<UserPreferencesCompat> {\n    override val defaultValue = UserPreferencesCompat.default()\n\n    override suspend fun readFrom(input: InputStream) =\n        try {\n            UserPreferences.parseFrom(input).let(::UserPreferencesCompat)\n        } catch (e: InvalidProtocolBufferException) {\n            throw CorruptionException(\"cannot read proto\", e)\n        }\n\n    override suspend fun writeTo(t: UserPreferencesCompat, output: OutputStream) {\n        t.toProto().writeTo(output)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/AppItem.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport android.content.pm.PackageInfo\n\ndata class AppItem(\n    var label: String,\n    val packageInfo: PackageInfo\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/CheckCountResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.google.gson.annotations.SerializedName\n\ndata class CheckCountResponse(\n    val status: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: Data?\n) {\n\n    data class Data(\n        val notification: Int,\n        @SerializedName(\"contacts_follow\")\n        val contactsFollow: Int,\n        val message: Int,\n        val atme: Int,\n        val atcommentme: Int,\n        val commentme: Int,\n        val feedlike: Int,\n        val badge: Int,\n        val dateline: String\n    )\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/CheckResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.google.gson.annotations.SerializedName\n\ndata class CheckResponse(\n    val status: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: Data?\n) {\n\n    data class Data(\n        val id: String?,\n        val status: Int,\n        @SerializedName(\"message_status\") val messageStatus: Int?,\n        val uid: String,\n        val username: String,\n        val token: String,\n        val refreshToken: String,\n        val userAvatar: String,\n        val notifyCount: NotifyCount,\n    )\n\n    data class NotifyCount(\n        val notification: Int,\n        @SerializedName(\"contacts_follow\") val contactsFollow: Int,\n        val message: Int,\n        val atme: Int,\n        val atcommentme: Int,\n        val commentme: Int,\n        val feedlike: Int,\n        val badge: Int,\n        val dateline: String\n    )\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/CreateFeedResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.google.gson.annotations.SerializedName\n\ndata class CreateFeedResponse(\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: Data?\n) {\n    data class Data(\n        val status: Int?,\n        val error: Int?,\n        val messageStatus: String?,\n        val id: String?,\n        val type: String?,\n        val message: String?,\n        val pic: String,\n        @SerializedName(\"device_title\") val deviceTitle: String?,\n        @SerializedName(\"message_signature\") val messageSignature: String?,\n        val picArr: List<String>?,\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/DeviceInfo.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\ndata class DeviceInfo(\n    val deviceId: String,\n    val mac: String,\n    val manufacturer: String,\n    val brand: String,\n    val model: String,\n    val display: String,\n    val oaid: String\n)\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/FeedAdapter.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse.Feed\nimport com.google.gson.Gson\nimport com.google.gson.TypeAdapter\nimport com.google.gson.TypeAdapterFactory\nimport com.google.gson.reflect.TypeToken\nimport com.google.gson.stream.JsonReader\nimport com.google.gson.stream.JsonToken\nimport com.google.gson.stream.JsonWriter\nimport java.io.IOException\n\nobject FeedAdapterFactory : TypeAdapterFactory {\n    override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {\n        return FeedAdapter(gson) as TypeAdapter<T>\n    }\n}\n\nclass FeedAdapter(private val gson: Gson) : TypeAdapter<Feed?>() {\n    @Throws(IOException::class)\n    override fun write(jsonWriter: JsonWriter, feed: Feed?) {\n        throw RuntimeException(\"Not implemented\")\n    }\n\n    @Throws(IOException::class)\n    override fun read(jsonReader: JsonReader): Feed? {\n        return when (jsonReader.peek()) {\n            JsonToken.NUMBER -> Feed(id = jsonReader.nextInt().toString())\n\n            JsonToken.BEGIN_OBJECT ->\n                gson.fromJson(jsonReader, Feed::class.java)\n\n            else -> throw RuntimeException(\"Expected object or int, not \" + jsonReader.peek())\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/FeedArticleContentBean.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport java.util.UUID\n\ndata class FeedArticleContentBean(\n    val key: String = UUID.randomUUID().toString(),\n    val type: String?,\n    val message: String?,\n    val url: String?,\n    val description: String?,\n    val title: String?,\n    val subTitle: String?,\n    val logo: String?,\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/FeedContentResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class FeedContentResponse(\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: HomeFeedResponse.Data?,\n) : Parcelable\n\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/FeedEntity.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity\ndata class FeedEntity(\n    @PrimaryKey(autoGenerate = false)\n    val id: String,\n    val uid: String,\n    val uname: String,\n    val avatar: String,\n    val device: String,\n    val message: String,\n    val pubDate: String,\n    val time: String = System.currentTimeMillis().toString(),\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/HomeFeedResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\n\nimport android.os.Parcelable\nimport com.google.gson.annotations.JsonAdapter\nimport com.google.gson.annotations.SerializedName\nimport kotlinx.parcelize.Parcelize\n\ndata class HomeFeedResponse(\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val messageStatus: Int?,\n    val data: List<Data>?,\n) {\n\n    @Parcelize\n    data class Data(\n        val isStickTop: Int?,\n        val likeUserInfo: UserInfo?,\n        val ukey: String?,\n        val islast: Int?,\n        val isnew: Int?,\n        @SerializedName(\"message_pic\")\n        val messagePic: String?,\n        val messageUid: String?,\n        val messageUsername: String?,\n        val messageUserAvatar: String?,\n        var unreadNum: Int?,\n        @SerializedName(\"is_top\")\n        var isTop: Int?,\n        val shorttitle: String?,\n        val packageName: String?,\n        @SerializedName(\"pkg_bit_type\")\n        val pkgBitType: Int?,\n        var localVersionName: String?,\n        var localVersionCode: Long?,\n        var expand: Boolean = false,\n        val forwardid: String?,\n        @SerializedName(\"source_id\")\n        val sourceId: String?,\n        val istag: Int?,\n        val likeUsername: String?,\n        val likeUid: String?,\n        val likeAvatar: String?,\n        val fromUserAvatar: String?,\n        val fromusername: String?,\n        val fromuid: String?,\n        val note: String?,\n        @SerializedName(\"extra_key\")\n        val extraKey: String?,\n        val feedUid: String?,\n        val fuid: String?,\n        val rid: Long?,\n        val forwardSourceType: String?,\n        val forwardSourceFeed: ForwardSourceFeed?,\n        @SerializedName(\"comment_num\")\n        val commentNum: String?,\n        @SerializedName(\"fans_num\")\n        val fansNum: String?,\n        @SerializedName(\"target_id\")\n        val targetId: String?,\n        @SerializedName(\"target_type\")\n        val targetType: String?,\n        @SerializedName(\"target_type_title\")\n        val targetTypeTitle: String?,\n        var replyMeRows: List<Data>?,\n        @SerializedName(\"cover_pic\")\n        val coverPic: String?,\n        @SerializedName(\"is_open\")\n        val isOpen: Int?,\n        @SerializedName(\"is_open_title\")\n        val isOpenTitle: String?,\n        @SerializedName(\"item_num\")\n        val itemNum: String?,\n        @SerializedName(\"follow_num\")\n        val followNum: String?,\n        var description: String?,\n        val subTitle: String?,\n        val likeTime: Long?,\n        @SerializedName(\"extra_title\")\n        val extraTitle: String?,\n        @SerializedName(\"extra_url\")\n        val extraUrl: String?,\n        @SerializedName(\"extra_pic\")\n        val extraPic: String?,\n        val feedTypeName: String?,\n        val vote: Vote?,\n        @SerializedName(\"message_cover\")\n        val messageCover: String?,\n        @SerializedName(\"message_title\")\n        val messageTitle: String?,\n        @SerializedName(\"message_raw_output\")\n        val messageRawOutput: String?,\n        val relationRows: ArrayList<RelationRows>?,\n        val targetRow: TargetRow?,\n        @SerializedName(\"change_count\")\n        val changeCount: Int?,\n        val isModified: Int?,\n        @SerializedName(\"ip_location\")\n        val ipLocation: String?,\n        val isFeedAuthor: Int?,\n        var topReplyRows: List<Data>?,\n        val extraDataArr: ExtraDataArr?,\n        val intro: String?,\n        @SerializedName(\"tag_pics\")\n        val tagPics: List<String>?,\n        val tabList: List<TabList>?,\n        val displayUsername: String?,\n        val cover: String?,\n        val selectedTab: String?,\n        val homeTabCardRows: List<HomeTabCardRows>?,\n        @SerializedName(\"be_like_num\")\n        val beLikeNum: String?,\n        val version: String?,\n        val apkversionname: String?,\n        val apkversioncode: String?,\n        val apksize: String?,\n        val apkfile: String?,\n        val lastupdate: Long?,\n        val follow: String?,\n        val level: String?,\n        val fans: String?,\n        val logintime: Long?,\n        val experience: String?,\n        val regdate: Long?,\n        @SerializedName(\"next_level_experience\")\n        val nextLevelExperience: String?,\n        val bio: String?,\n        @JsonAdapter(FeedAdapterFactory::class)\n        val feed: Feed?,\n        val gender: Int?,\n        val city: String?,\n        val downnum: String?,\n        val downCount: String?,\n        val apkname: String?,\n        val entityType: String?,\n        val type: String?,\n        val feedType: String?,\n        val fetchType: String?,\n        val entityTemplate: String?,\n        var entities: MutableList<Entities>?,\n        val id: String?,\n        val fid: String?,\n        val url: String?,\n        val uid: String?,\n        val ruid: String?,\n        val changelog: String?,\n        var username: String?,\n        val rusername: String?,\n        val tpic: String?,\n        val message: String?,\n        val pic: String?,\n        val tags: String?,\n        val ttitle: String?,\n        var likenum: String?,\n        val commentnum: String?,\n        val replynum: String?,\n        val forwardnum: String?,\n        val favnum: String?,\n        val dateline: Long?,\n        @SerializedName(\"create_time\")\n        val createTime: String?,\n        @SerializedName(\"device_title\")\n        val deviceTitle: String?,\n        @SerializedName(\"device_name\")\n        val deviceName: String?,\n        @SerializedName(\"recent_reply_ids\")\n        val recentReplyIds: String?,\n        @SerializedName(\"recent_like_list\")\n        val recentLikeList: String?,\n        val entityId: String?,\n        val userAvatar: String?,\n        val infoHtml: String?,\n        val title: String?,\n        val commentStatusText: String?,\n        @SerializedName(\"comment_status\")\n        val commentStatus: Int?,\n        val picArr: List<String>?,\n        var replyRows: List<Data>?,\n        val replyRowsMore: Int?,\n        val logo: String?,\n        @SerializedName(\"hot_num\")\n        val hotNum: String?,\n        @SerializedName(\"feed_comment_num\")\n        val feedCommentNum: String?,\n        @SerializedName(\"hot_num_txt\")\n        val hotNumTxt: String?,\n        @SerializedName(\"feed_comment_num_txt\")\n        val feedCommentNumTxt: String?,\n        @SerializedName(\"commentnum_txt\")\n        val commentnumTxt: String?,\n        val commentCount: String?,\n        @SerializedName(\"alias_title\")\n        val aliasTitle: String?,\n        val userAction: UserAction?,\n        val userInfo: UserInfo?,\n        val fUserInfo: UserInfo?,\n        var isFollow: Int?,\n    ) : Parcelable\n\n    @Parcelize\n    data class Feed(\n        val id: String? = null,\n        val uid: String? = null,\n        val username: String? = null,\n        val message: String? = null,\n        val pic: String? = null,\n        val url: String? = null,\n    ) : Parcelable\n\n    @Parcelize\n    data class ForwardSourceFeed(\n        val entityType: String?,\n        val feedType: String?,\n        val id: String?,\n        val username: String?,\n        val uid: String?,\n        val url: String?,\n        val message: String?,\n        @SerializedName(\"message_title\")\n        val messageTitle: String?,\n        val pic: String?,\n        val picArr: List<String>?,\n    ) : Parcelable\n\n    @Parcelize\n    data class Vote(\n        val id: String?,\n        val type: Int?,\n        @SerializedName(\"start_time\")\n        val startTime: Long?,\n        @SerializedName(\"end_time\")\n        val endTime: Long?,\n        @SerializedName(\"total_vote_num\")\n        val totalVoteNum: Int?,\n        @SerializedName(\"total_comment_num\")\n        val totalCommentNum: Int?,\n        @SerializedName(\"total_option_num\")\n        val totalOptionNum: Int?,\n        @SerializedName(\"max_select_num\")\n        val maxSelectNum: Int?,\n        @SerializedName(\"min_select_num\")\n        val minSelectNum: Int?,\n        @SerializedName(\"message_title\")\n        val messageTitle: String?,\n        val options: List<Option>?,\n    ) : Parcelable\n\n    @Parcelize\n    data class Option(\n        @SerializedName(\"total_select_num\")\n        val totalSelectNum: Long?,\n        val id: String?,\n        @SerializedName(\"vote_id\")\n        val voteId: String?,\n        val title: String?,\n        val status: Int?,\n        val order: Int?,\n        val color: String?,\n    ) : Parcelable\n\n    @Parcelize\n    data class RelationRows(\n        val id: String?,\n        val logo: String?,\n        val title: String?,\n        val url: String?,\n        val entityType: String?,\n    ) : Parcelable\n\n    @Parcelize\n    data class TargetRow(\n        val id: String?,\n        val logo: String?,\n        val title: String?,\n        val url: String?,\n        val entityType: String?,\n        val targetType: String?,\n    ) : Parcelable\n\n    @Parcelize\n    data class ExtraDataArr(\n        val pageTitle: String?,\n        val cardPageName: String?,\n    ) : Parcelable\n\n    @Parcelize\n    data class UserInfo(\n        val uid: String?,\n        val username: String?,\n        val level: Int?,\n        val logintime: Long?,\n        val regdate: String?,\n        val entityType: String?,\n        val displayUsername: String?,\n        val userAvatar: String?,\n        val cover: String?,\n        val fans: String?,\n        val follow: String?,\n        val bio: String?,\n    ) : Parcelable\n\n    @Parcelize\n    data class TabList(\n        val title: String?,\n        val url: String?,\n        @SerializedName(\"page_name\")\n        val pageName: String?,\n        val entityType: String?,\n        val entityId: Int?,\n    ) : Parcelable\n\n    @Parcelize\n    data class HomeTabCardRows(\n        val entityType: String?,\n        val entityTemplate: String?,\n        val title: String?,\n        val url: String?,\n        val entities: List<Entities>?,\n        val entityId: String?,\n    ) : Parcelable\n\n    @Parcelize\n    data class UserAction(\n        var like: Int?,\n        //val favorite: Int?,\n        var follow: Int? = 0,\n        //val collect: Int?,\n        //var followAuthor: Int?,\n        //val authorFollowYou: Int?\n    ) : Parcelable\n\n    data class ReplyRows(\n        val id: String?,\n        val uid: String?,\n        val feedUid: String?,\n        val username: String?,\n        var message: String?,\n        val ruid: String?,\n        val rusername: String?,\n        val picArr: List<String>?,\n        val pic: String?,\n        val userInfo: UserInfo?,\n    )\n\n    @Parcelize\n    data class Entities(\n        val uid: String?,\n        val userAvatar: String?,\n        @SerializedName(\"device_title\")\n        val deviceTitle: String?,\n        val dateline: String?,\n        val username: String?,\n        val url: String?,\n        val pic: String?,\n        val title: String?,\n        val message: String?,\n        val logo: String?,\n        val id: String?,\n        val entityType: String?,\n        @SerializedName(\"alias_title\")\n        val aliasTitle: String?,\n        val userInfo: UserInfo?,\n    ) : Parcelable\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/HomeMenu.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity\ndata class HomeMenu(\n    var position: Int,\n    @PrimaryKey\n    var title: String,\n    var isEnable: Boolean\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/LikeAdapter.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.google.gson.Gson\nimport com.google.gson.TypeAdapter\nimport com.google.gson.TypeAdapterFactory\nimport com.google.gson.reflect.TypeToken\nimport com.google.gson.stream.JsonReader\nimport com.google.gson.stream.JsonToken\nimport com.google.gson.stream.JsonWriter\nimport java.io.IOException\n\nobject LikeAdapterFactory : TypeAdapterFactory {\n    override fun <T> create(gson: Gson, type: TypeToken<T>): TypeAdapter<T> {\n        return LikeAdapter(gson) as TypeAdapter<T>\n    }\n}\n\nclass LikeAdapter(private val gson: Gson) : TypeAdapter<LikeResponse.Data?>() {\n    @Throws(IOException::class)\n    override fun write(jsonWriter: JsonWriter, feed: LikeResponse.Data?) {\n        throw RuntimeException(\"Not implemented\")\n    }\n\n    @Throws(IOException::class)\n    override fun read(jsonReader: JsonReader): LikeResponse.Data? {\n        return when (jsonReader.peek()) {\n            JsonToken.NUMBER -> LikeResponse.Data(count = jsonReader.nextInt().toString())\n\n            JsonToken.STRING -> LikeResponse.Data(count = jsonReader.nextString())\n\n            JsonToken.BEGIN_OBJECT ->\n                gson.fromJson(jsonReader, LikeResponse.Data::class.java)\n\n            else -> throw RuntimeException(\"Expected object or int, not \" + jsonReader.peek())\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/LikeResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.google.gson.annotations.JsonAdapter\n\ndata class LikeResponse(\n    @JsonAdapter(LikeAdapterFactory::class)\n    val data: Data?,\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val messageStatus: String?,\n) {\n    data class Data(\n        val count: String?,\n        val follow: Int? = 0,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/LoadUrlResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\ndata class LoadUrlResponse(\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val data: Data?\n) {\n    data class Data(\n        val title: String,\n        val logo: String?,\n        val url: String,\n        val description: String,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/LoginResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\ndata class LoginResponse(\n    val status: Int?,\n    val messageStatus: Int?,\n    val message: String?,\n    val uid: String?,\n    val username: String?,\n    val token: String?,\n    val refreshToken: String?\n)\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/MessageListResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport com.google.gson.annotations.SerializedName\n\ndata class MessageListResponse(\n    val status: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: List<Data>?\n) {\n    data class Data(\n        val title: String?,\n        val id: String?,\n        val ukey: String?,\n        val uid: String?,\n        val username: String?,\n        val fromuid: String?,\n        val fromusername: String?,\n        val islast: Int?,\n        val isnew: Int?,\n        val message: String?,\n        @SerializedName(\"message_pic\")\n        val messagePic: String?,\n        val dateline: Long?,\n        val entityType: String?,\n        val entityId: String?,\n        val messageUid: String?,\n        val messageUsername: String?,\n        val userAvatar: String?,\n        val fromUserAvatar: String?,\n        val messageUserAvatar: String?,\n        var unreadNum: Int?,\n        @SerializedName(\"is_top\")\n        val isTop: Int?,\n        val entityTemplate: String?,\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/OSSUploadPrepareModel.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\n\ndata class OSSUploadPrepareModel(\n    val name: String,\n    val resolution: String,\n    val md5: String,\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/OSSUploadPrepareResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\ndata class OSSUploadPrepareResponse(\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: Data?\n) {\n    data class Data(\n        val fileInfo: List<FileInfo>,\n        val uploadPrepareInfo: UploadPrepareInfo\n    )\n\n    data class FileInfo(\n        val name: String,\n        val resolution: String,\n        val md5: String,\n        val url: String,\n        val uploadFileName: String\n    )\n\n    data class UploadPrepareInfo(\n        val accessKeySecret: String,\n        val accessKeyId: String,\n        val securityToken: String,\n        val expiration: String,\n        val uploadImagePrefix: String,\n        val endPoint: String,\n        val bucket: String,\n        val callbackUrl: String,\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/PostReplyResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nclass PostReplyResponse(\n    val status: Int?,\n    val message: String?,\n    val messageStatus: String?,\n    val data: TotalReplyResponse.Data?\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/RecentAtUser.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity\ndata class RecentAtUser(\n    var id: Long = System.currentTimeMillis(),\n    val group: String = \"recent\",\n    val avatar: String,\n    @PrimaryKey(autoGenerate = false)\n    val username: String,\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/StringEntity.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity\ndata class StringEntity(\n    @PrimaryKey(autoGenerate = false)\n    var data: String,\n    var id: Long = System.currentTimeMillis()\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/TopicBean.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\n\n\ndata class TopicBean(\n    val url: String,\n    val title: String\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/TotalReplyResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\n\nimport com.google.gson.annotations.SerializedName\n\ndata class TotalReplyResponse(\n    val status: Int?,\n    val error: Int?,\n    val message: String?,\n    val data: List<Data>?\n) {\n\n    data class Data(\n        var lastupdate: Long?,\n        @SerializedName(\"extra_key\") val extraKey: String?,\n        val entityType: String?,\n        val id: String,\n        val ruid: String?,\n        val uid: String,\n        val feedUid: String?,\n        var username: String,\n        val rusername: String?,\n        var message: String,\n        val pic: String?,\n        val picArr: List<String>?,\n        val dateline: Long,\n        var likenum: String,\n        val replynum: String,\n        val userAvatar: String,\n        var replyRows: MutableList<Data>?,\n        val replyRowsMore: Int?,\n        val userAction: UserAction?,\n        val userInfo: UserInfo\n    )\n\n    data class UserAction(var like: Int)\n\n    data class UserInfo(val username: String)\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/UpdateCheckItem.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\nimport android.os.Parcelable\nimport kotlinx.parcelize.Parcelize\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@Parcelize\ndata class UpdateCheckItem(\n    val key: String,\n    val value: String,\n) : Parcelable"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/model/UpdateCheckResponse.kt",
    "content": "package com.example.c001apk.compose.logic.model\n\n\n\ndata class UpdateCheckResponse(val data: List<Data>) {\n    data class Data(\n        val id: Int,\n        val title: String,\n        val shorttitle: String,\n        val logo: String,\n        val apkversionname: String,\n        val apkversioncode: Long,\n        val apksize: String,\n        val lastupdate: Long,\n        val packageName: String,\n        val changelog: String,\n        val pkg_bit_type: Int,\n        var localVersionName: String?,\n        var localVersionCode: Long?,\n        var expand: Boolean = false,\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/network/ApiService.kt",
    "content": "package com.example.c001apk.compose.logic.network\n\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.CheckCountResponse\nimport com.example.c001apk.compose.logic.model.CheckResponse\nimport com.example.c001apk.compose.logic.model.CreateFeedResponse\nimport com.example.c001apk.compose.logic.model.FeedContentResponse\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.model.LikeResponse\nimport com.example.c001apk.compose.logic.model.LoadUrlResponse\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareResponse\nimport com.example.c001apk.compose.logic.model.TotalReplyResponse\nimport okhttp3.MultipartBody\nimport okhttp3.ResponseBody\nimport retrofit2.Call\nimport retrofit2.http.FieldMap\nimport retrofit2.http.FormUrlEncoded\nimport retrofit2.http.GET\nimport retrofit2.http.Multipart\nimport retrofit2.http.POST\nimport retrofit2.http.Part\nimport retrofit2.http.Query\nimport retrofit2.http.Url\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\ninterface ApiService {\n\n    @GET(\"/v6/main/indexV8\")\n    fun getHomeFeed(\n        @Query(\"page\") page: Int,\n        @Query(\"firstLaunch\") firstLaunch: Int,\n        @Query(\"installTime\") installTime: String,\n        @Query(\"firstItem\") firstItem: String?,\n        @Query(\"lastItem\") lastItem: String?,\n        @Query(\"ids\") ids: String = EMPTY_STRING,\n    ): Call<HomeFeedResponse>\n\n    @GET\n    fun getFeedContent(\n        @Url url: String,\n    ): Call<FeedContentResponse>\n\n    @GET(\"/v6/feed/replyList\")\n    fun getFeedContentReply(\n        @Query(\"id\") id: String,\n        @Query(\"listType\") listType: String,\n        @Query(\"page\") page: Int,\n        @Query(\"firstItem\") firstItem: String?,\n        @Query(\"lastItem\") lastItem: String?,\n        @Query(\"discussMode\") discussMode: Int,\n        @Query(\"feedType\") feedType: String,\n        @Query(\"blockStatus\") blockStatus: Int,\n        @Query(\"fromFeedAuthor\") fromFeedAuthor: Int\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/search\")\n    fun getSearch(\n        @Query(\"type\") type: String,\n        @Query(\"feedType\") feedType: String,\n        @Query(\"sort\") sort: String,\n        @Query(\"searchValue\") keyWord: String,\n        @Query(\"pageType\") pageType: String?,\n        @Query(\"pageParam\") pageParam: String?,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?,\n        @Query(\"showAnonymous\") showAnonymous: Int = -1\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/feed/replyList?listType=&discussMode=0&feedType=feed_reply&blockStatus=0&fromFeedAuthor=0\")\n    fun getReply2Reply(\n        @Query(\"id\") id: String,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/user/space\")\n    fun getUserSpace(\n        @Query(\"uid\") uid: String,\n    ): Call<FeedContentResponse>\n\n    @GET(\"/v6/user/feedList?showAnonymous=0&isIncludeTop=1&showDoing=0\")\n    fun getUserFeed(\n        @Query(\"uid\") uid: String,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/apk/detail\")\n    fun getAppInfo(\n        @Query(\"id\") id: String,\n        @Query(\"installed\") installed: Int = 1,\n    ): Call<FeedContentResponse>\n\n    @POST(\"/v6/apk/download?extra=\")\n    fun getAppDownloadLink(\n        @Query(\"pn\") id: String,\n        @Query(\"aid\") aid: String,\n        @Query(\"vc\") vc: String,\n    ): Call<Any>\n\n    @Multipart\n    @POST(\"/v6/apk/checkUpdate?coolmarket_beta=0\")\n    fun getAppsUpdate(\n        @Part pkgs: MultipartBody.Part\n    ): Call<HomeFeedResponse>\n\n    @GET //(\"/v6/topic/newTagDetail\")\n    fun getTopicLayout(\n        @Url url: String,\n        @Query(\"tag\") tag: String?, // topic\n        @Query(\"id\") id: String? // product\n    ): Call<FeedContentResponse>\n\n    @GET(\"/v6/product/detail\")\n    fun getProductLayout(\n        @Query(\"id\") id: String\n    ): Call<FeedContentResponse>\n\n    @GET(\"/v6/user/profile\")\n    fun getProfile(\n        @Query(\"uid\") uid: String\n    ): Call<FeedContentResponse>\n\n    @GET\n    fun getFollowList(\n        @Url url: String,\n        @Query(\"uid\") uid: String?,\n        @Query(\"id\") id: String?,\n        @Query(\"showDefault\") showDefault: Int?,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n    @POST\n    fun postLike(\n        @Url url: String,\n        @Query(\"id\") id: String\n    ): Call<LikeResponse>\n\n    @GET(\"/v6/account/checkLoginInfo\")\n    fun checkLoginInfo(\n    ): Call<CheckResponse>\n\n    @GET\n    fun getLoginParam(\n        @Url url: String\n    ): Call<ResponseBody>\n\n    @POST(\"/auth/loginByCoolApk\")\n    @FormUrlEncoded\n    fun tryLogin(@FieldMap data: HashMap<String, String?>): Call<ResponseBody>\n\n    @GET\n    fun getCaptcha(@Url url: String): Call<ResponseBody>\n\n    @GET\n    fun getValidateCaptcha(@Url url: String): Call<ResponseBody>\n\n    @POST(\"v6/feed/reply\")\n    @FormUrlEncoded\n    fun postReply(\n        @FieldMap data: HashMap<String, String>,\n        @Query(\"id\") id: String,\n        @Query(\"type\") type: String\n    ): Call<FeedContentResponse>\n\n    @GET(\"/v6/page/dataList\")\n    fun getDataList(\n        @Query(\"url\") url: String,\n        @Query(\"title\") title: String,\n        @Query(\"subTitle\") subTitle: String?,\n        @Query(\"lastItem\") lastItem: String?,\n        @Query(\"page\") page: Int\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/dyhArticle/list\")\n    fun getDyhDetail(\n        @Query(\"dyhId\") dyhId: String,\n        @Query(\"type\") type: String,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/picture/list\")\n    fun getCoolPic(\n        @Query(\"tag\") tag: String,\n        @Query(\"type\") type: String,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n\n    @GET(\"/auth/login\")\n    fun getSmsLoginParam(\n        @Query(\"type\") type: String = \"mobile\",\n    ): Call<ResponseBody>\n\n    @POST(\"/auth/login\")\n    @FormUrlEncoded\n    fun getSmsToken(\n        @Query(\"type\") type: String = \"mobile\",\n        @FieldMap data: HashMap<String, String?>\n    ): Call<ResponseBody>\n\n    @GET\n    fun getMessage(\n        @Url url: String,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n    @POST\n    fun postFollowUnFollow(\n        @Url url: String,\n        @Query(\"uid\") uid: String\n    ): Call<LikeResponse>\n\n    @POST(\"/v6/feed/createFeed\")\n    @FormUrlEncoded\n    fun postCreateFeed(\n        @FieldMap data: HashMap<String, String>\n    ): Call<CreateFeedResponse>\n\n    @POST(\"/v6/account/requestValidate\")\n    @FormUrlEncoded\n    fun postRequestValidate(\n        @FieldMap data: HashMap<String, String?>\n    ): Call<LikeResponse>\n\n    @GET(\"/v6/vote/commentList\")\n    fun getVoteComment(\n        @Query(\"fid\") fid: String,\n        @Query(\"extra_key\") extraKey: String,\n        @Query(\"page\") page: Int,\n        @Query(\"firstItem\") firstItem: String?,\n        @Query(\"lastItem\") lastItem: String?,\n    ): Call<TotalReplyResponse>\n\n    @GET(\"/v6/question/answerList\")\n    fun getAnswerList(\n        @Query(\"id\") fid: String,\n        @Query(\"sort\") sort: String,\n        @Query(\"page\") page: Int,\n        @Query(\"firstItem\") firstItem: String?,\n        @Query(\"lastItem\") lastItem: String?,\n    ): Call<TotalReplyResponse>\n\n    @GET\n    fun getProductList(\n        @Url url: String\n    ): Call<HomeFeedResponse>\n\n    @GET\n    fun getCollectionList(\n        @Url url: String,\n        @Query(\"uid\") uid: String?,\n        @Query(\"id\") id: String?,\n        @Query(\"showDefault\") showDefault: Int,\n        @Query(\"page\") page: Int,\n        @Query(\"lastItem\") lastItem: String?\n    ): Call<HomeFeedResponse>\n\n    @POST\n    fun postDelete(\n        @Url url: String,\n        @Query(\"id\") id: String,\n    ): Call<LikeResponse>\n\n    @POST(\"/v6/product/changeFollowStatus\")\n    @FormUrlEncoded\n    fun postFollow(\n        @FieldMap data: HashMap<String, String>\n    ): Call<LikeResponse>\n\n    @GET\n    fun getFollow(\n        @Url url: String,\n        @Query(\"tag\") tag: String?,\n        @Query(\"id\") id: String?,\n    ): Call<LikeResponse>\n\n    @POST(\"/v6/upload/ossUploadPrepare\")\n    @FormUrlEncoded\n    fun postOSSUploadPrepare(\n        @FieldMap data: HashMap<String, String>\n    ): Call<OSSUploadPrepareResponse>\n\n    @GET(\"/v6/feed/searchTag\")\n    fun getSearchTag(\n        @Query(\"q\") query: String,\n        @Query(\"page\") page: Int,\n        @Query(\"recentIds\") recentIds: String?,\n        @Query(\"firstItem\") firstItem: String?,\n        @Query(\"lastItem\") lastItem: String?,\n    ): Call<HomeFeedResponse>\n\n    @GET(\"/v6/feed/loadShareUrl\")\n    fun loadShareUrl(\n        @Query(\"url\") url: String,\n        @Query(\"packageName\") packageName: String = EMPTY_STRING,\n    ): Call<LoadUrlResponse>\n\n    @GET\n    fun messageOperation(\n        @Url url: String,\n        @Query(\"ukey\") ukey: String?,\n        @Query(\"uid\") uid: String?,\n        @Query(\"page\") page: Int?,\n        @Query(\"firstItem\") firstItem: String?,\n        @Query(\"lastItem\") lastItem: String?,\n    ): Call<HomeFeedResponse>\n\n    @Multipart\n    @POST(\"/v6/message/send\")\n    fun sendMessage(\n        @Query(\"uid\") uid: String,\n        @Part message: MultipartBody.Part,\n        @Part pic: MultipartBody.Part\n    ): Call<HomeFeedResponse>\n\n    @GET\n    fun deleteMessage(\n        @Url url: String,\n        @Query(\"ukey\") ukey: String?,\n        @Query(\"id\") uid: String?,\n    ): Call<LikeResponse>\n\n    @GET(\"/v6/message/showImage\")\n    fun getImageUrl(\n        @Query(\"id\") id: String,\n        @Query(\"type\") type: String = \"s\",\n    ): Call<Any>\n\n    @GET(\"/v6/notification/checkCount\")\n    fun checkCount(): Call<CheckCountResponse>\n\n    @POST\n    @FormUrlEncoded\n    fun postDelete(\n        @Url url: String,\n        @FieldMap data: Map<String, String>?\n    ): Call<LikeResponse>\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/providable/LocalUserPreferences.kt",
    "content": "package com.example.c001apk.compose.logic.providable\n\nimport androidx.compose.runtime.staticCompositionLocalOf\nimport com.example.c001apk.compose.logic.datastore.UserPreferencesCompat\n\nval LocalUserPreferences = staticCompositionLocalOf { UserPreferencesCompat.default() }\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/BlackListRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport androidx.lifecycle.LiveData\nimport com.example.c001apk.compose.di.TopicBlackList\nimport com.example.c001apk.compose.di.UserBlackList\nimport com.example.c001apk.compose.logic.dao.StringEntityDao\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.withContext\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass BlackListRepo @Inject constructor(\n    @UserBlackList\n    private val userBlackListDao: StringEntityDao,\n    @TopicBlackList\n    private val topicBlackListDao: StringEntityDao,\n) {\n\n    fun loadAllUserListLive(): LiveData<List<StringEntity>> {\n        return userBlackListDao.loadAllListLive()\n    }\n\n    fun loadAllUserListFlow(): Flow<List<StringEntity>> {\n        return userBlackListDao.loadAllListFlow()\n    }\n\n    suspend fun insertUid(uid: String) {\n        userBlackListDao.insert(StringEntity(uid))\n    }\n\n    suspend fun insertUidList(list: List<StringEntity>) {\n        userBlackListDao.insertList(list)\n    }\n\n    suspend fun checkUid(uid: String): Boolean {\n        return userBlackListDao.isExist(uid)\n    }\n\n    suspend fun saveUid(uid: String) {\n        if (!userBlackListDao.isExist(uid)) {\n            userBlackListDao.insert(StringEntity(uid))\n        }\n    }\n\n    suspend fun deleteUid(uid: String) {\n        userBlackListDao.delete(uid)\n    }\n\n    suspend fun deleteAllUser() {\n        userBlackListDao.deleteAll()\n    }\n\n    fun loadAllTopicListLive(): LiveData<List<StringEntity>> {\n        return topicBlackListDao.loadAllListLive()\n    }\n\n    fun loadAllTopicListFlow(): Flow<List<StringEntity>> {\n        return topicBlackListDao.loadAllListFlow()\n    }\n\n    suspend fun insertTopic(topic: String) {\n        topicBlackListDao.insert(StringEntity(topic))\n    }\n\n    suspend fun insertTopicList(list: List<StringEntity>) {\n        topicBlackListDao.insertList(list)\n    }\n\n    suspend fun checkTopic(topic: String): Boolean {\n        return withContext(Dispatchers.IO) {\n            topicBlackListDao.isContain(topic)\n        }\n    }\n\n    suspend fun saveTopic(topic: String) {\n        if (!topicBlackListDao.isExist(topic)) {\n            topicBlackListDao.insert(StringEntity(topic))\n        }\n    }\n\n    suspend fun deleteTopic(topic: String) {\n        topicBlackListDao.delete(topic)\n    }\n\n    suspend fun deleteAllTopic() {\n        topicBlackListDao.deleteAll()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/HistoryFavoriteRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport androidx.lifecycle.LiveData\nimport com.example.c001apk.compose.di.BrowseHistory\nimport com.example.c001apk.compose.di.FeedFavorite\nimport com.example.c001apk.compose.logic.dao.HistoryFavoriteDao\nimport com.example.c001apk.compose.logic.model.FeedEntity\nimport kotlinx.coroutines.flow.Flow\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass HistoryFavoriteRepo @Inject constructor(\n    @BrowseHistory\n    private val browseHistoryDao: HistoryFavoriteDao,\n    @FeedFavorite\n    private val feedFavoriteDao: HistoryFavoriteDao,\n) {\n\n    fun loadAllHistoryListLive(): LiveData<List<FeedEntity>> {\n        return browseHistoryDao.loadAllListLive()\n    }\n\n    fun loadAllHistoryListFlow(): Flow<List<FeedEntity>> {\n        return browseHistoryDao.loadAllListFlow()\n    }\n\n    suspend fun insertHistory(history: FeedEntity) {\n        browseHistoryDao.insert(history)\n    }\n\n    suspend fun checkHistory(id: String): Boolean {\n        return browseHistoryDao.isExist(id)\n    }\n\n    suspend fun saveHistory(\n        id: String,\n        uid: String,\n        uname: String,\n        avatar: String,\n        device: String,\n        message: String,\n        pubDate: String\n    ) {\n        if (!browseHistoryDao.isExist(id))\n            browseHistoryDao.insert(\n                FeedEntity(\n                    id,\n                    uid,\n                    uname,\n                    avatar,\n                    device,\n                    message,\n                    pubDate\n                )\n            )\n    }\n\n    suspend fun deleteHistory(id: String) {\n        browseHistoryDao.delete(id)\n    }\n\n    suspend fun deleteAllHistory() {\n        browseHistoryDao.deleteAll()\n    }\n\n    fun loadAllFavoriteListLive(): LiveData<List<FeedEntity>> {\n        return feedFavoriteDao.loadAllListLive()\n    }\n\n    fun loadAllFavoriteListFlow(): Flow<List<FeedEntity>> {\n        return feedFavoriteDao.loadAllListFlow()\n    }\n\n    suspend fun insertFavorite(favorite: FeedEntity) {\n        feedFavoriteDao.insert(favorite)\n    }\n\n    suspend fun checkFavorite(id: String): Boolean {\n        return feedFavoriteDao.isExist(id)\n    }\n\n    suspend fun saveFavorite(\n        id: String,\n        uid: String,\n        uname: String,\n        avatar: String,\n        device: String,\n        message: String,\n        pubDate: String\n    ) {\n        if (!feedFavoriteDao.isExist(id))\n            feedFavoriteDao.insert(\n                FeedEntity(\n                    id,\n                    uid,\n                    uname,\n                    avatar,\n                    device,\n                    message,\n                    pubDate\n                )\n            )\n    }\n\n    suspend fun deleteFavorite(id: String) {\n        feedFavoriteDao.delete(id)\n    }\n\n    suspend fun deleteAllFavorite() {\n        feedFavoriteDao.deleteAll()\n    }\n\n    suspend fun deleteHistoryByUid(uid: String){\n        browseHistoryDao.deleteByUid(uid)\n    }\n    \n    suspend fun deleteFavByUid(uid: String){\n        feedFavoriteDao.deleteByUid(uid)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/HomeMenuRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport androidx.lifecycle.LiveData\nimport com.example.c001apk.compose.logic.dao.HomeMenuDao\nimport com.example.c001apk.compose.logic.model.HomeMenu\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass HomeMenuRepo @Inject constructor(\n    private val homeMenuDao: HomeMenuDao,\n) {\n\n    fun loadAllListLive(): LiveData<List<HomeMenu>> {\n        return homeMenuDao.loadAllListLive()\n    }\n\n    suspend fun insert(homeMenu: HomeMenu) {\n        homeMenuDao.insert(homeMenu)\n    }\n\n    suspend fun insertList(list: List<HomeMenu>) {\n        homeMenuDao.insertList(list)\n    }\n\n    suspend fun updateList(list: List<HomeMenu>) {\n        homeMenuDao.updateList(list)\n    }\n\n    suspend fun deleteAll() {\n        homeMenuDao.deleteAll()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/NetworkRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport com.example.c001apk.compose.constant.Constants.LOADING_FAILED\nimport com.example.c001apk.compose.di.AccountService\nimport com.example.c001apk.compose.di.Api1Service\nimport com.example.c001apk.compose.di.Api1ServiceNoRedirect\nimport com.example.c001apk.compose.di.Api2Service\nimport com.example.c001apk.compose.logic.model.FeedContentResponse\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.network.ApiService\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport okhttp3.MultipartBody\nimport retrofit2.Call\nimport retrofit2.Callback\nimport retrofit2.Response\nimport javax.inject.Inject\nimport javax.inject.Singleton\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\nimport kotlin.coroutines.suspendCoroutine\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\n@Singleton\nclass NetworkRepo @Inject constructor(\n    @Api1Service\n    private val apiService: ApiService,\n    @Api1ServiceNoRedirect\n    private val apiServiceNoRedirect: ApiService,\n    @Api2Service\n    private val api2Service: ApiService,\n    @AccountService\n    private val accountService: ApiService,\n) {\n\n    suspend fun getHomeFeed(\n        page: Int,\n        firstLaunch: Int,\n        installTime: String,\n        firstItem: String?,\n        lastItem: String?\n    ) = flowList {\n        api2Service.getHomeFeed(page, firstLaunch, installTime, firstItem, lastItem).await()\n    }\n\n    suspend fun getFeedContent(url: String) = flowData {\n        apiService.getFeedContent(url).await()\n    }\n\n    suspend fun getFeedContentReply(\n        id: String,\n        listType: String,\n        page: Int,\n        firstItem: String?,\n        lastItem: String?,\n        discussMode: Int,\n        feedType: String,\n        blockStatus: Int,\n        fromFeedAuthor: Int\n    ) = flowList {\n        api2Service.getFeedContentReply(\n            id,\n            listType,\n            page,\n            firstItem,\n            lastItem,\n            discussMode,\n            feedType,\n            blockStatus,\n            fromFeedAuthor\n        ).await()\n    }\n\n    suspend fun getSearch(\n        type: String, feedType: String, sort: String, keyWord: String, pageType: String?,\n        pageParam: String?, page: Int, lastItem: String?\n    ) = flowList {\n        apiService.getSearch(\n            type, feedType, sort, keyWord, pageType, pageParam, page, lastItem\n        ).await()\n    }\n\n    suspend fun getReply2Reply(id: String, page: Int, lastItem: String?) = flowList {\n        apiService.getReply2Reply(id, page, lastItem).await()\n    }\n\n    suspend fun getTopicLayout(url: String, tag: String?, id: String?) = flowData {\n        api2Service.getTopicLayout(url, tag, id).await()\n    }\n\n    suspend fun getProductLayout(id: String) = fire {\n        Result.success(apiService.getProductLayout(id).await())\n    }\n\n    suspend fun getUserSpace(uid: String) = flowData {\n        apiService.getUserSpace(uid).await()\n    }\n\n    suspend fun getUserFeed(uid: String, page: Int, lastItem: String?) = flowList {\n        apiService.getUserFeed(uid, page, lastItem).await()\n    }\n\n    suspend fun getAppInfo(id: String) = flowData {\n        apiService.getAppInfo(id).await()\n    }\n\n    suspend fun getAppDownloadLink(pn: String, aid: String, vc: String) = fire {\n        val appResponse = apiServiceNoRedirect.getAppDownloadLink(pn, aid, vc).response()\n        Result.success(appResponse.headers()[\"Location\"])\n    }\n\n    suspend fun getAppsUpdate(pkgs: String) = flowList {\n        val multipartBody = MultipartBody.Part.createFormData(\"pkgs\", pkgs)\n        apiService.getAppsUpdate(multipartBody).await()\n    }\n\n    suspend fun getProfile(uid: String) = fire {\n        Result.success(api2Service.getProfile(uid).await())\n    }\n\n    suspend fun getFollowList(\n        url: String,\n        uid: String?,\n        id: String?,\n        showDefault: Int?,\n        page: Int,\n        lastItem: String?\n    ) = flowList {\n        apiService.getFollowList(url, uid, id, showDefault, page, lastItem).await()\n    }\n\n    suspend fun postLike(url: String, id: String) = fire {\n        Result.success(apiService.postLike(url, id).await())\n    }\n\n    suspend fun checkLoginInfo() = fire {\n        Result.success(apiService.checkLoginInfo().response())\n    }\n\n    suspend fun getLoginParam(url: String) = fire {\n        Result.success(accountService.getLoginParam(url).response())\n    }\n\n    suspend fun tryLogin(data: HashMap<String, String?>) = fire {\n        Result.success(accountService.tryLogin(data).response())\n    }\n\n    suspend fun getCaptcha(url: String) = fire {\n        Result.success(accountService.getCaptcha(url).response())\n    }\n\n    suspend fun getValidateCaptcha(url: String) = fire {\n        Result.success(apiService.getValidateCaptcha(url).response())\n    }\n\n    suspend fun postReply(data: HashMap<String, String>, id: String, type: String) = fire {\n        Result.success(apiService.postReply(data, id, type).await())\n    }\n\n    suspend fun getDataList(\n        url: String, title: String, subTitle: String?, lastItem: String?, page: Int\n    ) = flowList {\n        api2Service.getDataList(url, title, subTitle, lastItem, page).await()\n    }\n\n    suspend fun getDyhDetail(dyhId: String, type: String, page: Int, lastItem: String?) =\n        flowList {\n            apiService.getDyhDetail(dyhId, type, page, lastItem).await()\n        }\n\n    suspend fun getCoolPic(tag: String, type: String, page: Int, lastItem: String?) =\n        flowList {\n            apiService.getCoolPic(tag, type, page, lastItem).await()\n        }\n\n    suspend fun getMessage(url: String, page: Int, lastItem: String?) = flowList {\n        apiService.getMessage(url, page, lastItem).await()\n    }\n\n    suspend fun postFollowUnFollow(url: String, uid: String) = fire {\n        Result.success(apiService.postFollowUnFollow(url, uid).await())\n    }\n\n    suspend fun postCreateFeed(data: HashMap<String, String>) = fire {\n        Result.success(apiService.postCreateFeed(data).await())\n    }\n\n    suspend fun postRequestValidate(data: HashMap<String, String?>) = fire {\n        Result.success(apiService.postRequestValidate(data).await())\n    }\n\n    suspend fun getVoteComment(\n        fid: String,\n        extraKey: String,\n        page: Int,\n        firstItem: String?,\n        lastItem: String?,\n    ) = fire {\n        Result.success(apiService.getVoteComment(fid, extraKey, page, firstItem, lastItem).await())\n    }\n\n    suspend fun getAnswerList(\n        id: String,\n        sort: String,\n        page: Int,\n        firstItem: String?,\n        lastItem: String?,\n    ) = fire {\n        Result.success(apiService.getAnswerList(id, sort, page, firstItem, lastItem).await())\n    }\n\n    suspend fun getProductList(url: String) = flowList {\n        apiService.getProductList(url).await()\n    }\n\n    suspend fun getCollectionList(\n        url: String,\n        uid: String?,\n        id: String?,\n        showDefault: Int,\n        page: Int,\n        lastItem: String?\n    ) = fire {\n        Result.success(\n            apiService.getCollectionList(url, uid, id, showDefault, page, lastItem).await()\n        )\n    }\n\n    suspend fun postDelete(url: String, id: String) = fire {\n        Result.success(apiService.postDelete(url, id).await())\n    }\n\n    suspend fun postFollow(data: HashMap<String, String>) = fire {\n        Result.success(apiService.postFollow(data).await())\n    }\n\n    suspend fun getFollow(url: String, tag: String?, id: String?) = fire {\n        Result.success(apiService.getFollow(url, tag, id).await())\n    }\n\n    suspend fun postOSSUploadPrepare(data: HashMap<String, String>) = fire {\n        Result.success(apiService.postOSSUploadPrepare(data).await())\n    }\n\n    suspend fun getSearchTag(\n        query: String,\n        page: Int,\n        recentIds: String?,\n        firstItem: String?,\n        lastItem: String?,\n    ) = fire {\n        Result.success(apiService.getSearchTag(query, page, recentIds, firstItem, lastItem).await())\n    }\n\n    suspend fun loadShareUrl(url: String) = fire {\n        Result.success(apiService.loadShareUrl(url).await())\n    }\n\n    suspend fun messageOperation(\n        url: String,\n        ukey: String?,\n        uid: String?,\n        page: Int?,\n        firstItem: String?,\n        lastItem: String?,\n    ) = flowList {\n        apiService.messageOperation(url, ukey, uid, page, firstItem, lastItem).await()\n    }\n\n    suspend fun sendMessage(uid: String, message: MultipartBody.Part, pic: MultipartBody.Part) =\n        fire {\n            Result.success(apiService.sendMessage(uid, message, pic).await())\n        }\n\n    suspend fun deleteMessage(url: String, ukey: String, id: String?) = fire {\n        Result.success(apiService.deleteMessage(url, ukey, id).await())\n    }\n\n    suspend fun getImageUrl(id: String) = fire {\n        val response = apiServiceNoRedirect.getImageUrl(id).response()\n        Result.success(response.headers()[\"Location\"])\n    }\n\n    suspend fun checkCount() = fire {\n        Result.success(apiService.checkCount().await())\n    }\n\n\n    suspend fun postDelete(url: String, data: Map<String, String>?) = fire {\n        Result.success(apiService.postDelete(url, data).await())\n    }\n\n    private suspend fun <T> Call<T>.await(): T {\n        return suspendCoroutine { continuation ->\n            enqueue(object : Callback<T> {\n                override fun onResponse(call: Call<T>, response: Response<T>) {\n                    val body = response.body()\n                    if (body != null) continuation.resume(body)\n                    else continuation.resumeWithException(\n                        RuntimeException(\"response body is null\")\n                    )\n                }\n\n                override fun onFailure(call: Call<T>, t: Throwable) {\n                    continuation.resumeWithException(t)\n                }\n            })\n        }\n    }\n\n    private suspend fun <T> Call<T>.response(): Response<T> {\n        return suspendCoroutine { continuation ->\n            enqueue(object : Callback<T> {\n                override fun onResponse(call: Call<T>, response: Response<T>) {\n                    continuation.resume(response)\n                }\n\n                override fun onFailure(call: Call<T>, t: Throwable) {\n                    continuation.resumeWithException(t)\n                }\n            })\n        }\n    }\n\n    private fun <T> fire(block: suspend () -> Result<T>) = flow {\n        val result = try {\n            block()\n        } catch (e: Exception) {\n            Result.failure(e)\n        }\n        emit(result)\n    }.flowOn(Dispatchers.IO)\n\n    private fun flowList(block: suspend () -> HomeFeedResponse) = flow {\n        val result = try {\n            val response = block()\n            if (!response.message.isNullOrEmpty()) {\n                LoadingState.Error(response.message)\n            } else if (!response.data.isNullOrEmpty()) {\n                LoadingState.Success(response.data)\n            } else if (response.data?.isEmpty() == true) {\n                LoadingState.Empty\n            } else {\n                LoadingState.Error(LOADING_FAILED)\n            }\n        } catch (e: Exception) {\n            LoadingState.Error(e.message ?: \"unknown error\")\n        }\n        emit(result)\n    }.flowOn(Dispatchers.IO)\n\n    private fun flowData(block: suspend () -> FeedContentResponse) = flow {\n        val result = try {\n            val response = block()\n            if (!response.message.isNullOrEmpty()) {\n                LoadingState.Error(response.message)\n            } else if (response.data != null) {\n                LoadingState.Success(response.data)\n            } else {\n                LoadingState.Error(LOADING_FAILED)\n            }\n        } catch (e: Exception) {\n            LoadingState.Error(e.message ?: \"unknown error\")\n        }\n        emit(result)\n    }.flowOn(Dispatchers.IO)\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/RecentAtUserRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport androidx.lifecycle.LiveData\nimport com.example.c001apk.compose.logic.dao.RecentAtUserDao\nimport com.example.c001apk.compose.logic.model.RecentAtUser\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass RecentAtUserRepo @Inject constructor(\n    private val recentAtUserDao: RecentAtUserDao,\n) {\n\n    fun loadAllListLive(): LiveData<List<RecentAtUser>> {\n        return recentAtUserDao.loadAllListLive()\n    }\n\n    suspend fun insertUser(user: RecentAtUser) {\n        recentAtUserDao.insert(user)\n    }\n\n    suspend fun insertList(list: List<RecentAtUser>) {\n        recentAtUserDao.insertList(list)\n    }\n\n    suspend fun deleteUser(user: String) {\n        recentAtUserDao.delete(user)\n    }\n\n    suspend fun deleteUser(user: RecentAtUser) {\n        recentAtUserDao.delete(user)\n    }\n\n    suspend fun deleteAll() {\n        recentAtUserDao.deleteAll()\n    }\n\n    suspend fun checkUser(username: String): Boolean {\n        return recentAtUserDao.isExist(username)\n    }\n\n    suspend fun updateUser(username: String, newId: Long) {\n        recentAtUserDao.updateUser(username, newId)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/RecentEmojiRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport androidx.lifecycle.LiveData\nimport com.example.c001apk.compose.di.RecentEmoji\nimport com.example.c001apk.compose.logic.dao.StringEntityDao\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport kotlinx.coroutines.flow.Flow\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass RecentEmojiRepo @Inject constructor(\n    @RecentEmoji\n    private val recentEmojiDao: StringEntityDao,\n) {\n\n    fun loadAllListLive(): LiveData<List<StringEntity>> {\n        return recentEmojiDao.loadAllListLive()\n    }\n\n    fun loadAllListFlow(): Flow<List<StringEntity>> {\n        return recentEmojiDao.loadAllListFlow()\n    }\n\n    suspend fun insertEmoji(emoji: StringEntity) {\n        recentEmojiDao.insert(emoji)\n    }\n\n    suspend fun insertList(list: List<StringEntity>) {\n        recentEmojiDao.insertList(list)\n    }\n\n    suspend fun saveEmoji(emoji: String) {\n        if (!recentEmojiDao.isExist(emoji)) {\n            recentEmojiDao.insert(StringEntity(emoji))\n        }\n    }\n\n    suspend fun deleteEmoji(emoji: String) {\n        recentEmojiDao.delete(emoji)\n    }\n\n    suspend fun deleteEmoji(emoji: StringEntity) {\n        recentEmojiDao.delete(emoji)\n    }\n\n    suspend fun deleteAll() {\n        recentEmojiDao.deleteAll()\n    }\n\n    suspend fun checkEmoji(emoji: String): Boolean {\n        return recentEmojiDao.isExist(emoji)\n    }\n\n    suspend fun updateEmoji(data: String) {\n        recentEmojiDao.updateHistory(data, System.currentTimeMillis())\n    }\n\n    suspend fun updateEmoji(oldData: String, newData: String) {\n        recentEmojiDao.updateEmoji(oldData, newData, System.currentTimeMillis())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/SearchHistoryRepo.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport androidx.lifecycle.LiveData\nimport com.example.c001apk.compose.di.SearchHistory\nimport com.example.c001apk.compose.logic.dao.StringEntityDao\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport kotlinx.coroutines.flow.Flow\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass SearchHistoryRepo @Inject constructor(\n    @SearchHistory\n    private val searchHistoryDao: StringEntityDao,\n) {\n\n    fun loadAllListFlow(): Flow<List<StringEntity>> {\n        return searchHistoryDao.loadAllListFlow()\n    }\n\n    fun loadAllListLive(): LiveData<List<StringEntity>> {\n        return searchHistoryDao.loadAllListLive()\n    }\n\n    suspend fun insertHistory(history: StringEntity) {\n        searchHistoryDao.insert(history)\n    }\n\n    suspend fun insertList(list: List<StringEntity>) {\n        searchHistoryDao.insertList(list)\n    }\n\n    suspend fun saveHistory(history: String) {\n        searchHistoryDao.insert(StringEntity(history))\n    }\n\n    suspend fun deleteHistory(history: String) {\n        searchHistoryDao.delete(history)\n    }\n\n    suspend fun deleteAllHistory() {\n        searchHistoryDao.deleteAll()\n    }\n\n    suspend fun isExist(history: String): Boolean {\n        return searchHistoryDao.isExist(history)\n    }\n\n    suspend fun updateHistory(data: String) {\n        searchHistoryDao.updateHistory(data, System.currentTimeMillis())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/repository/UserPreferencesRepository.kt",
    "content": "package com.example.c001apk.compose.logic.repository\n\nimport com.example.c001apk.compose.FollowType\nimport com.example.c001apk.compose.ThemeMode\nimport com.example.c001apk.compose.ThemeType\nimport com.example.c001apk.compose.logic.datastore.UserPreferencesDataSource\nimport javax.inject.Inject\nimport javax.inject.Singleton\n\n@Singleton\nclass UserPreferencesRepository @Inject constructor(\n    private val userPreferencesDataSource: UserPreferencesDataSource\n) {\n    val data get() = userPreferencesDataSource.data\n\n    suspend fun setThemeMode(value: ThemeMode) = userPreferencesDataSource.setThemeMode(value)\n\n    suspend fun setMaterialYou(value: Boolean) = userPreferencesDataSource.setMaterialYou(value)\n\n    suspend fun setPureBlack(value: Boolean) = userPreferencesDataSource.setPureBlack(value)\n\n    suspend fun setFontScale(value: Float) = userPreferencesDataSource.setFontScale(value)\n\n    suspend fun setContentScale(value: Float) = userPreferencesDataSource.setContentScale(value)\n\n    suspend fun setSZLMId(value: String) = userPreferencesDataSource.setSZLMId(value)\n\n    suspend fun setImageQuality(value: Int) = userPreferencesDataSource.setImageQuality(value)\n\n    suspend fun setImageFilter(value: Boolean) = userPreferencesDataSource.setImageFilter(value)\n\n    suspend fun setOpenInBrowser(value: Boolean) = userPreferencesDataSource.setOpenInBrowser(value)\n\n    suspend fun setShowSquare(value: Boolean) = userPreferencesDataSource.setShowSquare(value)\n\n    suspend fun setRecordHistory(value: Boolean) = userPreferencesDataSource.setRecordHistory(value)\n\n    suspend fun setShowEmoji(value: Boolean) = userPreferencesDataSource.setShowEmoji(value)\n\n    suspend fun setCheckUpdate(value: Boolean) = userPreferencesDataSource.setCheckUpdate(value)\n\n    suspend fun setCheckCount(value: Boolean) = userPreferencesDataSource.setCheckCount(value)\n\n    suspend fun setVersionName(value: String) = userPreferencesDataSource.setVersionName(value)\n\n    suspend fun setApiVersion(value: String) = userPreferencesDataSource.setApiVersion(value)\n\n    suspend fun setVersionCode(value: String) = userPreferencesDataSource.setVersionCode(value)\n\n    suspend fun setManufacturer(value: String) = userPreferencesDataSource.setManufacturer(value)\n\n    suspend fun setBrand(value: String) = userPreferencesDataSource.setBrand(value)\n\n    suspend fun setModel(value: String) = userPreferencesDataSource.setModel(value)\n\n    suspend fun setBuildNumber(value: String) = userPreferencesDataSource.setBuildNumber(value)\n\n    suspend fun setSdkInt(value: String) = userPreferencesDataSource.setSdkInt(value)\n\n    suspend fun setAndroidVersion(value: String) =\n        userPreferencesDataSource.setAndroidVersion(value)\n\n    suspend fun setUserAgent(value: String) = userPreferencesDataSource.setUserAgent(value)\n\n    suspend fun setXAppDevice(value: String) = userPreferencesDataSource.setXAppDevice(value)\n\n    suspend fun setXAppToken(value: String) = userPreferencesDataSource.setXAppToken(value)\n\n    suspend fun setIsLogin(value: Boolean) = userPreferencesDataSource.setIsLogin(value)\n\n    suspend fun setUserAvatar(value: String) = userPreferencesDataSource.setUserAvatar(value)\n\n    suspend fun setUsername(value: String) = userPreferencesDataSource.setUsername(value)\n\n    suspend fun setLevel(value: String) = userPreferencesDataSource.setLevel(value)\n\n    suspend fun setExperience(value: String) = userPreferencesDataSource.setExperience(value)\n\n    suspend fun setNextLevelExperience(value: String) =\n        userPreferencesDataSource.setNextLevelExperience(value)\n\n    suspend fun setUid(value: String) = userPreferencesDataSource.setUid(value)\n\n    suspend fun setToken(value: String) = userPreferencesDataSource.setToken(value)\n\n    suspend fun setFollowType(value: FollowType) = userPreferencesDataSource.setFollowType(value)\n\n    suspend fun setRecentIds(value: String) = userPreferencesDataSource.setRecentIds(value)\n\n    suspend fun setCheckCountPeriod(value: Int) =\n        userPreferencesDataSource.setCheckCountPeriod(value)\n\n    suspend fun setInstallTime(value: String) = userPreferencesDataSource.setInstallTime(value)\n\n    suspend fun setThemeType(value: ThemeType) = userPreferencesDataSource.setThemeType(value)\n\n    suspend fun setSeedColor(value: String) = userPreferencesDataSource.setSeedColor(value)\n\n    suspend fun setPaletteStyle(value: Int) = userPreferencesDataSource.setPaletteStyle(value)\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/state/FooterState.kt",
    "content": "package com.example.c001apk.compose.logic.state\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nsealed class FooterState : State(){\n    data object Loading : FooterState()\n    data object End : FooterState()\n    data object Success : FooterState()\n    data class Error(val errMsg: String) : FooterState()\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/state/LoadingState.kt",
    "content": "package com.example.c001apk.compose.logic.state\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nsealed class LoadingState<out T> : State() {\n    data object Loading : LoadingState<Nothing>()\n    data object Empty : LoadingState<Nothing>()\n    data class Success<out T>(val response: T) : LoadingState<T>()\n    data class Error(val errMsg: String) : LoadingState<Nothing>()\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/logic/state/State.kt",
    "content": "package com.example.c001apk.compose.logic.state\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nsealed class State\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/app/AppContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.app\n\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.asPaddingValues\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@Composable\nfun AppContentScreen(\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    id: String,\n    appCommentSort: String,\n    appCommentTitle: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    isScrollingUp: ((Boolean) -> Unit)? = null,\n) {\n\n    val viewModel =\n        hiltViewModel<AppContentViewModel, AppContentViewModel.ViewModelFactory>(key = id + appCommentSort) { factory ->\n            factory.create(\n                url = \"/page?url=/feed/apkCommentList?id=$id$appCommentSort\",\n                appCommentTitle = appCommentTitle\n            )\n        }\n\n    val windowInsets =\n        WindowInsets.navigationBars.only(WindowInsetsSides.Start + WindowInsetsSides.Bottom)\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = windowInsets.asPaddingValues(),\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n        isScrollingUp = isScrollingUp,\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/app/AppContentViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.app\n\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@HiltViewModel(assistedFactory = AppContentViewModel.ViewModelFactory::class)\nclass AppContentViewModel @AssistedInject constructor(\n    @Assisted(\"url\") val url: String,\n    @Assisted(\"appCommentTitle\") val appCommentTitle: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"url\") url: String,\n            @Assisted(\"appCommentTitle\") appCommentTitle: String,\n        ): AppContentViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getDataList(\n            url,\n            appCommentTitle,\n            null,\n            lastItem,\n            page\n        )\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/app/AppScreen.kt",
    "content": "package com.example.c001apk.compose.ui.app\n\nimport android.content.Intent\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.layout.windowInsetsPadding\nimport androidx.compose.foundation.layout.wrapContentSize\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.SecondaryTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.graphicsLayer\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.res.ResourcesCompat\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.cards.AppInfoCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.feed.reply.ReplyActivity\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.downloadApk\nimport com.example.c001apk.compose.util.makeToast\nimport com.google.accompanist.drawablepainter.rememberDrawablePainter\nimport kotlinx.coroutines.launch\nimport me.onebone.toolbar.CollapsingToolbarScaffold\nimport me.onebone.toolbar.ExperimentalToolbarApi\nimport me.onebone.toolbar.ScrollStrategy\nimport me.onebone.toolbar.rememberCollapsingToolbarScaffoldState\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalToolbarApi::class)\n@Composable\nfun AppScreen(\n    onBackClick: () -> Unit,\n    packageName: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onSearch: (String, String, String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<AppViewModel, AppViewModel.ViewModelFactory>(key = packageName) { factory ->\n            factory.create(packageName)\n        }\n\n    val context = LocalContext.current\n\n    val tabList by lazy { listOf(\"最近回复\", \"最新发布\", \"热度排序\") }\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n\n    val pagerState = rememberPagerState(\n        initialPage = 0,\n        pageCount = {\n            tabList.size\n        }\n    )\n    val scope = rememberCoroutineScope()\n    var refreshState by remember { mutableStateOf(false) }\n\n    val state = rememberCollapsingToolbarScaffoldState()\n\n    val windowInsets = WindowInsets.systemBars\n    var isScrollingUp by remember { mutableStateOf(false) }\n\n    CollapsingToolbarScaffold(\n        state = state,\n        scrollStrategy = ScrollStrategy.ExitUntilCollapsed,\n        modifier = Modifier.fillMaxSize(),\n        toolbar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        modifier = Modifier\n                            .graphicsLayer {\n                                alpha = if (state.toolbarState.progress == 0f) 1f else 0f\n                            },\n                        text = viewModel.title,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                },\n                actions = {\n                    if (viewModel.appState is LoadingState.Success) {\n                        Row(Modifier.wrapContentSize(Alignment.TopEnd)) {\n                            IconButton(\n                                onClick = {\n                                    onSearch(viewModel.title, \"apk\", viewModel.id)\n                                }\n                            ) {\n                                Icon(Icons.Default.Search, contentDescription = null)\n                            }\n                            Box {\n                                IconButton(onClick = { dropdownMenuExpanded = true }) {\n                                    Icon(\n                                        Icons.Default.MoreVert,\n                                        contentDescription = null\n                                    )\n                                }\n                                DropdownMenu(\n                                    expanded = dropdownMenuExpanded,\n                                    onDismissRequest = { dropdownMenuExpanded = false }\n                                ) {\n                                    if (isLogin) {\n                                        DropdownMenuItem(\n                                            text = {\n                                                Text(\n                                                    if (viewModel.isFollowed) \"UnFollow\"\n                                                    else \"Follow\"\n                                                )\n                                            },\n                                            onClick = {\n                                                dropdownMenuExpanded = false\n                                                viewModel.onGetFollowApk()\n                                            }\n                                        )\n                                    }\n                                    DropdownMenuItem(\n                                        text = {\n                                            Text(\n                                                if (viewModel.isBlocked) \"UnBlock\"\n                                                else \"Block\"\n                                            )\n                                        },\n                                        onClick = {\n                                            dropdownMenuExpanded = false\n                                            viewModel.blockApp()\n                                        }\n                                    )\n                                }\n                            }\n\n                        }\n                    }\n                },\n                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),\n            )\n            if (viewModel.appState !is LoadingState.Error) {\n                AppInfoCard(\n                    modifier = Modifier\n                        .padding(top = 58.dp)\n                        .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Start + WindowInsetsSides.Top))\n                        .parallax(0.5f)\n                        .graphicsLayer {\n                            alpha = state.toolbarState.progress\n                        },\n                    data = (viewModel.appState as? LoadingState.Success)?.response,\n                    onDownloadApk = viewModel::onGetDownloadLink\n                )\n            }\n        }\n    ) {\n\n        Column(modifier = Modifier.fillMaxSize()) {\n            when (viewModel.appState) {\n                LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                    Box(modifier = Modifier.fillMaxSize()) {\n                        LoadingCard(\n                            modifier = Modifier\n                                .align(Alignment.Center)\n                                .padding(horizontal = 10.dp),\n                            state = viewModel.appState,\n                            onClick = if (viewModel.appState is LoadingState.Loading) null\n                            else viewModel::refresh\n                        )\n                    }\n                }\n\n                is LoadingState.Success -> {\n\n                    if (viewModel.commentStatus == 1) {\n\n                        SecondaryTabRow(\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Start)),\n                            selectedTabIndex = pagerState.currentPage,\n                            indicator = {\n                                TabRowDefaults.SecondaryIndicator(\n                                    Modifier\n                                        .tabIndicatorOffset(\n                                            pagerState.currentPage,\n                                            matchContentSize = true\n                                        )\n                                        .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                                )\n                            },\n                            divider = {}\n                        ) {\n                            tabList.forEachIndexed { index, tab ->\n                                Tab(\n                                    selected = pagerState.currentPage == index,\n                                    onClick = {\n                                        if (pagerState.currentPage == index) {\n                                            refreshState = true\n                                        }\n                                        scope.launch { pagerState.animateScrollToPage(index) }\n                                    },\n                                    text = { Text(text = tab) }\n                                )\n                            }\n                        }\n\n                        HorizontalDivider()\n\n                        HorizontalPager(\n                            state = pagerState\n                        ) { index ->\n                            AppContentScreen(\n                                refreshState = refreshState,\n                                resetRefreshState = {\n                                    refreshState = false\n                                },\n                                id = viewModel.id,\n                                appCommentSort = when (index) {\n                                    0 -> EMPTY_STRING\n                                    1 -> \"&sort=dateline_desc\"\n                                    2 -> \"&sort=popular\"\n                                    else -> EMPTY_STRING\n\n                                },\n                                appCommentTitle = when (index) {\n                                    0 -> \"最近回复\"\n                                    1 -> \"最新发布\"\n                                    2 -> \"热度排序\"\n                                    else -> \"最近回复\"\n                                },\n                                onViewUser = onViewUser,\n                                onViewFeed = onViewFeed,\n                                onOpenLink = onOpenLink,\n                                onCopyText = onCopyText,\n                                onReport = onReport,\n                                isScrollingUp = {\n                                    isScrollingUp = it\n                                }\n                            )\n                        }\n\n                    } else {\n                        HorizontalDivider()\n                        Box(modifier = Modifier.fillMaxSize()) {\n                            Text(\n                                modifier = Modifier\n                                    .padding(16.dp)\n                                    .align(Alignment.Center),\n                                text = viewModel.commentStatusText,\n                            )\n                        }\n                    }\n                }\n            }\n        }\n\n        if (isLogin) {\n            AnimatedVisibility(\n                visible = isScrollingUp,\n                enter = slideInVertically { it * 2 },\n                exit = slideOutVertically { it * 2 },\n                modifier = Modifier\n                    .align(Alignment.BottomEnd)\n                    .navigationBarsPadding()\n                    .padding(20.dp)\n            ) {\n                FloatingActionButton(\n                    onClick = {\n                        val intent = Intent(context, ReplyActivity::class.java)\n                        intent.putExtra(\"type\", \"createFeed\")\n                        intent.putExtra(\"targetType\", \"apk\")\n                        intent.putExtra(\n                            \"targetId\",\n                            \"${1000000000 + (viewModel.id.toIntOrNull() ?: 4599)}\"\n                        )\n                        val animationBundle = ActivityOptionsCompat.makeCustomAnimation(\n                            context,\n                            R.anim.anim_bottom_sheet_slide_up,\n                            R.anim.anim_bottom_sheet_slide_down\n                        ).toBundle()\n                        ContextCompat.startActivity(context, intent, animationBundle)\n                    }\n                ) {\n                    Icon(\n                        painter = rememberDrawablePainter(\n                            ResourcesCompat.getDrawable(\n                                context.resources,\n                                R.drawable.outline_note_alt_24,\n                                context.theme\n                            )\n                        ),\n                        contentDescription = null\n                    )\n                }\n            }\n        }\n\n    }\n\n    when {\n        viewModel.downloadApk -> {\n            viewModel.reset()\n            context.downloadApk(\n                viewModel.downloadUrl,\n                \"${viewModel.title}-${viewModel.versionName}-${viewModel.versionCode}.apk\"\n            )\n        }\n    }\n\n    viewModel.toastText?.let {\n        context.makeToast(it)\n        viewModel.resetToastText()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/app/AppViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.app\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@HiltViewModel(assistedFactory = AppViewModel.ViewModelFactory::class)\nclass AppViewModel @AssistedInject constructor(\n    @Assisted var packageName: String,\n    private val networkRepo: NetworkRepo,\n    private val blackListRepo: BlackListRepo,\n) : ViewModel() {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(packageName: String): AppViewModel\n    }\n\n    lateinit var id: String\n    var title: String = EMPTY_STRING\n    lateinit var versionName: String\n    lateinit var versionCode: String\n    lateinit var commentStatusText: String\n    var commentStatus: Int = -1\n\n    var isFollowed by mutableStateOf(false)\n    var isBlocked by mutableStateOf(false)\n        private set\n\n    var appState by mutableStateOf<LoadingState<HomeFeedResponse.Data>>(LoadingState.Loading)\n        private set\n\n    init {\n        if (packageName.isNotEmpty()) {\n            fetchAppInfo()\n        }\n    }\n\n    private fun fetchAppInfo() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getAppInfo(packageName)\n                .collect { state ->\n                    if (state is LoadingState.Success) {\n                        val response = state.response\n                        id = response.id.orEmpty()\n                        title = response.title.orEmpty()\n                        versionName = response.apkversionname.orEmpty()\n                        versionCode = response.apkversioncode.orEmpty()\n                        commentStatus = response.commentStatus ?: -1\n                        commentStatusText = response.commentStatusText.orEmpty()\n                        isFollowed = response.userAction?.follow == 1\n                        checkIsBlocked(response.title.orEmpty())\n                    }\n                    appState = state\n                }\n        }\n    }\n\n    fun refresh() {\n        appState = LoadingState.Loading\n        fetchAppInfo()\n    }\n\n    var downloadApk by mutableStateOf(false)\n        private set\n    lateinit var downloadUrl: String\n    fun onGetDownloadLink() {\n        if (::downloadUrl.isInitialized) {\n            downloadApk = true\n        } else {\n            viewModelScope.launch(Dispatchers.IO) {\n                networkRepo.getAppDownloadLink(packageName, id, versionCode)\n                    .collect { result ->\n                        val link = result.getOrNull()\n                        if (link != null) {\n                            downloadUrl = link\n                            downloadApk = true\n                        } else {\n                            toastText =\n                                result.exceptionOrNull()?.message ?: \"failed to get download url\"\n                            result.exceptionOrNull()?.printStackTrace()\n                        }\n                    }\n            }\n        }\n    }\n\n    fun reset() {\n        downloadApk = false\n    }\n\n    var updateState by mutableStateOf<LoadingState<List<HomeFeedResponse.Data>>>(LoadingState.Loading)\n        private set\n\n    fun fetchAppsUpdate(pkg: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            updateState = LoadingState.Loading\n            networkRepo.getAppsUpdate(pkg)\n                .collect { state ->\n                    updateState = state\n                }\n        }\n    }\n\n    var toastText by mutableStateOf<String?>(null)\n        private set\n\n    fun resetToastText() {\n        toastText = null\n    }\n\n    fun onGetFollowApk() {\n        val followUrl = if (isFollowed) \"/v6/apk/unFollow\"\n        else \"/v6/apk/follow\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getFollow(followUrl, null, id)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (!response.message.isNullOrEmpty()) {\n                            toastText = response.message\n                        } else if (response.data?.follow != null) {\n                            toastText = if (isFollowed) \"取消关注成功\"\n                            else \"关注成功\"\n                            isFollowed = !isFollowed\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    fun blockApp() {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (isBlocked)\n                blackListRepo.deleteTopic(title)\n            else\n                blackListRepo.saveTopic(title)\n            isBlocked = !isBlocked\n        }\n    }\n\n    private fun checkIsBlocked(title: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            isBlocked = blackListRepo.checkTopic(title)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/appupdate/AppUpdateScreen.kt",
    "content": "package com.example.c001apk.compose.ui.appupdate\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.unit.dp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.UpdateCheckItem\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.app.AppViewModel\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.cards.AppUpdateCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.util.Utils.getBase64\nimport com.example.c001apk.compose.util.downloadApk\nimport org.json.JSONObject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppUpdateScreen(\n    onBackClick: () -> Unit,\n    data: List<UpdateCheckItem>?,\n    onViewApp: (String) -> Unit,\n) {\n\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    val viewModel =\n        hiltViewModel<AppViewModel, AppViewModel.ViewModelFactory> { factory ->\n            factory.create(EMPTY_STRING)\n        }\n    var count by remember { mutableIntStateOf(0) }\n\n    fun checkUpdate() {\n        data?.let { data ->\n            val updateCheckJsonObject = JSONObject()\n            data.forEach {\n                updateCheckJsonObject.put(it.key, it.value)\n            }\n            viewModel.fetchAppsUpdate(updateCheckJsonObject.toString().getBase64(false))\n        }\n    }\n\n    LaunchedEffect(Unit) {\n        if (viewModel.updateState is LoadingState.Loading) {\n            checkUpdate()\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = { Text(text = \"Update${if (count == 0) EMPTY_STRING else \": $count\"}\") },\n            )\n        }\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(\n                    top = paddingValues.calculateTopPadding(),\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                ),\n            contentPadding = PaddingValues(\n                start = 10.dp, end = 10.dp, top = 10.dp,\n                bottom = 10.dp + paddingValues.calculateBottomPadding()\n            ),\n            verticalArrangement = Arrangement.spacedBy(10.dp)\n        ) {\n\n            when (viewModel.updateState) {\n                LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                    item(key = \"updateState\") {\n                        Box(modifier = Modifier.fillParentMaxSize()) {\n                            LoadingCard(\n                                modifier = Modifier\n                                    .align(Alignment.Center),\n                                state = viewModel.updateState,\n                                onClick = if (viewModel.updateState is LoadingState.Loading) null\n                                else ::checkUpdate\n                            )\n                        }\n                    }\n                }\n\n                is LoadingState.Success -> {\n                    (viewModel.updateState as? LoadingState.Success)?.response?.let {\n                        count = it.size\n                        itemsIndexed(it,\n                            key = { _, item -> item.packageName + item.id }) { _, item ->\n                            AppUpdateCard(\n                                data = item,\n                                onViewApp = onViewApp,\n                                onDownloadApk = { packageName, id, title, versionName, versionCode ->\n                                    viewModel.packageName = packageName\n                                    viewModel.id = id\n                                    viewModel.title = title\n                                    viewModel.versionName = versionName\n                                    viewModel.versionCode = versionCode\n                                    viewModel.onGetDownloadLink()\n                                }\n                            )\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    when {\n        viewModel.downloadApk -> {\n            viewModel.reset()\n            context.downloadApk(\n                viewModel.downloadUrl,\n                \"${viewModel.title}-${viewModel.versionName}-${viewModel.versionCode}.apk\"\n            )\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/base/BaseViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.base\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.entityTemplateList\nimport com.example.c001apk.compose.constant.Constants.entityTypeList\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.FooterState\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.util.CookieUtil\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n\nenum class LikeType {\n    FEED, REPLY\n}\n\nabstract class BaseViewModel(\n    val networkRepo: NetworkRepo,\n    val blackListRepo: BlackListRepo,\n) : ViewModel() {\n\n    var isRefreshing by mutableStateOf(false)\n\n    var loadingState by mutableStateOf<LoadingState<List<HomeFeedResponse.Data>>>(LoadingState.Loading)\n\n    var footerState by mutableStateOf<FooterState>(FooterState.Success)\n\n    var page = 1\n    var firstLaunch = 1\n    var isLoadMore = false\n    var isEnd = false\n    var firstItem: String? = null\n    var lastItem: String? = null\n\n    abstract suspend fun customFetchData(): Flow<LoadingState<List<HomeFeedResponse.Data>>>\n\n    fun fetchData() {\n        viewModelScope.launch(Dispatchers.IO) {\n            customFetchData().collect { state ->\n                when (state) {\n                    LoadingState.Empty -> {\n                        if (loadingState is LoadingState.Success && !isRefreshing)\n                            footerState = FooterState.End\n                        else {\n                            loadingState = state\n                            footerState = FooterState.Success\n                        }\n                        isEnd = true\n                    }\n\n                    is LoadingState.Error -> {\n                        if (loadingState is LoadingState.Success)\n                            footerState = FooterState.Error(state.errMsg)\n                        else\n                            loadingState = state\n                        isEnd = true\n                    }\n\n                    LoadingState.Loading -> {\n                        if (loadingState is LoadingState.Success)\n                            footerState = FooterState.Loading\n                        else\n                            loadingState = state\n                    }\n\n                    is LoadingState.Success -> {\n                        page++\n                        var response = state.response.filter {\n                            (it.entityType in entityTypeList\n                                    || it.entityTemplate in\n                                    if (CookieUtil.showSquare) entityTemplateList\n                                    else entityTemplateList.toMutableList()\n                                        .also { list ->\n                                            list.removeAll(\n                                                listOf(\n                                                    \"iconMiniScrollCard\",\n                                                    \"iconMiniGridCard\"\n                                                )\n                                            )\n                                        })\n                                    && !blackListRepo.checkUid(\n                                if (!it.fromuid.isNullOrEmpty()) it.fromuid\n                                else it.uid.orEmpty()\n                            )\n                                    && !blackListRepo.checkTopic(\n                                it.tags + it.ttitle +\n                                        it.relationRows?.getOrNull(0)?.title\n                            )\n                        }\n                        firstItem = response.firstOrNull()?.id\n                        lastItem = response.lastOrNull()?.id\n\n                        handleResponse(response)?.let {\n                            response = it\n                        }\n\n                        if (isLoadMore) {\n                            response =\n                                ((loadingState as? LoadingState.Success)?.response ?: emptyList()) +\n                                        response\n                        }\n\n                        handleLoadMore(response)?.let {\n                            response = it\n                        }\n\n                        loadingState = LoadingState.Success(response)\n                        footerState = FooterState.Success\n                        if (response.isEmpty()) {\n                            isLoadMore = false\n                            isRefreshing = false\n                            loadMore()\n                        }\n                    }\n                }\n                isLoadMore = false\n                isRefreshing = false\n            }\n        }\n    }\n\n    open fun handleResponse(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data>? {\n        return null\n    }\n\n    open fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data>? {\n        return null\n    }\n\n    open fun refresh() {\n        if (!isRefreshing && !isLoadMore) {\n            page = 1\n            isEnd = false\n            isLoadMore = false\n            isRefreshing = true\n            firstItem = null\n            lastItem = null\n            fetchData()\n        }\n    }\n\n    open fun loadMore() {\n        if (!isRefreshing && !isLoadMore) {\n            isEnd = false\n            isLoadMore = true\n            fetchData()\n            if (loadingState is LoadingState.Success) {\n                footerState = FooterState.Loading\n            } else {\n                loadingState = LoadingState.Loading\n            }\n        }\n    }\n\n    var toastText by mutableStateOf<String?>(null)\n\n    fun resetToastText() {\n        toastText = null\n    }\n\n    fun onLike(id: String, like: Int, likeType: LikeType) {\n        val isLike = when (likeType) {\n            LikeType.FEED -> if (like == 1) \"unlike\" else \"like\"\n            LikeType.REPLY -> if (like == 1) \"unLikeReply\" else \"likeReply\"\n        }\n        val likeUrl = \"/v6/feed/$isLike\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postLike(likeUrl, id)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (!response.message.isNullOrEmpty()) {\n                            toastText = response.message\n                        } else if (response.data != null) {\n                            if (handleLikeResponse(id, like, response.data.count) == null) {\n                                val dataList = (loadingState as LoadingState.Success).response.map {\n                                    if (it.id == id) {\n                                        it.copy(\n                                            likenum = response.data.count,\n                                            userAction = it.userAction?.copy(like = if (like == 1) 0 else 1)\n                                        )\n                                    } else it\n                                }\n                                loadingState = LoadingState.Success(dataList)\n                            }\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    open fun handleLikeResponse(id: String, like: Int, count: String?): Boolean? {\n        return null\n    }\n\n    fun onDelete(id: String, deleteType: LikeType) {\n        val url = if (deleteType == LikeType.FEED) \"/v6/feed/deleteFeed\"\n        else \"/v6/feed/deleteReply\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postDelete(url, id)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                        } else if (data.data?.count == \"删除成功\") {\n                            var response = (loadingState as LoadingState.Success).response\n                            handleDeleteResponse(id, response)?.let {\n                                response = it\n                                loadingState = LoadingState.Success(response)\n                            }\n                            toastText = data.data.count\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    open fun handleDeleteResponse(\n        id: String,\n        response: List<HomeFeedResponse.Data>\n    ): List<HomeFeedResponse.Data>? {\n        return response.filterNot { it.id == id }\n    }\n\n    open fun onBlockUser(uid: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            blackListRepo.saveUid(uid)\n\n            if (loadingState is LoadingState.Success) {\n                var response =\n                    (loadingState as LoadingState.Success).response.filterNot { it.uid == uid }\n                handleBlockUser(uid, response)?.let {\n                    response = it\n                }\n                loadingState = LoadingState.Success(response)\n            }\n        }\n    }\n\n    open fun handleBlockUser(\n        uid: String,\n        response: List<HomeFeedResponse.Data>\n    ): List<HomeFeedResponse.Data>? {\n        return null\n    }\n\n    fun onFollowUser(uid: String, isFollow: Int) {\n        val url = if (isFollow == 1) \"/v6/user/unfollow\" else \"/v6/user/follow\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postFollowUnFollow(url, uid)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (!response.message.isNullOrEmpty()) {\n                            toastText = response.message\n                        } else {\n                            val follow = if (isFollow == 1) 0 else 1\n                            if (handleFollowResponse(follow) == null) {\n                                val dataList = (loadingState as LoadingState.Success).response.map {\n                                    if (it.uid == uid)\n                                        it.copy(isFollow = follow)\n                                    else it\n                                }\n                                loadingState = LoadingState.Success(dataList)\n                            }\n                            toastText = if (follow == 1) \"关注成功\"\n                            else \"取消关注成功\"\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    open fun handleFollowResponse(follow: Int): Boolean? {\n        return null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/base/PrefsViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.base\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.util.CookieUtil.szlmId\nimport com.example.c001apk.compose.util.CookieUtil.versionCode\nimport com.example.c001apk.compose.util.CookieUtil.versionName\nimport com.example.c001apk.compose.util.TokenDeviceUtils.encode\nimport com.example.c001apk.compose.util.TokenDeviceUtils.randHexString\nimport com.example.c001apk.compose.util.Utils.randomAndroidVersionRelease\nimport com.example.c001apk.compose.util.Utils.randomBrand\nimport com.example.c001apk.compose.util.Utils.randomDeviceModel\nimport com.example.c001apk.compose.util.Utils.randomMacAddress\nimport com.example.c001apk.compose.util.Utils.randomManufacturer\nimport com.example.c001apk.compose.util.Utils.randomSdkInt\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/15\n */\nopen class PrefsViewModel(\n    private val userPreferencesRepository: UserPreferencesRepository\n) : ViewModel() {\n\n    fun regenerateParams() {\n        viewModelScope.launch {\n            val manufacturer = randomManufacturer()\n            val brand = randomBrand()\n            val model = randomDeviceModel()\n            val buildNumber = randHexString(16)\n            val sdkInt = randomSdkInt()\n            val androidVersion = randomAndroidVersionRelease()\n            val mac = randomMacAddress()\n            val userAgent =\n                \"Dalvik/2.1.0 (Linux; U; Android $androidVersion; $model $buildNumber) (#Build; $brand; $model; $buildNumber; $androidVersion) +CoolMarket/$versionName-$versionCode-${Constants.MODE}\"\n            val xAppDevice =\n                encode(\"$szlmId; ; ; $mac; $manufacturer; $brand; $model; $buildNumber; null\")\n\n            userPreferencesRepository.apply {\n                setManufacturer(manufacturer)\n                setBrand(brand)\n                setModel(model)\n                setBuildNumber(buildNumber)\n                setSdkInt(sdkInt)\n                setAndroidVersion(androidVersion)\n                setUserAgent(userAgent)\n                setXAppDevice(xAppDevice)\n            }\n\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/blacklist/BlackListScreen.kt",
    "content": "package com.example.c001apk.compose.ui.blacklist\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.ExperimentalLayoutApi\nimport androidx.compose.foundation.layout.FlowRow\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.KeyboardActions\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Clear\nimport androidx.compose.material.icons.filled.ClearAll\nimport androidx.compose.material.icons.filled.Download\nimport androidx.compose.material.icons.filled.Upload\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.LocalTextStyle\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TextField\nimport androidx.compose.material3.TextFieldDefaults\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.TextFieldValue\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.core.text.isDigitsOnly\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.component.cards.SearchHistoryCard\nimport com.example.c001apk.compose.util.makeToast\nimport com.google.gson.Gson\nimport java.io.IOException\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/16\n */\n\nenum class BlackListType {\n    USER, TOPIC\n}\n\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)\n@Composable\nfun BlackListScreen(\n    onBackClick: () -> Unit,\n    type: String,\n    onViewUser: (String) -> Unit,\n    onViewTopic: (String) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<BlackListViewModel, BlackListViewModel.ViewModelFactory>(key = type) { factory ->\n            factory.create(BlackListType.valueOf(type))\n        }\n    val blackList by viewModel.blackList.collectAsStateWithLifecycle(initialValue = emptyList())\n\n    val context = LocalContext.current\n    val focusRequest = remember { FocusRequester() }\n    LaunchedEffect(Unit) {\n        try {\n            focusRequest.requestFocus()\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    var textInput by remember { mutableStateOf(TextFieldValue(text = EMPTY_STRING)) }\n    val textStyle = LocalTextStyle.current\n    var showClearDialog by remember { mutableStateOf(false) }\n    val rememberScrollState = rememberScrollState()\n\n    val backupSAFLauncher =\n        rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument(\"application/json\")) backup@{ uri ->\n            if (uri == null) return@backup\n            context.contentResolver.openOutputStream(uri).use { output ->\n                if (output == null)\n                    context.makeToast(\"导出失败\")\n                else\n                    output.write(Gson().toJson(blackList.map { it.data }).toByteArray())\n            }\n            context.makeToast(\"导出成功\")\n        }\n\n    fun onBackup() {\n        if (blackList.isEmpty()) {\n            context.makeToast(\"黑名单为空\")\n        } else {\n            try {\n                val date =\n                    SimpleDateFormat(\n                        \"yyyy-MM-dd_HH.mm.ss\", Locale.getDefault()\n                    ).format(Date())\n                backupSAFLauncher.launch(\"${type.lowercase()}_blacklist_$date.json\")\n            } catch (e: Exception) {\n                context.makeToast(\"导出失败\")\n                e.printStackTrace()\n            }\n        }\n    }\n\n    val restoreSAFLauncher =\n        rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) restore@{ uri ->\n            if (uri == null) return@restore\n            try {\n                val string = context.contentResolver\n                    .openInputStream(uri)?.reader().use { it?.readText() }\n                    ?: throw IOException(\"Backup file was damaged\")\n                val dataList: List<String> =\n                    Gson().fromJson(string, Array<String>::class.java).toList()\n                val currentList: List<String> = blackList.map { it.data }\n                val newList: List<String> =\n                    if (currentList.isEmpty())\n                        dataList\n                    else\n                        dataList.filter { it !in currentList }\n                if (newList.isNotEmpty())\n                    viewModel.insertList(newList.map { StringEntity(it) })\n                context.makeToast(\"导入成功\")\n            } catch (e: Exception) {\n                context.makeToast(e.message ?: \"导入失败\")\n                e.printStackTrace()\n            }\n        }\n\n    fun onRestore() {\n        try {\n            restoreSAFLauncher.launch(\"application/json\")\n        } catch (e: Exception) {\n            context.makeToast(\"导入失败\")\n            e.printStackTrace()\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    TextField(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .focusRequester(focusRequest),\n                        singleLine = true,\n                        value = textInput,\n                        onValueChange = {\n                            when (type) {\n                                BlackListType.USER.name -> {\n                                    if (it.text.isDigitsOnly())\n                                        textInput = it\n                                }\n\n                                else -> {\n                                    textInput = it\n                                }\n                            }\n                        },\n                        textStyle = textStyle.copy(fontSize = 18.sp),\n                        placeholder = {\n                            Text(\n                                text = when (type) {\n                                    BlackListType.USER.name -> \"uid\"\n                                    BlackListType.TOPIC.name -> \"topic\"\n                                    else -> EMPTY_STRING\n                                },\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis\n                            )\n                        },\n                        trailingIcon = {\n                            AnimatedVisibility(\n                                visible = textInput.text.isNotEmpty(),\n                                enter = fadeIn(),\n                                exit = fadeOut()\n                            ) {\n                                IconButton(onClick = {\n                                    textInput = TextFieldValue(EMPTY_STRING)\n                                }) {\n                                    Icon(\n                                        imageVector = Icons.Default.Clear,\n                                        contentDescription = null\n                                    )\n                                }\n                            }\n                        },\n                        colors = TextFieldDefaults.colors(\n                            focusedContainerColor = Color.Transparent,\n                            unfocusedContainerColor = Color.Transparent,\n                            focusedIndicatorColor = Color.Transparent,\n                            unfocusedIndicatorColor = Color.Transparent\n                        ),\n                        keyboardOptions = KeyboardOptions(\n                            keyboardType = when (type) {\n                                BlackListType.USER.name -> KeyboardType.Number\n                                BlackListType.TOPIC.name -> KeyboardType.Text\n                                else -> KeyboardType.Text\n                            },\n                            imeAction = ImeAction.Done\n                        ),\n                        keyboardActions = KeyboardActions(\n                            onDone = {\n                                if (textInput.text.trim().isNotEmpty()) {\n                                    viewModel.save(textInput.text)\n                                    textInput = TextFieldValue(EMPTY_STRING)\n                                }\n                            }\n                        )\n                    )\n                },\n                actions = {\n                    Row {\n                        IconButton(onClick = {\n                            onBackup()\n                        }) {\n                            Icon(imageVector = Icons.Default.Upload, contentDescription = null)\n                        }\n                        IconButton(onClick = {\n                            onRestore()\n                        }) {\n                            Icon(imageVector = Icons.Default.Download, contentDescription = null)\n                        }\n                    }\n                }\n            )\n        }\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(paddingValues)\n                .verticalScroll(rememberScrollState)\n        ) {\n            HorizontalDivider()\n\n            androidx.compose.animation.AnimatedVisibility(\n                visible = blackList.isNotEmpty(),\n                enter = fadeIn(),\n                exit = fadeOut()\n            ) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Text(\n                        text = type, modifier = Modifier\n                            .weight(1f)\n                            .padding(start = 10.dp),\n                        style = MaterialTheme.typography.titleSmall.copy(\n                            fontWeight = FontWeight.Bold,\n                        )\n                    )\n\n                    IconButton(\n                        onClick = {\n                            showClearDialog = true\n                        }\n                    ) {\n                        Icon(imageVector = Icons.Default.ClearAll, contentDescription = null)\n                    }\n                }\n            }\n\n            FlowRow(\n                horizontalArrangement = Arrangement.spacedBy(10.dp),\n                verticalArrangement = Arrangement.spacedBy(10.dp),\n                modifier = Modifier.padding(horizontal = 10.dp)\n            ) {\n                blackList.forEach {\n                    SearchHistoryCard(\n                        data = it.data,\n                        onSearch = {\n                            when (type) {\n                                BlackListType.USER.name -> onViewUser(it.data)\n                                BlackListType.TOPIC.name -> onViewTopic(it.data)\n                                else -> {}\n                            }\n                        },\n                        onDelete = {\n                            viewModel.delete(it.data)\n                        }\n                    )\n                }\n            }\n        }\n\n        if (blackList.isEmpty()) {\n            Box(\n                modifier = Modifier.fillMaxSize(),\n                contentAlignment = Alignment.Center\n            ) {\n                LoadingCard(state = LoadingState.Empty)\n            }\n        }\n\n    }\n\n    when {\n        showClearDialog -> {\n            AlertDialog(\n                onDismissRequest = { showClearDialog = false },\n                confirmButton = {\n                    TextButton(\n                        onClick = {\n                            showClearDialog = false\n                            viewModel.clearAll()\n                        }) {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                },\n                dismissButton = {\n                    TextButton(\n                        onClick = {\n                            showClearDialog = false\n                        }) {\n                        Text(text = stringResource(id = android.R.string.cancel))\n                    }\n                },\n                title = {\n                    Text(text = \"确定清除全部黑名单？\", modifier = Modifier.fillMaxWidth())\n                }\n            )\n        }\n    }\n\n    viewModel.toastText?.let {\n        viewModel.reset()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/blacklist/BlackListViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.blacklist\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/16\n */\n@HiltViewModel(assistedFactory = BlackListViewModel.ViewModelFactory::class)\nclass BlackListViewModel @AssistedInject constructor(\n    @Assisted val type: BlackListType = BlackListType.USER,\n    private val blackListRepo: BlackListRepo\n) : ViewModel() {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(type: BlackListType): BlackListViewModel\n    }\n\n    val blackList: Flow<List<StringEntity>> = when (type) {\n        BlackListType.USER -> blackListRepo.loadAllUserListFlow()\n        BlackListType.TOPIC -> blackListRepo.loadAllTopicListFlow()\n    }\n\n    fun clearAll() {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (type) {\n                BlackListType.USER -> blackListRepo.deleteAllUser()\n                BlackListType.TOPIC -> blackListRepo.deleteAllTopic()\n            }\n        }\n    }\n\n    fun delete(data: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (type) {\n                BlackListType.USER -> blackListRepo.deleteUid(data)\n                BlackListType.TOPIC -> blackListRepo.deleteTopic(data)\n            }\n        }\n    }\n\n    var toastText by mutableStateOf<String?>(null)\n        private set\n\n    fun save(data: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (type) {\n                BlackListType.USER -> {\n                    if (blackListRepo.checkUid(data))\n                        toast()\n                    else\n                        blackListRepo.insertUid(data)\n                }\n\n                BlackListType.TOPIC -> {\n                    if (blackListRepo.checkTopic(data))\n                        toast()\n                    else\n                        blackListRepo.insertTopic(data)\n                }\n            }\n        }\n    }\n\n    private fun toast() {\n        toastText = \"已存在\"\n    }\n\n    fun reset() {\n        toastText = null\n    }\n\n    fun insertList(list: List<StringEntity>) {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (type) {\n                BlackListType.USER -> blackListRepo.insertUidList(list)\n                BlackListType.TOPIC -> blackListRepo.insertTopicList(list)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/carousel/CarouselContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.carousel\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@Composable\nfun CarouselContentScreen(\n    url: String,\n    title: String,\n    paddingValues: PaddingValues,\n    refreshState: Boolean?,\n    resetRefreshState: () -> Unit,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    isHomeFeed: Boolean = false,\n    onReport: ((String, ReportType) -> Unit)? = null,\n) {\n\n    val viewModel =\n        hiltViewModel<CarouselViewModel, CarouselViewModel.ViewModelFactory>(key = title + url) { factory ->\n            factory.create(isInit = false, url = url, title = title)\n        }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = paddingValues,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        isHomeFeed = isHomeFeed,\n        onReport = onReport,\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/carousel/CarouselScreen.kt",
    "content": "package com.example.c001apk.compose.ui.carousel\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.PagerState\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SecondaryScrollableTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.model.TopicBean\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.decode\nimport com.example.c001apk.compose.util.makeToast\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun CarouselScreen(\n    onBackClick: () -> Unit,\n    url: String,\n    title: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<CarouselViewModel, CarouselViewModel.ViewModelFactory>(key = url + title) { factory ->\n            factory.create(isInit = true, url = url.decode, title = title)\n        }\n\n    val layoutDirection = LocalLayoutDirection.current\n    val scope = rememberCoroutineScope()\n    var refreshState by remember { mutableStateOf(false) }\n\n    var pagerState: PagerState\n\n    val context = LocalContext.current\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        text = viewModel.pageTitle,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                }\n            )\n        }\n    ) { paddingValues ->\n\n        Column(modifier = Modifier.padding(top = paddingValues.calculateTopPadding())) {\n            when (viewModel.loadingState) {\n                LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                    Box(modifier = Modifier.fillMaxSize()) {\n                        LoadingCard(\n                            modifier = Modifier\n                                .align(Alignment.Center)\n                                .padding(horizontal = 10.dp),\n                            state = viewModel.loadingState,\n                            onClick = if (viewModel.loadingState is LoadingState.Loading) null\n                            else viewModel::loadMore\n                        )\n                    }\n                }\n\n                is LoadingState.Success -> {\n                    val dataList =\n                        (viewModel.loadingState as LoadingState.Success<List<HomeFeedResponse.Data>>).response\n\n                    val isIconTabLinkGridCard =\n                        dataList.find { it.entityTemplate == \"iconTabLinkGridCard\" }\n                    if (isIconTabLinkGridCard == null) {\n                        HorizontalDivider()\n                        CommonScreen(\n                            viewModel = viewModel,\n                            refreshState = null,\n                            resetRefreshState = {},\n                            paddingValues = PaddingValues(\n                                start = paddingValues.calculateLeftPadding(layoutDirection),\n                                bottom = paddingValues.calculateBottomPadding(),\n                            ),\n                            onViewUser = onViewUser,\n                            onViewFeed = onViewFeed,\n                            onOpenLink = onOpenLink,\n                            onCopyText = onCopyText,\n                            onReport = onReport,\n                        )\n                    } else {\n                        isIconTabLinkGridCard.entities?.map {\n                            TopicBean(it.url.orEmpty(), it.title.orEmpty())\n                        }?.let { tabList ->\n                            pagerState = rememberPagerState(pageCount = { tabList.size })\n                            SecondaryScrollableTabRow(\n                                modifier = Modifier.padding(\n                                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                                ),\n                                selectedTabIndex = pagerState.currentPage,\n                                indicator = {\n                                    TabRowDefaults.SecondaryIndicator(\n                                        Modifier\n                                            .tabIndicatorOffset(\n                                                pagerState.currentPage,\n                                                matchContentSize = true\n                                            )\n                                            .clip(\n                                                RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp)\n                                            )\n                                    )\n                                },\n                                divider = {}\n                            ) {\n                                tabList.forEachIndexed { index, tab ->\n                                    Tab(\n                                        selected = pagerState.currentPage == index,\n                                        onClick = {\n                                            if (pagerState.currentPage == index) {\n                                                refreshState = true\n                                            }\n                                            scope.launch { pagerState.animateScrollToPage(index) }\n                                        },\n                                        text = { Text(text = tab.title) }\n                                    )\n                                }\n                            }\n\n                            HorizontalDivider()\n\n                            HorizontalPager(\n                                state = pagerState,\n                            ) { index ->\n                                CarouselContentScreen(\n                                    url = tabList[index].url,\n                                    title = tabList[index].title,\n                                    paddingValues = PaddingValues(\n                                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                                        bottom = paddingValues.calculateBottomPadding(),\n                                    ),\n                                    refreshState = refreshState,\n                                    resetRefreshState = { refreshState = false },\n                                    onViewUser = onViewUser,\n                                    onViewFeed = onViewFeed,\n                                    onOpenLink = onOpenLink,\n                                    onCopyText = onCopyText,\n                                    onReport = onReport,\n                                )\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/carousel/CarouselViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.carousel\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.entityTemplateList\nimport com.example.c001apk.compose.constant.Constants.entityTypeList\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@HiltViewModel(assistedFactory = CarouselViewModel.ViewModelFactory::class)\nclass CarouselViewModel @AssistedInject constructor(\n    @Assisted val isInit: Boolean,\n    @Assisted(\"url\") val url: String,\n    @Assisted(\"title\") val title: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            isInit: Boolean,\n            @Assisted(\"url\") url: String,\n            @Assisted(\"title\") title: String,\n        ): CarouselViewModel\n    }\n\n    init {\n        if (isInit)\n            preFetchData()\n        else\n            fetchData()\n    }\n\n    var pageTitle by mutableStateOf(\"\")\n        private set\n\n    private fun preFetchData() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getDataList(url, title, null, lastItem, page)\n                .collect { state ->\n                    loadingState = if (state is LoadingState.Success) {\n                        page++\n                        pageTitle = state.response.lastOrNull()?.extraDataArr?.pageTitle ?: title\n                        val response = state.response.filter {\n                            it.entityType in entityTypeList || it.entityTemplate in entityTemplateList\n                        } // TODO\n                        firstItem = response.firstOrNull()?.id\n                        lastItem = response.lastOrNull()?.id\n                        LoadingState.Success(response)\n                    } else state\n                }\n        }\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getDataList(url, title, null, lastItem, page)\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/chat/ChatScreen.kt",
    "content": "package com.example.c001apk.compose.ui.chat\n\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.PickVisualMediaRequest\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.RowScope\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.exclude\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.ime\nimport androidx.compose.foundation.layout.imePadding\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.layout.windowInsetsPadding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.grid.GridCells\nimport androidx.compose.foundation.lazy.grid.LazyVerticalGrid\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.PagerState\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.Send\nimport androidx.compose.material.icons.automirrored.outlined.Backspace\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.outlined.AddPhotoAlternate\nimport androidx.compose.material.icons.outlined.EmojiEmotions\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.ScaffoldDefaults\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.VerticalDivider\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.Dialog\nimport androidx.compose.ui.window.DialogProperties\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.core.content.res.ResourcesCompat\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareModel\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.ChatEditText\nimport com.example.c001apk.compose.ui.component.cards.CardIndicator\nimport com.example.c001apk.compose.ui.component.cards.ChatLeftCard\nimport com.example.c001apk.compose.ui.component.cards.ChatRightCard\nimport com.example.c001apk.compose.ui.component.cards.ChatTimeCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.EmojiUtils\nimport com.example.c001apk.compose.util.EmojiUtils.coolBList\nimport com.example.c001apk.compose.util.EmojiUtils.emojiList\nimport com.example.c001apk.compose.util.OSSUtil.getImageDimensionsAndMD5\nimport com.example.c001apk.compose.util.OSSUtil.toHex\nimport com.example.c001apk.compose.util.OssUploadUtil.ossUpload\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.makeToast\nimport com.google.accompanist.drawablepainter.rememberDrawablePainter\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport java.util.UUID\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/19\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ChatScreen(\n    onBackClick: () -> Unit,\n    ukey: String,\n    uid: String,\n    username: String,\n    onViewUser: (String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<ChatViewModel, ChatViewModel.ViewModelFactory>(key = ukey) { factory ->\n            factory.create(ukey = ukey)\n        }\n\n    val context = LocalContext.current\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    var showDialog by remember { mutableStateOf(false) }\n    val lazyListState = rememberLazyListState()\n    val scope = rememberCoroutineScope()\n    var clearText by remember { mutableStateOf(false) }\n    var clearFocus by remember { mutableStateOf(false) }\n    val windowInsets = WindowInsets.navigationBars\n\n    LaunchedEffect(key1 = viewModel.scroll) {\n        if (viewModel.scroll) {\n            clearText = true\n            delay(150)\n            lazyListState.scrollToItem(0)\n            viewModel.reset()\n        }\n    }\n\n    fun onClearFocus() {\n        clearFocus = true\n    }\n\n    val pickVisualMedia =\n        rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->\n            uri?.let { uri1 ->\n                try {\n                    val result = getImageDimensionsAndMD5(context.contentResolver, uri1)\n                    val md5Byte = result.second\n                    val md5 = md5Byte?.toHex() ?: \"\"\n                    val width = result.first?.first ?: 0\n                    val height = result.first?.second ?: 0\n                    val type = result.first?.third ?: \"\"\n\n                    viewModel.uriList = listOf(uri)\n                    viewModel.md5List = listOf(md5Byte)\n                    viewModel.typeList = listOf(type)\n                    val list = listOf(\n                        OSSUploadPrepareModel(\n                            name = \"${\n                                UUID.randomUUID().toString().replace(\"-\", \"\")\n                            }.${if (type.startsWith(\"image/\")) type.substring(6) else type}\",\n                            resolution = \"${width}x${height}\",\n                            md5 = md5,\n                        )\n                    )\n                    viewModel.onPostOSSUploadPrepare(uid, list)\n                } catch (e: Exception) {\n                    e.message?.let {\n                        context.makeToast(it)\n                    }\n                    e.printStackTrace()\n                }\n            }\n        }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton {\n                        clearFocus = true\n                        onBackClick()\n                    }\n                },\n                title = { Text(text = username) },\n                actions = {\n                    Box {\n                        IconButton(onClick = { dropdownMenuExpanded = true }) {\n                            Icon(imageVector = Icons.Default.MoreVert, contentDescription = null)\n                        }\n                        DropdownMenu(\n                            expanded = dropdownMenuExpanded,\n                            onDismissRequest = { dropdownMenuExpanded = false }) {\n                            listOf(\"Check\", \"Block\", \"Report\").forEachIndexed { index, menu ->\n                                DropdownMenuItem(\n                                    text = { Text(text = menu) },\n                                    onClick = {\n                                        dropdownMenuExpanded = false\n                                        when (index) {\n                                            0 -> {\n                                                onClearFocus()\n                                                onViewUser(uid)\n                                            }\n\n                                            1 -> viewModel.onBlockUser(uid)\n                                            2 -> {\n                                                onClearFocus()\n                                                onReport(uid, ReportType.USER)\n                                            }\n                                        }\n                                    }\n                                )\n                            }\n                        }\n                    }\n                }\n            )\n        },\n        contentWindowInsets = ScaffoldDefaults\n            .contentWindowInsets\n            .exclude(WindowInsets.navigationBars)\n            .exclude(WindowInsets.ime),\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(\n                    top = paddingValues.calculateTopPadding(),\n                )\n                .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Start))\n        ) {\n            HorizontalDivider()\n\n            LazyColumn(\n                modifier = Modifier\n                    .weight(1f)\n                    .fillMaxSize(),\n                reverseLayout = true,\n                state = lazyListState,\n            ) {\n                when (viewModel.loadingState) {\n                    LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                        item(key = \"loadingState\") {\n                            Box(modifier = Modifier.fillParentMaxSize()) {\n                                LoadingCard(\n                                    modifier = Modifier\n                                        .align(Alignment.Center)\n                                        .padding(horizontal = 10.dp),\n                                    state = viewModel.loadingState,\n                                    onClick = if (viewModel.loadingState is LoadingState.Loading) null\n                                    else viewModel::loadMore\n                                )\n                            }\n                        }\n                    }\n\n                    is LoadingState.Success -> {\n                        itemsIndexed(\n                            items = (viewModel.loadingState as LoadingState.Success).response,\n                            key = { _, item -> item.entityId + item.dateline },\n                        ) { index, item ->\n                            when (item.entityType) {\n                                \"message\" -> when (item.fromuid) {\n                                    CookieUtil.uid ->\n                                        ChatRightCard(\n                                            data = item,\n                                            onGetImageUrl = viewModel::onGetImageUrl,\n                                            onLongClick = { id, msg, url ->\n                                                onClearFocus()\n                                                viewModel.deleteId = id\n                                                viewModel.message = msg\n                                                viewModel.pic = url\n                                                showDialog = true\n                                            },\n                                            onViewUser = { uid ->\n                                                onClearFocus()\n                                                onViewUser(uid)\n                                            },\n                                            onClearFocus = ::onClearFocus\n                                        )\n\n                                    else ->\n                                        ChatLeftCard(\n                                            data = item,\n                                            onGetImageUrl = viewModel::onGetImageUrl,\n                                            onLongClick = { id, msg, url ->\n                                                onClearFocus()\n                                                viewModel.deleteId = id\n                                                viewModel.message = msg\n                                                viewModel.pic = url\n                                                showDialog = true\n                                            },\n                                            onViewUser = { uid ->\n                                                onClearFocus()\n                                                onViewUser(uid)\n                                            },\n                                            onClearFocus = ::onClearFocus\n                                        )\n                                }\n\n                                \"messageExtra\" -> ChatTimeCard(title = item.title.orEmpty())\n                            }\n\n                            if (index == (viewModel.loadingState as LoadingState.Success).response.lastIndex && !viewModel.isEnd) {\n                                viewModel.loadMore()\n                            }\n                        }\n                    }\n                }\n            }\n\n            ChatBottom(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .background(cardBg())\n                    .imePadding()\n                    .windowInsetsPadding(windowInsets.only(WindowInsetsSides.Start + WindowInsetsSides.Bottom)),\n                onPickImage = {\n                    onClearFocus()\n                    val options = ActivityOptionsCompat.makeCustomAnimation(\n                        context,\n                        R.anim.anim_bottom_sheet_slide_up,\n                        R.anim.anim_bottom_sheet_slide_down\n                    )\n                    pickVisualMedia.launch(\n                        PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly),\n                        options\n                    )\n                },\n                onSendMessage = {\n                    viewModel.onSendMessage(uid, it, EMPTY_STRING)\n                },\n                clearFocus = clearFocus,\n                clearText = clearText,\n                resetClear = {\n                    clearText = false\n                },\n                resetFocus = {\n                    clearFocus = false\n                },\n                onClearFocus = ::onClearFocus,\n                viewModel = viewModel,\n            )\n        }\n\n    }\n\n    when {\n        viewModel.showUploadDialog -> {\n            LoadingDialog()\n        }\n\n        showDialog -> {\n            Dialog(onDismissRequest = { showDialog = false }) {\n                ElevatedCard(\n                    modifier = Modifier.fillMaxWidth(),\n                    shape = RoundedCornerShape(28.dp),\n                    colors = CardDefaults.elevatedCardColors()\n                        .copy(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh)\n                ) {\n                    Column(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(vertical = 8.dp)\n                    ) {\n                        Text(\n                            text = \"删除\",\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clickable {\n                                    showDialog = false\n                                    viewModel.onDeleteMsg()\n                                }\n                                .padding(horizontal = 24.dp, vertical = 14.dp),\n                            style = MaterialTheme.typography.titleSmall\n                        )\n                        if (viewModel.pic.isEmpty()) {\n                            Text(\n                                text = \"复制\",\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .clickable {\n                                        showDialog = false\n                                        context.copyText(viewModel.message)\n                                    }\n                                    .padding(horizontal = 24.dp, vertical = 14.dp),\n                                style = MaterialTheme.typography.titleSmall\n                            )\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    viewModel.toastText?.let {\n        context.makeToast(it)\n        viewModel.resetToastText()\n    }\n\n    viewModel.uploadImage?.let { responseData ->\n        scope.launch(Dispatchers.IO) {\n            ossUpload(\n                context = context,\n                responseData = responseData,\n                uriList = viewModel.uriList,\n                typeList = viewModel.typeList,\n                md5List = viewModel.md5List,\n                iOnSuccess = {\n                    viewModel.onSendMessage(\n                        uid,\n                        EMPTY_STRING,\n                        \"/${responseData.fileInfo.getOrNull(0)?.uploadFileName}\"\n                    )\n                },\n                iOnFailure = {\n                    viewModel.showUploadDialog = false\n                    context.makeToast(\"图片上传失败\")\n                },\n                closeDialog = {\n                    viewModel.showUploadDialog = false\n                }\n            )\n            viewModel.resetUploadImage()\n        }\n    }\n\n}\n\n@Composable\nfun ChatBottom(\n    modifier: Modifier = Modifier,\n    viewModel: ChatViewModel,\n    onPickImage: () -> Unit,\n    onSendMessage: (String) -> Unit,\n    clearText: Boolean,\n    resetClear: () -> Unit,\n    clearFocus: Boolean,\n    resetFocus: () -> Unit,\n    onClearFocus: () -> Unit,\n) {\n    val recentList by\n    viewModel.recentEmojiData.collectAsStateWithLifecycle(initialValue = emptyList())\n    var shouldShowSendBtn by remember { mutableStateOf(false) }\n    var showEmojiPanel by remember { mutableStateOf(false) }\n    var clickedEmoji by remember { mutableStateOf<String?>(null) }\n    var textInput by remember { mutableStateOf(EMPTY_STRING) }\n    var pagerState: PagerState\n    val context = LocalContext.current\n\n    Column(modifier = modifier) {\n        Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {\n            Box(\n                modifier = Modifier\n                    .size(48.dp)\n                    .clickable {\n                        onClearFocus()\n                        showEmojiPanel = !showEmojiPanel\n                    },\n                contentAlignment = Alignment.Center\n            ) {\n                Image(\n                    imageVector = Icons.Outlined.EmojiEmotions,\n                    contentDescription = null,\n                    colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant)\n                )\n            }\n            ChatEditText(\n                modifier = Modifier\n                    .weight(1f)\n                    .padding(vertical = 6.dp)\n                    .clip(RoundedCornerShape(4.dp))\n                    .background(MaterialTheme.colorScheme.surface)\n                    .padding(horizontal = 6.dp),\n                showSendBtn = { shouldShowSendBtn = it },\n                updateInputText = {\n                    textInput = it\n                },\n                clearText = clearText,\n                resetClear = resetClear,\n                clearFocus = clearFocus,\n                resetFocus = resetFocus,\n                clickedEmoji = clickedEmoji,\n                resetEmoji = {\n                    clickedEmoji = null\n                },\n                onCloseEmojiPanel = {\n                    showEmojiPanel = false\n                },\n            )\n            Box(\n                modifier = Modifier\n                    .size(48.dp)\n                    .clickable {\n                        if (!shouldShowSendBtn)\n                            onPickImage()\n                        else\n                            onSendMessage(textInput)\n                    },\n                contentAlignment = Alignment.Center\n            ) {\n                Image(\n                    imageVector =\n                    if (!shouldShowSendBtn)\n                        Icons.Outlined.AddPhotoAlternate\n                    else\n                        Icons.AutoMirrored.Default.Send,\n                    contentDescription = null,\n                    colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant)\n                )\n            }\n        }\n        if (showEmojiPanel) {\n            pagerState = rememberPagerState(\n                initialPage = if (recentList.isEmpty()) 1 else 0,\n                pageCount = { 3 }\n            )\n            HorizontalPager(\n                state = pagerState,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(10.dp)\n            ) { index ->\n                Column(modifier = Modifier.fillMaxWidth()) {\n                    val pagerState1 = rememberPagerState {\n                        when (index) {\n                            0 -> 1\n                            1 -> 4\n                            2 -> 2\n                            else -> 0\n                        }\n                    }\n                    HorizontalPager(\n                        state = pagerState1,\n                        modifier = Modifier.fillMaxWidth()\n                    ) { index0 ->\n                        LazyVerticalGrid(\n                            columns = GridCells.Fixed(7),\n                            modifier = Modifier.fillMaxWidth()\n                        ) {\n                            items(28) {\n                                val emojiName = when (index) {\n                                    0 -> recentList.getOrNull(it)?.data\n                                    1 -> emojiList.getOrNull(index0)?.getOrNull(it)?.first\n                                    2 -> coolBList.getOrNull(index0)?.getOrNull(it)?.first\n                                    else -> null\n                                }\n                                val emojiRes = when (index) {\n                                    0 -> emojiName?.let { name ->\n                                        EmojiUtils.emojiMap.getValue(name)\n                                    }\n\n                                    1 -> emojiList.getOrNull(index0)?.getOrNull(it)?.second\n                                    2 -> coolBList.getOrNull(index0)?.getOrNull(it)?.second\n                                    else -> null\n                                }\n                                Box(\n                                    modifier = Modifier\n                                        .size(48.dp)\n                                        .clip(RoundedCornerShape(4.dp))\n                                        .clickable {\n                                            emojiName?.let { name ->\n                                                clickedEmoji = name\n                                                if (pagerState.currentPage != 0) {\n                                                    viewModel.updateRecentEmoji(\n                                                        name,\n                                                        recentList.size,\n                                                        recentList.lastOrNull()?.data\n                                                    )\n                                                }\n                                            }\n                                            if (it == 27) clickedEmoji = \"[c001apk]\"\n                                        },\n                                    contentAlignment = Alignment.Center,\n                                ) {\n                                    if (emojiRes != null) {\n                                        Image(\n                                            painter = rememberDrawablePainter(\n                                                drawable = ResourcesCompat.getDrawable(\n                                                    context.resources,\n                                                    emojiRes,\n                                                    context.theme\n                                                )\n                                            ),\n                                            contentDescription = null,\n                                            modifier = Modifier.size(28.dp)\n                                        )\n                                    } else if (it == 27) {\n                                        Icon(\n                                            imageVector = Icons.AutoMirrored.Outlined.Backspace,\n                                            contentDescription = null,\n                                            modifier = Modifier.size(28.dp)\n                                        )\n                                    }\n                                }\n\n                            }\n                        }\n                    }\n                    CardIndicator(\n                        modifier = Modifier\n                            .align(Alignment.CenterHorizontally)\n                            .padding(top = 10.dp),\n                        pagerState = pagerState1,\n                        defColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.3f),\n                    )\n                }\n            }\n            HorizontalDivider()\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .height(IntrinsicSize.Min)\n            ) {\n                TextIndicator(\n                    index = 0,\n                    title = \"最近\",\n                    pagerState = pagerState,\n                )\n                VerticalDivider()\n                TextIndicator(\n                    index = 1,\n                    title = \"默认\",\n                    pagerState = pagerState,\n                )\n                VerticalDivider()\n                TextIndicator(\n                    index = 2,\n                    title = \"酷币\",\n                    pagerState = pagerState,\n                )\n            }\n        }\n    }\n}\n\n@Composable\nfun RowScope.TextIndicator(\n    modifier: Modifier = Modifier,\n    index: Int,\n    title: String,\n    pagerState: PagerState,\n) {\n    val scope = rememberCoroutineScope()\n    Text(\n        text = title,\n        modifier = modifier\n            .weight(1f)\n            .background(\n                if (pagerState.currentPage == index)\n                    MaterialTheme.colorScheme.primary\n                else\n                    Color.Transparent\n            )\n            .clickable {\n                scope.launch {\n                    pagerState.scrollToPage(index)\n                }\n            }\n            .padding(vertical = 10.dp),\n        color = if (pagerState.currentPage == index)\n            MaterialTheme.colorScheme.onPrimary\n        else\n            MaterialTheme.colorScheme.onSurfaceVariant,\n        maxLines = 1,\n        overflow = TextOverflow.Ellipsis,\n        style = MaterialTheme.typography.titleSmall,\n        textAlign = TextAlign.Center,\n    )\n}\n\n@Composable\nprivate fun LoadingDialog() {\n    Dialog(\n        onDismissRequest = {},\n        properties = DialogProperties(dismissOnClickOutside = false, dismissOnBackPress = false)\n    ) {\n        Surface(\n            modifier = Modifier.size(100.dp), shape = RoundedCornerShape(20.dp)\n        ) {\n            Box(\n                contentAlignment = Alignment.Center,\n            ) {\n                CircularProgressIndicator()\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/chat/ChatViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.chat\n\nimport android.net.Uri\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareModel\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareResponse\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.repository.RecentEmojiRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport com.google.gson.Gson\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport okhttp3.MultipartBody\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/19\n */\n@HiltViewModel(assistedFactory = ChatViewModel.ViewModelFactory::class)\nclass ChatViewModel @AssistedInject constructor(\n    @Assisted val ukey: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n    private val recentEmojiRepo: RecentEmojiRepo\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(ukey: String): ChatViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.messageOperation(\"/v6/message/chat\", ukey, null, page, firstItem, lastItem)\n\n    override fun onBlockUser(uid: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            blackListRepo.saveUid(uid)\n        }\n    }\n\n    override fun handleResponse(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.reversed()\n    }\n\n    fun onGetImageUrl(id: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getImageUrl(id)\n                .collect { result ->\n                    val imageUrl = result.getOrNull()\n                    if (!imageUrl.isNullOrEmpty()) {\n                        var response = (loadingState as LoadingState.Success).response\n                        response = response.map { item ->\n                            if (item.id == id)\n                                item.copy(messagePic = imageUrl)\n                            else item\n                        }\n                        loadingState = LoadingState.Success(response)\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    fun onDeleteMsg() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.deleteMessage(\"/v6/message/delete\", ukey, deleteId)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                        } else if (data.data?.count != null) {\n                            var response = (loadingState as LoadingState.Success).response\n                            response = response.filterNot { it.id == deleteId }\n                            loadingState = LoadingState.Success(response)\n                            toastText = data.data.count\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    lateinit var deleteId: String\n    lateinit var message: String\n    lateinit var pic: String\n    var scroll by mutableStateOf(false)\n        private set\n\n    fun reset() {\n        scroll = false\n    }\n\n    fun onSendMessage(uid: String, text: String, url: String) {\n        showUploadDialog = true\n        val message = MultipartBody.Part.createFormData(\"message\", text)\n        val pic = MultipartBody.Part.createFormData(\"message_pic\", url)\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.sendMessage(uid, message, pic)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                        } else if (data.data != null) {\n                            val response =\n                                (loadingState as LoadingState.Success).response.toMutableList()\n                                    .also {\n                                        it.addAll(0, data.data)\n                                    }\n                            loadingState = LoadingState.Success(response)\n                            scroll = true\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"failed to send message\"\n                    }\n                    showUploadDialog = false\n                }\n        }\n    }\n\n    var uploadImage by mutableStateOf<OSSUploadPrepareResponse.Data?>(null)\n        private set\n\n    fun resetUploadImage() {\n        uploadImage = null\n    }\n\n    lateinit var uriList: List<Uri>\n    lateinit var typeList: List<String>\n    lateinit var md5List: List<ByteArray?>\n    var showUploadDialog by mutableStateOf(false)\n\n    fun onPostOSSUploadPrepare(uid: String, imageList: List<OSSUploadPrepareModel>) {\n        showUploadDialog = true\n        val ossUploadPrepareData = hashMapOf(\n            \"uploadBucket\" to \"message\",\n            \"uploadDir\" to \"message\",\n            \"is_anonymous\" to \"0\",\n            \"uploadFileList\" to Gson().toJson(imageList),\n            \"toUid\" to uid,\n        )\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postOSSUploadPrepare(ossUploadPrepareData)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                            showUploadDialog = false\n                        } else if (data.data != null) {\n                            uploadImage = data.data\n                        }\n                    } else {\n                        showUploadDialog = false\n                        toastText = result.exceptionOrNull()?.message ?: \"upload prepare failed\"\n                    }\n                }\n        }\n    }\n\n    val recentEmojiData = recentEmojiRepo.loadAllListFlow()\n\n    fun updateRecentEmoji(data: String, size: Int, last: String?) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (recentEmojiRepo.checkEmoji(data)) {\n                recentEmojiRepo.updateEmoji(data)\n            } else {\n                if (size == 27)\n                    last?.let {\n                        recentEmojiRepo.updateEmoji(it, data)\n                    }\n                else\n                    recentEmojiRepo.insertEmoji(StringEntity(data))\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/collection/CollectionScreen.kt",
    "content": "package com.example.c001apk.compose.ui.collection\n\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.FooterCard\nimport com.example.c001apk.compose.ui.component.ItemCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.util.DateUtils.fromToday\nimport com.example.c001apk.compose.util.ReportType\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/21\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun CollectionScreen(\n    onBackClick: () -> Unit,\n    id: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<CollectionViewModel, CollectionViewModel.ViewModelFactory>(key = id) { factory ->\n            factory.create(id = id)\n        }\n    val layoutDirection = LocalLayoutDirection.current\n    val state = rememberPullToRefreshState()\n    val lazyListState = rememberLazyListState()\n    val shouldShowTitle by remember {\n        derivedStateOf { lazyListState.firstVisibleItemIndex > 0 }\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton {\n                        onBackClick()\n                    }\n                },\n                title = {\n                    AnimatedContent(\n                        shouldShowTitle,\n                        label = EMPTY_STRING,\n                    ) {\n                        if (it) {\n                            Text(text = viewModel.title)\n                        } else {\n                            Text(text = \"收藏单\")\n                        }\n                    }\n                }\n            )\n        }\n    ) { paddingValues ->\n\n        PullToRefreshBox(\n            modifier = Modifier.padding(\n                top = paddingValues.calculateTopPadding(),\n                start = paddingValues.calculateLeftPadding(layoutDirection),\n            ),\n            state = state,\n            isRefreshing = viewModel.isRefreshing,\n            onRefresh = {\n                viewModel.isPull = true\n                viewModel.refresh()\n            },\n            indicator = {\n                PullToRefreshDefaults.Indicator(\n                    modifier = Modifier.align(Alignment.TopCenter),\n                    isRefreshing = viewModel.isRefreshing,\n                    state = state,\n                    color = MaterialTheme.colorScheme.primary,\n                )\n            }\n        ) {\n            LazyColumn(\n                modifier = Modifier\n                    .fillMaxSize(),\n                contentPadding = PaddingValues(bottom = 10.dp + paddingValues.calculateBottomPadding()),\n                verticalArrangement = Arrangement.spacedBy(10.dp),\n                state = lazyListState\n            ) {\n                when (viewModel.collectionState) {\n                    LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                        item(key = \"collectionState\") {\n                            Box(modifier = Modifier.fillParentMaxSize()) {\n                                LoadingCard(\n                                    modifier = Modifier\n                                        .align(Alignment.Center)\n                                        .padding(horizontal = 10.dp),\n                                    state = viewModel.collectionState,\n                                    onClick = if (viewModel.collectionState is LoadingState.Loading) null\n                                    else viewModel::refresh\n                                )\n                            }\n                        }\n                    }\n\n                    is LoadingState.Success -> {\n                        val response = (viewModel.collectionState as LoadingState.Success).response\n                        item(key = \"title\") {\n                            Text(\n                                text = viewModel.title,\n                                style = MaterialTheme.typography.titleLarge.copy(\n                                    fontWeight = FontWeight.SemiBold,\n                                    fontSize = 20.sp\n                                ),\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .padding(horizontal = 16.dp)\n                            )\n                        }\n                        item(key = \"info\") {\n                            Column(\n                                modifier = Modifier\n                                    .fillMaxWidth()\n                                    .padding(horizontal = 16.dp)\n                            ) {\n                                Row(\n                                    modifier = Modifier.fillMaxWidth(),\n                                    verticalAlignment = Alignment.CenterVertically\n                                ) {\n                                    CoilLoader(\n                                        url = response.userAvatar,\n                                        modifier = Modifier\n                                            .size(30.dp)\n                                            .clip(CircleShape)\n                                            .clickable { onViewUser(response.uid.orEmpty()) }\n                                    )\n                                    Text(\n                                        text = response.username.orEmpty(),\n                                        color = MaterialTheme.colorScheme.outline,\n                                        style = MaterialTheme.typography.bodyMedium.copy(fontSize = 16.sp),\n                                        maxLines = 1,\n                                        overflow = TextOverflow.Ellipsis,\n                                        modifier = Modifier.padding(start = 10.dp),\n                                    )\n                                    Text(\n                                        text = \"${fromToday(response.lastupdate ?: 0)}更新\",\n                                        color = MaterialTheme.colorScheme.outline,\n                                        style = MaterialTheme.typography.bodyMedium,\n                                        modifier = Modifier\n                                            .weight(1f)\n                                            .padding(start = 10.dp),\n                                        maxLines = 1,\n                                        overflow = TextOverflow.Ellipsis,\n                                    )\n                                    Text(\n                                        text = \"${response.followNum}人关注\",\n                                        color = MaterialTheme.colorScheme.outline,\n                                        style = MaterialTheme.typography.bodyMedium,\n                                        maxLines = 1,\n                                        overflow = TextOverflow.Ellipsis,\n                                    )\n                                }\n                                response.description?.let {\n                                    Text(\n                                        text = it,\n                                        style = MaterialTheme.typography.titleSmall,\n                                        modifier = Modifier.padding(top = 10.dp)\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (viewModel.collectionState is LoadingState.Success) {\n\n                    ItemCard(\n                        loadingState = viewModel.loadingState,\n                        loadMore = viewModel::loadMore,\n                        isEnd = viewModel.isEnd,\n                        onViewUser = onViewUser,\n                        onViewFeed = onViewFeed,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText,\n                        onReport = onReport,\n                        onLike = viewModel::onLike,\n                        onDelete = { id, deleteType, _ ->\n                            viewModel.onDelete(id, deleteType)\n                        },\n                        onBlockUser = { uid, _ ->\n                            viewModel.onBlockUser(uid)\n                        },\n                    )\n\n                    FooterCard(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        footerState = viewModel.footerState,\n                        loadMore = viewModel::loadMore,\n                    )\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/collection/CollectionViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.collection\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/21\n */\n@HiltViewModel(assistedFactory = CollectionViewModel.ViewModelFactory::class)\nclass CollectionViewModel @AssistedInject constructor(\n    @Assisted val id: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(id: String): CollectionViewModel\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getFollowList(\"/v6/collection/itemList\", null, id, null, page, lastItem)\n\n    init {\n        fetchInfo()\n    }\n\n    var collectionState by mutableStateOf<LoadingState<HomeFeedResponse.Data>>(LoadingState.Loading)\n        private set\n\n    var title by mutableStateOf(EMPTY_STRING)\n\n    var isPull = false\n    override fun refresh() {\n        if (!isRefreshing && !isLoadMore) {\n            if (collectionState is LoadingState.Success) {\n                page = 1\n                isEnd = false\n                isLoadMore = false\n                isRefreshing = true\n                firstItem = null\n                lastItem = null\n                fetchData()\n            } else {\n                if (isPull) {\n                    isPull = false\n                    viewModelScope.launch {\n                        isRefreshing = true\n                        delay(50)\n                        isRefreshing = false\n                    }\n                }\n                collectionState = LoadingState.Loading\n                fetchInfo()\n            }\n        }\n    }\n\n    private fun fetchInfo() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getFeedContent(\"/v6/collection/detail?id=$id\")\n                .collect { state ->\n                    collectionState = state\n                    if (state is LoadingState.Success) {\n                        title = state.response.title.orEmpty()\n                        fetchData()\n                    }\n                    isRefreshing = false\n                }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/ArticleItem.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyListScope\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.Text\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.logic.model.FeedArticleContentBean\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.cards.FeedArticleCard\nimport com.example.c001apk.compose.ui.component.cards.FeedBottomInfo\nimport com.example.c001apk.compose.ui.component.cards.FeedHeader\nimport com.example.c001apk.compose.ui.component.cards.FeedRows\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/17\n */\nfun LazyListScope.ArticleItem(\n    response: HomeFeedResponse.Data,\n    articleList: List<FeedArticleContentBean>,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onLike: () -> Unit,\n    onViewUser: (String) -> Unit,\n) {\n\n    item(key = \"header\") {\n        FeedHeader(\n            modifier = Modifier.padding(horizontal = 16.dp),\n            data = response,\n            onViewUser = onViewUser,\n            isFeedContent = true,\n            isFeedTop = false,\n        )\n    }\n\n    if (!response.messageCover.isNullOrEmpty()) {\n        item(key = \"cover\") {\n            NineImageView(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 16.dp)\n                    .padding(top = 12.dp),\n                pic = null,\n                picArr = listOf(response.messageCover),\n                feedType = null,\n                isSingle = true\n            )\n        }\n    }\n    if (!response.title.isNullOrEmpty()) {\n        item(key = \"title\") {\n            Text(\n                text = response.title,\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 16.dp)\n                    .padding(top = 12.dp),\n                fontWeight = FontWeight.Bold,\n                fontSize = 16.sp\n            )\n        }\n    }\n\n    items(\n        items = articleList,\n        key = { item -> item.key },\n    ) { item ->\n        FeedArticleCard(\n            item = item,\n            onOpenLink = onOpenLink,\n            onCopyText = onCopyText,\n        )\n    }\n\n    item(key = \"bottom\") {\n        FeedBottomInfo(\n            modifier = Modifier\n                .padding(horizontal = 16.dp)\n                .padding(bottom = if (response.targetRow == null && response.relationRows.isNullOrEmpty()) 12.dp else 0.dp),\n            isFeedContent = true,\n            ip = response.ipLocation.orEmpty(),\n            dateline = response.dateline ?: 0,\n            replyNum = response.replynum.orEmpty(),\n            likeNum = response.likenum.orEmpty(),\n            onViewFeed = {},\n            onLike = onLike,\n            like = response.userAction?.like,\n        )\n    }\n\n    item(key = \"rows\") {\n        FeedRows(\n            modifier = Modifier.padding(bottom = 12.dp),\n            isFeedContent = true,\n            relationRows = response.relationRows,\n            targetRow = response.targetRow,\n            onOpenLink = onOpenLink\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/Button.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.ArrowBack\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.runtime.Composable\nimport com.example.c001apk.compose.util.composeClick\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/2\n */\n@Composable\nfun BackButton(\n    onBackClick: () -> Unit\n) {\n    IconButton(onClick = composeClick {\n        onBackClick()\n    }) {\n        Icon(\n            imageVector = Icons.AutoMirrored.Outlined.ArrowBack,\n            contentDescription = null\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/ChatEditText.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Color\nimport android.text.InputFilter\nimport android.util.TypedValue\nimport android.view.KeyEvent\nimport android.view.MotionEvent\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.EditText\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.graphics.ColorUtils.setAlphaComponent\nimport com.example.c001apk.compose.util.EmojiTextWatcher\nimport com.example.c001apk.compose.util.FastDeleteAtUserKeyListener\nimport com.example.c001apk.compose.util.OnTextInputListener\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/20\n */\n@SuppressLint(\"ClickableViewAccessibility\")\n@Composable\nfun ChatEditText(\n    modifier: Modifier = Modifier,\n    showSendBtn: (Boolean) -> Unit,\n    updateInputText: (String) -> Unit,\n    clearText: Boolean,\n    resetClear: () -> Unit,\n    clearFocus: Boolean,\n    resetFocus: () -> Unit,\n    clickedEmoji: String?,\n    resetEmoji: () -> Unit,\n    onCloseEmojiPanel: () -> Unit,\n) {\n\n    val primary = MaterialTheme.colorScheme.primary.toArgb()\n\n    AndroidView(\n        modifier = modifier,\n        factory = { context ->\n            EditText(context).apply {\n                highlightColor = setAlphaComponent(primary, 128)\n                maxLines = 4\n                hint = \"写私信...\"\n                setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f)\n                setLineSpacing(0.0f, 1.2f)\n                setBackgroundColor(Color.TRANSPARENT)\n                filters = arrayOf(InputFilter.LengthFilter(500))\n                setOnTouchListener { _, event ->\n                    when (event.action) {\n                        MotionEvent.ACTION_DOWN -> {\n                            onCloseEmojiPanel()\n                            requestFocus()\n                        }\n                    }\n                    return@setOnTouchListener false\n                }\n                addTextChangedListener(\n                    EmojiTextWatcher(\n                        context = context,\n                        size = textSize,\n                        primary = primary,\n                        onAfterTextChanged = {\n                            if (text.toString().trim().isBlank()) {\n                                showSendBtn(false)\n                            } else {\n                                showSendBtn(true)\n                            }\n                            updateInputText(text.toString())\n                        },\n                    )\n                )\n                addTextChangedListener(\n                    OnTextInputListener(\n                        text = \"@\",\n                        onTextChange = {\n                            // isFromAt = true\n                            // launchAtTopic()\n                        }\n                    )\n                )\n                setOnKeyListener(FastDeleteAtUserKeyListener)\n            }\n        },\n        update = { editText ->\n            if (clearText) {\n                resetClear()\n                editText.text = null\n            }\n            if (clearFocus) {\n                resetFocus()\n                editText.clearFocus()\n                val inputMethodManager =\n                    editText.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n                inputMethodManager.hideSoftInputFromWindow(\n                    editText.windowToken,\n                    InputMethodManager.HIDE_NOT_ALWAYS\n                )\n            }\n            clickedEmoji?.let {\n                resetEmoji()\n                if (it == \"[c001apk]\")\n                    editText.dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))\n                else\n                    editText.editableText.replace(\n                        editText.selectionStart, editText.selectionEnd, it\n                    )\n            }\n        }\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/CoilLoader.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport android.os.Build\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.layout.ContentScale\nimport androidx.compose.ui.platform.LocalContext\nimport coil.compose.AsyncImage\nimport coil.decode.GifDecoder\nimport coil.decode.ImageDecoderDecoder\nimport coil.request.CachePolicy\nimport coil.request.ImageRequest\nimport com.example.c001apk.compose.constant.Constants.SUFFIX_GIF\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.http2https\nimport jp.wasabeef.transformers.coil.ColorFilterTransformation\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@Composable\nfun CoilLoader(\n    modifier: Modifier = Modifier,\n    url: String?,\n    colorFilter: Long? = null,\n) {\n    val prefs = LocalUserPreferences.current\n    url?.let {\n        val context = LocalContext.current\n        val imageUrl = it.http2https\n        AsyncImage(\n            model = ImageRequest.Builder(context)\n                .memoryCachePolicy(CachePolicy.ENABLED)\n                .diskCachePolicy(CachePolicy.ENABLED)\n                .memoryCacheKey(imageUrl)\n                .diskCacheKey(imageUrl)\n                .data(imageUrl)\n                .addHeader(\"User-Agent\", prefs.userAgent)\n                .apply {\n                    if (it.endsWith(SUFFIX_GIF)) {\n                        decoderFactory(\n                            if (Build.VERSION.SDK_INT >= 28) {\n                                ImageDecoderDecoder.Factory()\n                            } else {\n                                GifDecoder.Factory()\n                            }\n                        )\n                    }\n                    colorFilter?.let {\n                        transformations(ColorFilterTransformation(Color(colorFilter).toArgb()))\n                    }\n                    if (!it.endsWith(SUFFIX_GIF) && colorFilter == null && prefs.isDarkMode() && CookieUtil.imageFilter) {\n                        transformations(ColorFilterTransformation(Color(0x2D000000).toArgb()))\n                    }\n                }\n                .crossfade(true)\n                .build(),\n            contentDescription = null,\n            contentScale = ContentScale.Crop,\n            modifier = modifier,\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/CommonScreen.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.unit.dp\nimport androidx.core.view.isVisible\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.isScrollingUp\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun CommonScreen(\n    viewModel: BaseViewModel,\n    refreshState: Boolean?,\n    resetRefreshState: () -> Unit,\n    paddingValues: PaddingValues = PaddingValues(),\n    needTopPadding: Boolean = false,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    isHomeFeed: Boolean = false,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    onViewFFFList: ((String?, String, String?, String?) -> Unit)? = null,\n    onHandleRecent: ((String, String, String, Int) -> Unit)? = null,\n    onHandleMessage: ((String, Int) -> Unit)? = null,\n    onViewChat: ((String, String, String) -> Unit)? = null,\n    onDeleteNotice: ((String) -> Unit)? = null,\n    isScrollingUp: ((Boolean) -> Unit)? = null,\n) {\n\n    val view = LocalView.current\n    val layoutDirection = LocalLayoutDirection.current\n    val state = rememberPullToRefreshState()\n    val lazyListState = rememberLazyListState()\n\n    LaunchedEffect(refreshState) {\n        if (refreshState == true) {\n            resetRefreshState()\n            if (view.isVisible) {\n                viewModel.refresh()\n                lazyListState.scrollToItem(0)\n            }\n        }\n    }\n\n    isScrollingUp?.let {\n        it(lazyListState.isScrollingUp())\n    }\n\n    PullToRefreshBox(\n        modifier = Modifier.padding(\n            start = paddingValues.calculateLeftPadding(layoutDirection),\n            end = paddingValues.calculateRightPadding(layoutDirection),\n            top = if (needTopPadding) paddingValues.calculateTopPadding() else 0.dp\n        ),\n        state = state,\n        isRefreshing = viewModel.isRefreshing,\n        onRefresh = viewModel::refresh,\n        indicator = {\n            PullToRefreshDefaults.Indicator(\n                modifier = Modifier.align(Alignment.TopCenter),\n                isRefreshing = viewModel.isRefreshing,\n                state = state,\n                color = MaterialTheme.colorScheme.primary,\n            )\n        }\n    ) {\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize(),\n            contentPadding = PaddingValues(\n                top = 10.dp,\n                bottom = 10.dp + paddingValues.calculateBottomPadding()\n            ),\n            verticalArrangement = Arrangement.spacedBy(10.dp),\n            state = lazyListState\n        ) {\n\n            ItemCard(\n                loadingState = viewModel.loadingState,\n                loadMore = viewModel::loadMore,\n                isEnd = viewModel.isEnd,\n                onViewUser = onViewUser,\n                onViewFeed = onViewFeed,\n                onOpenLink = onOpenLink,\n                onCopyText = onCopyText,\n                isHomeFeed = isHomeFeed,\n                onReport = onReport,\n                onViewFFFList = onViewFFFList,\n                onLike = viewModel::onLike,\n                onDelete = { id, deleteType, _ ->\n                    viewModel.onDelete(id, deleteType)\n                },\n                onBlockUser = { uid, _ ->\n                    viewModel.onBlockUser(uid)\n                },\n                onFollowUser = viewModel::onFollowUser,\n                onHandleRecent = onHandleRecent,\n                onHandleMessage = onHandleMessage,\n                onViewChat = onViewChat,\n                onDeleteNotice = onDeleteNotice,\n            )\n\n            FooterCard(\n                modifier = Modifier.padding(horizontal = 10.dp),\n                footerState = viewModel.footerState,\n                loadMore = viewModel::loadMore,\n                isFeed = false\n            )\n\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/FooterCard.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.foundation.lazy.LazyListScope\nimport androidx.compose.ui.Modifier\nimport com.example.c001apk.compose.logic.state.FooterState\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\nfun LazyListScope.FooterCard(\n    modifier: Modifier = Modifier,\n    footerState: FooterState,\n    loadMore: () -> Unit,\n    isFeed: Boolean = false,\n) {\n    item(key = \"footer\") {\n        LoadingCard(\n            modifier = modifier,\n            state = footerState,\n            onClick = if (footerState is FooterState.Error) loadMore\n            else null,\n            isFeed = isFeed,\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/IconText.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.foundation.text.InlineTextContent\nimport androidx.compose.foundation.text.appendInlineContent\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.text.Placeholder\nimport androidx.compose.ui.text.PlaceholderVerticalAlign\nimport androidx.compose.ui.text.buildAnnotatedString\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.util.noRippleClickable\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@Composable\nfun IconText(\n    modifier: Modifier = Modifier,\n    imageVector: ImageVector,\n    title: String,\n    textSize: Float = 14f,\n    onClick: (() -> Unit)? = null,\n    isLike: Boolean = false,\n) {\n\n    val color = if (isLike) MaterialTheme.colorScheme.primary\n    else MaterialTheme.colorScheme.outline\n\n    val id = \"0\"\n    val text1 = buildAnnotatedString {\n        if (title.isNotEmpty()) appendInlineContent(id, \"[icon]\")\n        append(title)\n    }\n\n    val inlineContent = if (title.isNotEmpty()) mapOf(\n        Pair(\n            id,\n            InlineTextContent(\n                Placeholder(\n                    width = textSize.sp,\n                    height = textSize.sp,\n                    placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter\n                )\n            ) {\n                Icon(imageVector, null, tint = color)\n            }\n        )\n    ) else mapOf()\n\n    Text(\n        inlineContent = inlineContent,\n        text = text1,\n        lineHeight = textSize.sp,\n        fontSize = textSize.sp,\n        color = color,\n        maxLines = 1,\n        overflow = TextOverflow.Ellipsis,\n        modifier = run {\n            val tmp = if (onClick == null) modifier\n            else modifier\n                .noRippleClickable {\n                    onClick()\n                }\n            tmp\n        }\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/ImageView.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport android.graphics.Color\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.viewinterop.AndroidView\nimport coil.drawable.CrossfadeDrawable\nimport coil.load\nimport com.example.c001apk.compose.constant.Constants.PREFIX_HTTP\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.util.CookieUtil.token\nimport com.example.c001apk.compose.util.CookieUtil.uid\nimport com.example.c001apk.compose.util.CookieUtil.username\nimport com.example.c001apk.compose.util.ImageShowUtil.startBigImgViewSimple\nimport com.example.c001apk.compose.util.dp\nimport com.example.c001apk.compose.util.http2https\nimport com.example.c001apk.compose.view.RoundedImageView\nimport jp.wasabeef.transformers.coil.ColorFilterTransformation\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@Composable\nfun ImageView(\n    modifier: Modifier = Modifier,\n    url: String,\n    isRound: Boolean = false,\n    borderWidth: Float? = null,\n    borderColor: Int? = null,\n    isCover: Boolean = false,\n    isChat: Boolean = false,\n    onClearFocus: (() -> Unit)? = null,\n) {\n\n    val prefs = LocalUserPreferences.current\n    val isDarkMode = prefs.isDarkMode()\n    val cookie by lazy { \"uid=$uid; username=$username; token=$token\" }\n\n    AndroidView(\n        modifier = modifier,\n        factory = { context ->\n            RoundedImageView(context).apply {\n                scaleType = android.widget.ImageView.ScaleType.CENTER_CROP\n                if (isRound) {\n                    setCornerRadius(1000)\n                }\n                borderWidth?.let {\n                    setBorderWidth(it.dp)\n                }\n                borderColor?.let {\n                    setBorderColor(it)\n                }\n\n            }\n        },\n        update = { imageView ->\n            imageView.setOnClickListener {\n                if (isChat) {\n                    onClearFocus?.let { it() }\n                }\n                if (url.startsWith(PREFIX_HTTP)) {\n                    startBigImgViewSimple(\n                        imageView,\n                        url = if (isChat) url else url.http2https,\n                        cookie = if (isChat) cookie else null,\n                        userAgent = prefs.userAgent,\n                    )\n                }\n            }\n            imageView.load(if (isChat) url else url.http2https) {\n                crossfade(CrossfadeDrawable.DEFAULT_DURATION)\n                addHeader(\"User-Agent\", prefs.userAgent)\n                if (isChat) {\n                    addHeader(\"Cookie\", cookie)\n                }\n                if (isCover) {\n                    transformations(\n                        ColorFilterTransformation(\n                            Color.parseColor(\n                                if (isDarkMode)\n                                    \"#8D000000\"\n                                else\n                                    \"#5DFFFFFF\"\n                            )\n                        )\n                    )\n                }\n            }\n        }\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/ItemCard.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyListScope\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.LikeType\nimport com.example.c001apk.compose.ui.component.cards.AppCard\nimport com.example.c001apk.compose.ui.component.cards.AppCardType\nimport com.example.c001apk.compose.ui.component.cards.CarouselCard\nimport com.example.c001apk.compose.ui.component.cards.CollectionCard\nimport com.example.c001apk.compose.ui.component.cards.FeedCard\nimport com.example.c001apk.compose.ui.component.cards.FeedReplyCard\nimport com.example.c001apk.compose.ui.component.cards.IconLinkGridCard\nimport com.example.c001apk.compose.ui.component.cards.IconMiniGridCard\nimport com.example.c001apk.compose.ui.component.cards.IconMiniScrollCard\nimport com.example.c001apk.compose.ui.component.cards.IconScrollCard\nimport com.example.c001apk.compose.ui.component.cards.ImageSquareScrollCard\nimport com.example.c001apk.compose.ui.component.cards.ImageTextScrollCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.component.cards.MessageCard\nimport com.example.c001apk.compose.ui.component.cards.NotificationCard\nimport com.example.c001apk.compose.ui.component.cards.TextCard\nimport com.example.c001apk.compose.ui.component.cards.TitleCard\nimport com.example.c001apk.compose.util.ReportType\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\nfun LazyListScope.ItemCard(\n    loadingState: LoadingState<List<HomeFeedResponse.Data>>,\n    loadMore: () -> Unit,\n    isEnd: Boolean,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onShowTotalReply: ((String, String, String?) -> Unit)? = null,\n    isHomeFeed: Boolean = false,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    isTotalReply: Boolean = false,\n    isReply2Reply: Boolean = false,\n    onViewFFFList: ((String?, String, String?, String?) -> Unit)? = null,\n    onLike: ((String, Int, LikeType) -> Unit)? = null,\n    onDelete: ((String, LikeType, String?) -> Unit)? = null,\n    onBlockUser: (String, String?) -> Unit,\n    onFollowUser: ((String, Int) -> Unit)? = null,\n    onHandleRecent: ((String, String, String, Int) -> Unit)? = null,\n    onHandleMessage: ((String, Int) -> Unit)? = null,\n    onViewChat: ((String, String, String) -> Unit)? = null,\n    onDeleteNotice: ((String) -> Unit)? = null,\n    onReply: ((String, String, String, String?) -> Unit)? = null,\n) {\n\n    when (loadingState) {\n        LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n            item(key = \"loadingState\") {\n                Box(modifier = Modifier.fillParentMaxSize()) {\n                    LoadingCard(\n                        modifier = Modifier\n                            .align(Alignment.Center)\n                            .padding(horizontal = 10.dp),\n                        state = loadingState,\n                        onClick = if (loadingState is LoadingState.Loading) null\n                        else loadMore\n                    )\n                }\n            }\n        }\n\n        is LoadingState.Success -> {\n            itemsIndexed(\n                items = loadingState.response,\n                key = { _, item -> item.entityId + item.dateline + item.fuid + item.likeUid },\n            ) { index, item ->\n                when (val type = item.entityType) {\n                    \"card\" -> when (item.entityTemplate) {\n                        \"imageCarouselCard_1\" -> CarouselCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            entities = item.entities,\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"iconLinkGridCard\" -> IconLinkGridCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            entities = item.entities,\n                            onOpenLink = onOpenLink\n                        )\n\n\n                        \"iconMiniScrollCard\" -> IconMiniScrollCard(\n                            data = item,\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"iconMiniGridCard\" -> IconMiniGridCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            data = item,\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"imageSquareScrollCard\" -> ImageSquareScrollCard(\n                            entities = item.entities,\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"titleCard\" -> TitleCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            url = item.url.orEmpty(),\n                            title = item.title.orEmpty(),\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"iconScrollCard\" -> IconScrollCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            data = item,\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"imageTextScrollCard\" -> ImageTextScrollCard(\n                            data = item,\n                            onOpenLink = onOpenLink\n                        )\n\n                        \"noMoreDataCard\" -> TextCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            text = item.title.orEmpty()\n                        )\n\n                    }\n\n                    \"feed\" -> FeedCard(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        data = item,\n                        onViewUser = onViewUser,\n                        onViewFeed = onViewFeed,\n                        isFeedContent = false,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText,\n                        onReport = onReport,\n                        onLike = onLike,\n                        onDelete = onDelete,\n                        onBlockUser = { uid ->\n                            onBlockUser(uid, null)\n                        },\n                    )\n\n                    \"feed_reply\" -> {\n                        FeedReplyCard(\n                            data = item,\n                            onViewUser = onViewUser,\n                            onShowTotalReply = onShowTotalReply,\n                            onOpenLink = onOpenLink,\n                            onCopyText = onCopyText,\n                            onReport = onReport,\n                            isTotalReply = isTotalReply,\n                            isTopReply = isTotalReply && index == 0,\n                            onLike = onLike,\n                            onDelete = onDelete,\n                            onBlockUser = onBlockUser,\n                            isReply2Reply = if (index == 0) isReply2Reply else false,\n                            onReply = onReply,\n                        )\n                        if (item.fetchType == \"feed_reply\")\n                            HorizontalDivider()\n                    }\n\n                    \"apk\", \"product\", \"user\", \"topic\", \"contacts\", \"recentHistory\" -> AppCard(\n                        data = item,\n                        onOpenLink = onOpenLink,\n                        appCardType = when (type) {\n                            \"apk\" -> AppCardType.APP\n                            \"product\" -> AppCardType.PRODUCT\n                            \"user\" -> AppCardType.USER\n                            \"topic\" -> AppCardType.TOPIC\n                            \"contacts\" -> AppCardType.CONTACTS\n                            \"recentHistory\" -> AppCardType.RECENT\n                            else -> throw IllegalArgumentException(\"invalid type: $type\")\n                        },\n                        isHomeFeed = isHomeFeed,\n                        onViewUser = onViewUser,\n                        onFollowUser = onFollowUser,\n                        onHandleRecent = onHandleRecent,\n                    )\n\n                    \"notification\" -> NotificationCard(\n                        data = item,\n                        onViewUser = onViewUser,\n                        onOpenLink = onOpenLink,\n                        onReport = onReport,\n                        onDeleteNotice = onDeleteNotice,\n                    )\n\n                    \"message\" -> MessageCard(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        data = item,\n                        onOpenLink = onOpenLink,\n                        onViewUser = onViewUser,\n                        onHandleMessage = onHandleMessage,\n                        onViewChat = onViewChat,\n                    )\n\n                    \"collection\" -> CollectionCard(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        data = item,\n                        onViewFFFList = onViewFFFList,\n                    )\n\n                }\n\n                if (index == loadingState.response.lastIndex && !isEnd) {\n                    loadMore()\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/LinkText.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport android.graphics.Typeface\nimport android.text.TextUtils\nimport android.widget.TextView\nimport androidx.compose.material3.LocalContentColor\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.text.HtmlCompat\nimport androidx.core.text.method.LinkMovementMethodCompat\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.util.ImageShowUtil\nimport com.example.c001apk.compose.view.LinkTextView\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/2\n */\n@Composable\nfun LinkText(\n    modifier: Modifier = Modifier,\n    text: String?,\n    textSize: Float = 15f,\n    isBold: Boolean = false,\n    lineSpacingMultiplier: Float = 1.0f,\n    maxLines: Int? = null,\n    ellipsize: TextUtils.TruncateAt = TextUtils.TruncateAt.END,\n    color: Int? = null,\n    onOpenLink: ((String, String?) -> Unit)? = null,\n    isReply: Boolean = false,\n    onShowTotalReply: (() -> Unit)? = null,\n    imgList: List<String>? = null,\n) {\n    val contentColor = LocalContentColor.current\n    val userPreference = LocalUserPreferences.current\n\n    val primary = MaterialTheme.colorScheme.primary.toArgb()\n    AndroidView(\n        modifier = modifier,\n        factory = { context ->\n            LinkTextView(context).apply {\n                setParams(\n                    isReply = isReply,\n                    size = textSize,\n                    fontScale = userPreference.fontScale,\n                )\n                if (isBold)\n                    setTypeface(typeface, Typeface.BOLD)\n                setLineSpacing(0.0f, lineSpacingMultiplier)\n                maxLines?.let {\n                    this.maxLines = it\n                    this.ellipsize = ellipsize\n                }\n                setTextColor(contentColor.toArgb())\n                color?.let {\n                    setTextColor(it)\n                }\n            }\n        },\n        update = { textView ->\n            textView.setSpText(\n                text = text.orEmpty(),\n                color = primary,\n                onOpenLink = { url, title ->\n                    onOpenLink?.let { it(url, title) }\n                },\n                onShowTotalReply = onShowTotalReply,\n                onShowImages = { url ->\n                    ImageShowUtil.startBigImgViewSimple(\n                        textView.context,\n                        imgList ?: listOf(url),\n                        userAgent = userPreference.userAgent,\n                    )\n                }\n            )\n        }\n    )\n}\n\n@Composable\nfun HtmlText(html: String, modifier: Modifier = Modifier) {\n    val contentColor = LocalContentColor.current.toArgb()\n    val primary = MaterialTheme.colorScheme.primary.toArgb()\n    AndroidView(\n        modifier = modifier,\n        factory = { context ->\n            TextView(context).apply {\n                movementMethod = LinkMovementMethodCompat.getInstance()\n                setTextColor(contentColor)\n                setLinkTextColor(primary)\n                text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT)\n            }\n        }\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/NineImageView.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.viewinterop.AndroidView\nimport com.example.c001apk.compose.constant.Constants.SUFFIX_THUMBNAIL\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.util.ImageShowUtil.getImageLp\nimport com.example.c001apk.compose.view.NineGridImageView\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/5\n */\n@Composable\nfun NineImageView(\n    modifier: Modifier = Modifier,\n    pic: String?,\n    picArr: List<String>?,\n    feedType: String?,\n    isSingle: Boolean = false\n) {\n    val prefs = LocalUserPreferences.current\n\n    val primaryContainer = MaterialTheme.colorScheme.primaryContainer.toArgb()\n    val onPrimaryContainer = MaterialTheme.colorScheme.onPrimaryContainer.toArgb()\n\n    AndroidView(\n        modifier = modifier,\n        factory = {\n            NineGridImageView(it).apply {\n                this.colorPrimaryContainer = primaryContainer\n                this.colorOnPrimaryContainer = onPrimaryContainer\n                this.isSingle = isSingle\n                this.userAgent = prefs.userAgent\n            }\n        },\n        update = { imageView ->\n            if (!picArr.isNullOrEmpty()) {\n                if (picArr.size == 1 || feedType in listOf(\"feedArticle\", \"trade\")) {\n                    val imageLp = getImageLp(pic ?: picArr[0])\n                    imageView.imgWidth = imageLp.first\n                    imageView.imgHeight = imageLp.second\n                }\n                imageView.apply {\n                    val urlList: MutableList<String> = ArrayList()\n                    if (feedType in listOf(\"feedArticle\", \"trade\") && imgWidth > imgHeight)\n                        if (!pic.isNullOrEmpty()) urlList.add(\"$pic$SUFFIX_THUMBNAIL\")\n                        else urlList.add(\"${picArr[0]}$SUFFIX_THUMBNAIL\")\n                    else\n                        urlList.addAll(picArr.map { \"$it$SUFFIX_THUMBNAIL\" })\n                    setUrlList(urlList)\n                }\n            }\n        }\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/Transitions.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport androidx.compose.animation.core.Easing\nimport androidx.compose.animation.core.FastOutSlowInEasing\nimport androidx.compose.animation.core.FiniteAnimationSpec\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.scaleIn\nimport androidx.compose.animation.scaleOut\nimport androidx.compose.animation.slideIn\nimport androidx.compose.animation.slideOut\nimport androidx.compose.ui.unit.IntOffset\nimport androidx.compose.ui.unit.IntSize\n\nsealed class TransitionDurations(private val milliseconds: Int) {\n    data object Fast : TransitionDurations(150)\n    data object Normal : TransitionDurations(300)\n    data object Slow : TransitionDurations(500)\n\n    fun <T> asTween(delayMillis: Int = 0, easing: Easing = FastOutSlowInEasing) =\n        tween<T>(milliseconds, delayMillis, easing)\n}\n\ndata class ScaleTransition(val enterScale: Float, val exitScale: Float) {\n    fun enterTransition(\n        animationSpec: FiniteAnimationSpec<Float> = defaultAnimationSpec(),\n    ) = scaleIn(animationSpec, enterScale) + fadeIn()\n\n    fun exitTransition(\n        animationSpec: FiniteAnimationSpec<Float> = defaultAnimationSpec(),\n    ) = scaleOut(animationSpec, exitScale) + fadeOut()\n\n    companion object {\n        private const val ShrinkScale = 0.95f\n        private const val ExpandScale = 1.05f\n\n        val scaleUp = ScaleTransition(ShrinkScale, ExpandScale)\n        val scaleDown = ScaleTransition(ExpandScale, ShrinkScale)\n\n        private fun <T> defaultAnimationSpec() = TransitionDurations.Slow.asTween<T>()\n    }\n}\n\nprivate typealias CalcOffsetFn = (IntSize) -> IntOffset\n\ndata class SlideTransition(\n    val enterOffset: CalcOffsetFn,\n    val exitOffset: CalcOffsetFn,\n) {\n    fun enterTransition(\n        animationSpec: FiniteAnimationSpec<IntOffset> = defaultAnimationSpec(),\n    ) = slideIn(animationSpec) { enterOffset(it) } + fadeIn()\n\n    fun exitTransition(\n        animationSpec: FiniteAnimationSpec<IntOffset> = defaultAnimationSpec(),\n    ) = slideOut(animationSpec) { exitOffset(it) } + fadeOut()\n\n    companion object {\n        private val slideUpOffset: CalcOffsetFn = { IntOffset(0, calculateOffset(-it.height)) }\n        private val slideDownOffset: CalcOffsetFn = { IntOffset(0, calculateOffset(it.height)) }\n        private val slideLeftOffset: CalcOffsetFn = { IntOffset(calculateOffset(-it.width), 0) }\n        private val slideRightOffset: CalcOffsetFn = { IntOffset(calculateOffset(it.width), 0) }\n\n        val slideUp = SlideTransition(slideDownOffset, slideUpOffset)\n        val slideDown = SlideTransition(slideUpOffset, slideDownOffset)\n        val slideLeft = SlideTransition(slideRightOffset, slideLeftOffset)\n        val slideRight = SlideTransition(slideLeftOffset, slideRightOffset)\n\n        private fun calculateOffset(size: Int) = (0.1 * size).toInt()\n        private fun <T> defaultAnimationSpec() = TransitionDurations.Normal.asTween<T>()\n    }\n}\n\nobject FadeTransition {\n    fun enterTransition(animationSpec: FiniteAnimationSpec<Float> = defaultAnimationSpec()) =\n        fadeIn(animationSpec)\n\n    fun exitTransition(animationSpec: FiniteAnimationSpec<Float> = defaultAnimationSpec()) =\n        fadeOut(animationSpec)\n\n    private fun <T> defaultAnimationSpec() = TransitionDurations.Normal.asTween<T>()\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/WebView.kt",
    "content": "package com.example.c001apk.compose.ui.component\n\nimport android.annotation.SuppressLint\nimport android.app.DownloadManager\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Environment\nimport android.view.ViewGroup\nimport android.webkit.CookieManager\nimport android.webkit.URLUtil\nimport android.webkit.WebChromeClient\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.viewinterop.AndroidView\nimport androidx.core.content.ContextCompat\nimport androidx.webkit.WebSettingsCompat\nimport androidx.webkit.WebViewFeature\nimport com.example.c001apk.compose.constant.Constants.APP_ID\nimport com.example.c001apk.compose.constant.Constants.UTF8\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.webview.ActionType\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.decode\nimport com.example.c001apk.compose.util.makeToast\nimport com.example.c001apk.compose.util.openInBrowser\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport java.net.URISyntaxException\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@SuppressLint(\"SetJavaScriptEnabled\")\n@Composable\nfun WebView(\n    modifier: Modifier = Modifier,\n    url: String,\n    isLogin: Boolean,\n    onBack: Boolean,\n    onBackReset: () -> Unit,\n    actionType: ActionType = ActionType.NONE,\n    resetActionType: () -> Unit,\n    onFinishLogin: (String) -> Unit,\n    onFinish: () -> Unit,\n    onUpdateProgress: (Float) -> Unit,\n    onUpdateTitle: (String) -> Unit,\n    onShowSnackbar: (String) -> Unit,\n) {\n\n    val prefs = LocalUserPreferences.current\n    val isDarkMode = prefs.isDarkMode()\n\n    AndroidView(\n        modifier = modifier.fillMaxSize(),\n        factory = { context ->\n            WebView(context).apply {\n                layoutParams = ViewGroup.LayoutParams(\n                    ViewGroup.LayoutParams.MATCH_PARENT,\n                    ViewGroup.LayoutParams.MATCH_PARENT\n                )\n\n                settings.apply {\n                    javaScriptEnabled = true\n                    blockNetworkImage = false\n                    mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n                    domStorageEnabled = true\n                    setSupportZoom(true)\n                    builtInZoomControls = true\n                    displayZoomControls = false\n                    cacheMode = WebSettings.LOAD_NO_CACHE\n                    defaultTextEncodingName = UTF8\n                    allowContentAccess = true\n                    useWideViewPort = true\n                    loadWithOverviewMode = true\n                    javaScriptCanOpenWindowsAutomatically = true\n                    loadsImagesAutomatically = true\n                    allowFileAccess = false\n                    userAgentString = prefs.userAgent\n                    if (SDK_INT >= 32) {\n                        if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) {\n                            WebSettingsCompat.setAlgorithmicDarkeningAllowed(this, true)\n                        }\n                    } else {\n                        if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {\n                            if (isDarkMode) {\n                                WebSettingsCompat.setForceDark(\n                                    this,\n                                    WebSettingsCompat.FORCE_DARK_ON\n                                )\n                            }\n                        }\n                    }\n                }\n\n                CookieManager.getInstance().let {\n                    it.setAcceptCookie(true)\n                    it.setAcceptThirdPartyCookies(this@apply, true)\n                    if (prefs.isLogin) {\n                        it.removeAllCookies { }\n                        it.setCookie(\".coolapk.com\", \"DID=${prefs.szlmId}\")\n                        it.setCookie(\".coolapk.com\", \"forward=https://www.coolapk.com\")\n                        it.setCookie(\".coolapk.com\", \"displayVersion=v14\")\n                        it.setCookie(\".coolapk.com\", \"uid=${prefs.uid}\")\n                        it.setCookie(\".coolapk.com\", \"username=${prefs.username}\")\n                        it.setCookie(\".coolapk.com\", \"token=${prefs.token}\")\n                    }\n                }\n\n                setDownloadListener { url, userAgent, contentDisposition, mimetype, _ ->\n                    val fileName = URLUtil.guessFileName(url, contentDisposition, mimetype).decode\n                    MaterialAlertDialogBuilder(context).apply {\n                        setTitle(\"确定下载文件吗？\")\n                        setMessage(fileName)\n                        setNeutralButton(\"外部打开\") { _, _ ->\n                            context.openInBrowser(url)\n                        }\n                        setNegativeButton(android.R.string.cancel, null)\n                        setPositiveButton(android.R.string.ok) { _, _ ->\n                            try {\n                                val request = DownloadManager.Request(Uri.parse(url))\n                                    .setMimeType(mimetype)\n                                    .addRequestHeader(\n                                        \"cookie\",\n                                        CookieManager.getInstance().getCookie(url)\n                                    )\n                                    .addRequestHeader(\"User-Agent\", userAgent)\n                                    .setTitle(fileName)\n                                    .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)\n                                    .setDestinationInExternalPublicDir(\n                                        Environment.DIRECTORY_DOWNLOADS,\n                                        fileName\n                                    )\n                                val downloadManager =\n                                    context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager\n                                downloadManager.enqueue(request)\n                            } catch (e: Exception) {\n                                context.makeToast(\"下载失败\")\n                                context.copyText(url)\n                                e.printStackTrace()\n                            }\n                        }\n                        show()\n                    }\n                }\n\n                webViewClient = object : WebViewClient() {\n                    override fun shouldOverrideUrlLoading(\n                        webView: WebView?, request: WebResourceRequest?\n                    ): Boolean {\n\n                        request?.let {\n                            val requestUrl = request.url.toString()\n\n                            if (isLogin && requestUrl == \"https://www.coolapk.com/\") {\n                                val cookie = CookieManager.getInstance().getCookie(requestUrl)\n                                onFinishLogin(cookie)\n                            }\n\n                            try {\n                                //处理intent协议\n                                if (requestUrl.startsWith(\"intent://\")) {\n                                    val intent: Intent\n                                    try {\n                                        intent =\n                                            Intent.parseUri(requestUrl, Intent.URI_INTENT_SCHEME)\n                                        intent.addCategory(\"android.intent.category.BROWSABLE\")\n                                        intent.component = null\n                                        intent.selector = null\n                                        val resolves =\n                                            context.packageManager.queryIntentActivities(\n                                                intent,\n                                                0\n                                            )\n                                        if (resolves.size > 0) {\n                                            ContextCompat.startActivity(context, intent, null)\n                                        }\n                                        return true\n                                    } catch (e: URISyntaxException) {\n                                        e.printStackTrace()\n                                    }\n                                }\n                                // 处理自定义scheme协议\n                                if (!requestUrl.startsWith(\"http\")) {\n                                    onShowSnackbar(requestUrl)\n                                    return true\n                                }\n                            } catch (e: Exception) {\n                                e.printStackTrace()\n                            }\n                        }\n                        return super.shouldOverrideUrlLoading(webView, request)\n                    }\n                }\n\n                webChromeClient = object : WebChromeClient() {\n                    override fun onCloseWindow(window: WebView?) {\n                        super.onCloseWindow(window)\n                        onFinish()\n                    }\n\n                    override fun onProgressChanged(view: WebView?, newProgress: Int) {\n                        onUpdateProgress(newProgress / 100f)\n                    }\n\n                    override fun onReceivedTitle(view: WebView, title: String) {\n                        super.onReceivedTitle(view, title)\n                        onUpdateTitle(title)\n                    }\n                }\n\n                loadUrl(url, mapOf(\"X-Requested-With\" to APP_ID))\n            }\n        },\n        update = { webView ->\n\n            when (actionType) {\n                ActionType.REFRESH -> {\n                    resetActionType()\n                    webView.reload()\n                }\n\n                ActionType.COPY -> {\n                    resetActionType()\n                    webView.context.copyText(webView.url)\n                }\n\n                ActionType.OPEN -> {\n                    resetActionType()\n                    webView.url?.let { webView.context.openInBrowser(it) }\n                }\n\n                ActionType.CLEAN -> {\n                    resetActionType()\n                    webView.clearHistory()\n                    webView.clearCache(true)\n                    webView.clearFormData()\n                    webView.context.makeToast(\"清除缓存成功\")\n                }\n\n                ActionType.NONE -> {}\n            }\n\n\n            if (onBack) {\n                onBackReset()\n                if (webView.canGoBack())\n                    webView.goBack()\n                else\n                    onFinish()\n            }\n        }\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/AppCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.DateUtils.fromToday\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n\nenum class AppCardType {\n    APP, PRODUCT, TOPIC, USER, CONTACTS, RECENT\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun AppCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit,\n    appCardType: AppCardType,\n    isHomeFeed: Boolean = false,\n    onViewUser: (String) -> Unit,\n    onFollowUser: ((String, Int) -> Unit)? = null,\n    onHandleRecent: ((String, String, String, Int) -> Unit)? = null,\n) {\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxSize()\n            .padding(horizontal = 10.dp)\n            .clip(MaterialTheme.shapes.medium)\n            .background(\n                if (isHomeFeed) MaterialTheme.colorScheme.surface\n                else if (data.isTop == 1) MaterialTheme.colorScheme.primaryContainer\n                else cardBg()\n            )\n            .combinedClickable(\n                onClick = {\n                    if (appCardType == AppCardType.CONTACTS)\n                        onViewUser(data.userInfo?.uid ?: data.fUserInfo?.uid.orEmpty())\n                    else\n                        onOpenLink(data.url.orEmpty(), data.title)\n                },\n                onLongClick = if (appCardType == AppCardType.RECENT) {\n                    {\n                        onHandleRecent?.let {\n                            it(\n                                data.id.orEmpty(),\n                                data.targetId.orEmpty(),\n                                data.targetType.orEmpty(),\n                                data.isTop ?: 0\n                            )\n                        }\n                    }\n                } else null\n            )\n            .padding(10.dp)\n    ) {\n\n        val (logo, appName, commentNum, downCount, active, follow) = createRefs()\n\n        CoilLoader(\n            url = when (appCardType) {\n                AppCardType.APP, AppCardType.PRODUCT, AppCardType.TOPIC, AppCardType.RECENT -> data.logo\n                AppCardType.USER -> data.userAvatar\n                AppCardType.CONTACTS -> data.userInfo?.userAvatar ?: data.fUserInfo?.userAvatar\n            },\n            modifier = Modifier\n                .clip(RoundedCornerShape(8.dp))\n                .aspectRatio(1f)\n                .constrainAs(logo) {\n                    start.linkTo(parent.start)\n                    top.linkTo(appName.top)\n                    bottom.linkTo(commentNum.bottom)\n                    height = Dimension.fillToConstraints\n                },\n        )\n\n        Text(\n            text = when (appCardType) {\n                AppCardType.APP, AppCardType.PRODUCT, AppCardType.TOPIC -> data.title.orEmpty()\n                AppCardType.USER -> data.username.orEmpty()\n                AppCardType.CONTACTS ->\n                    data.userInfo?.username ?: data.fUserInfo?.username.orEmpty()\n\n                AppCardType.RECENT -> \"${data.targetTypeTitle}: ${data.title}\"\n            },\n            modifier = Modifier\n                .padding(start = 10.dp, end = if (appCardType == AppCardType.USER) 10.dp else 0.dp)\n                .constrainAs(appName) {\n                    start.linkTo(logo.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(if (appCardType == AppCardType.USER) follow.start else parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.titleSmall.copy(lineHeight = 18.sp)\n        )\n\n        Text(\n            text = when (appCardType) {\n                AppCardType.APP -> \"${data.commentnum}讨论\"\n                AppCardType.PRODUCT, AppCardType.TOPIC -> \"${data.hotNumTxt}热度\"\n                AppCardType.USER -> \"${data.follow}关注\"\n                AppCardType.CONTACTS -> \"${data.userInfo?.follow ?: data.fUserInfo?.follow}关注\"\n                AppCardType.RECENT -> \"${data.followNum}关注\"\n            },\n            modifier = Modifier\n                .padding(start = 10.dp, top = 5.dp)\n                .constrainAs(commentNum) {\n                    start.linkTo(logo.end)\n                    top.linkTo(appName.bottom)\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.bodyMedium.copy(fontSize = 13.sp, lineHeight = 16.sp),\n            color = MaterialTheme.colorScheme.outline,\n        )\n\n        Text(\n            text = when (appCardType) {\n                AppCardType.APP -> \"${data.downCount}下载\"\n                AppCardType.PRODUCT -> \"${data.feedCommentNumTxt}讨论\"\n                AppCardType.TOPIC -> \"${data.commentnumTxt}讨论\"\n                AppCardType.USER -> \"${data.fans}粉丝\"\n                AppCardType.CONTACTS -> \"${data.userInfo?.fans ?: data.fUserInfo?.fans}粉丝\"\n                AppCardType.RECENT -> when (data.targetType) {\n                    \"user\" -> \"${data.fansNum}粉丝\"\n                    else -> \"${data.commentNum}讨论\" /*\"apk\",\"topic\"*/\n                }\n            },\n            modifier = Modifier\n                .padding(start = 10.dp, top = 5.dp)\n                .constrainAs(downCount) {\n                    start.linkTo(commentNum.end)\n                    top.linkTo(appName.bottom)\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.bodyMedium.copy(fontSize = 13.sp, lineHeight = 16.sp),\n            color = MaterialTheme.colorScheme.outline,\n        )\n\n        if (appCardType in listOf(AppCardType.USER, AppCardType.CONTACTS)) {\n            Text(\n                text = if (appCardType == AppCardType.USER)\n                    \"${fromToday(data.logintime ?: 0)}活跃\"\n                else\n                    \"${fromToday(data.userInfo?.logintime ?: data.fUserInfo?.logintime ?: 0)}活跃\",\n                modifier = Modifier\n                    .padding(start = 10.dp, top = 5.dp)\n                    .constrainAs(active) {\n                        start.linkTo(downCount.end)\n                        top.linkTo(appName.bottom)\n                    },\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis,\n                style = MaterialTheme.typography.bodyMedium.copy(\n                    fontSize = 13.sp,\n                    lineHeight = 16.sp\n                ),\n                color = MaterialTheme.colorScheme.outline,\n            )\n\n            if (appCardType == AppCardType.USER) {\n                TextButton(\n                    onClick = {\n                        onFollowUser?.let {\n                            it(data.uid.orEmpty(), data.isFollow ?: 0)\n                        }\n                    },\n                    modifier = Modifier\n                        .constrainAs(follow) {\n                            top.linkTo(appName.top)\n                            end.linkTo(parent.end)\n                            bottom.linkTo(commentNum.bottom)\n                            height = Dimension.fillToConstraints\n                        }\n                ) {\n                    Text(\n                        text = if (data.isFollow == 1) \"取消关注\" else \"关注\",\n                        color = if (data.isFollow == 1)\n                            MaterialTheme.colorScheme.outline\n                        else\n                            MaterialTheme.colorScheme.primary\n                    )\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/AppInfoCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.util.DateUtils.fromToday\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\n@Composable\nfun AppInfoCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data?,\n    onDownloadApk: () -> Unit,\n) {\n\n    ConstraintLayout(\n        modifier = modifier.fillMaxWidth()\n    ) {\n\n        val (logo, appName, version, size, updateTime, downloadBtn) = createRefs()\n\n        CoilLoader(\n            url = data?.logo,\n            modifier = Modifier\n                .padding(start = 20.dp)\n                .height(80.dp)\n                .width(80.dp)\n                .clip(RoundedCornerShape(18.dp))\n                .constrainAs(logo) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                },\n        )\n\n        Text(\n            text = data?.title.orEmpty(),\n            style = MaterialTheme.typography.bodySmall.copy(\n                fontSize = 16.sp,\n                fontWeight = FontWeight.Bold\n            ),\n            modifier = Modifier\n                .padding(start = 10.dp, top = 20.dp, end = 10.dp)\n                .constrainAs(appName) {\n                    start.linkTo(logo.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                }\n        )\n\n        Text(\n            text = data?.apkversionname?.let { \"版本: ${data.apkversionname}(${data.apkversioncode})\" }\n                ?: EMPTY_STRING,\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n            modifier = Modifier\n                .padding(start = 10.dp, top = 5.dp, end = 10.dp)\n                .constrainAs(version) {\n                    start.linkTo(logo.end)\n                    top.linkTo(appName.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                }\n        )\n\n        Text(\n            text = data?.apksize?.let { \"大小: ${data.apksize}\" } ?: EMPTY_STRING,\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            modifier = Modifier\n                .padding(start = 10.dp, top = 5.dp, end = 10.dp)\n                .constrainAs(size) {\n                    start.linkTo(logo.end)\n                    top.linkTo(version.bottom)\n                    end.linkTo(if (data?.entityType == \"apk\") downloadBtn.start else parent.end)\n                    width = Dimension.fillToConstraints\n                }\n        )\n\n        Text(\n            text = data?.let {\n                \"更新时间: ${if (data.lastupdate != null) fromToday(data.lastupdate) else \"null\"}\"\n            } ?: EMPTY_STRING,\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            modifier = Modifier\n                .padding(start = 10.dp, top = 5.dp, end = 10.dp, bottom = 20.dp)\n                .constrainAs(updateTime) {\n                    start.linkTo(logo.end)\n                    top.linkTo(size.bottom)\n                    end.linkTo(if (data?.entityType == \"apk\") downloadBtn.start else parent.end)\n                    width = Dimension.fillToConstraints\n                }\n        )\n\n        if (data?.entityType == \"apk\") {\n            FilledTonalButton(\n                onClick = {\n                    onDownloadApk()\n                },\n                modifier = Modifier\n                    .padding(end = 10.dp, bottom = 20.dp)\n                    .constrainAs(downloadBtn) {\n                        bottom.linkTo(parent.bottom)\n                        end.linkTo(parent.end)\n                    },\n            ) {\n                Text(text = \"下载\")\n            }\n\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/AppUpdateCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.animation.animateContentSize\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.DateUtils.fromToday\nimport com.example.c001apk.compose.util.Utils.getAppVersion\nimport com.example.c001apk.compose.util.noRippleClickable\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@Composable\nfun AppUpdateCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onViewApp: (String) -> Unit,\n    onDownloadApk: (String, String, String, String, String) -> Unit\n) {\n\n    val context = LocalContext.current\n    var expanded by remember { mutableStateOf(false) }\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n            .clickable {\n                onViewApp(data.packageName.orEmpty())\n            }\n            .padding(10.dp)\n            .animateContentSize()\n    ) {\n\n        val (icon, title, bit, version, time, size, log, download) = createRefs()\n\n        CoilLoader(url = data.logo,\n            modifier = Modifier\n                .size(40.dp)\n                .clip(RoundedCornerShape(8.dp))\n                .aspectRatio(1f)\n                .constrainAs(icon) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                })\n\n        Text(\n            text = data.title.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(title) {\n                    start.linkTo(icon.end)\n                    top.linkTo(parent.top)\n                },\n        )\n\n        Text(\n            text = when (data.pkgBitType) {\n                1 -> \"32位\"\n                2, 3 -> \"64位\"\n                else -> EMPTY_STRING\n            },\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(bit) {\n                    start.linkTo(title.end)\n                    bottom.linkTo(title.bottom)\n                    top.linkTo(title.top)\n                },\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        Text(\n            text = run {\n                if (data.localVersionName.isNullOrEmpty()) {\n                    val ver = getAppVersion(context, data.packageName.orEmpty())\n                    data.localVersionName = ver.first\n                    data.localVersionCode = ver.second\n                }\n                \"${data.localVersionName}(${data.localVersionCode}) > ${data.apkversionname}(${data.apkversioncode})\"\n            },\n            modifier = Modifier\n                .padding(horizontal = 10.dp)\n                .constrainAs(version) {\n                    start.linkTo(icon.end)\n                    top.linkTo(title.bottom)\n                    end.linkTo(download.start)\n                    width = Dimension.fillToConstraints\n                },\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        Text(\n            text = fromToday(data.lastupdate ?: 0),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(time) {\n                    start.linkTo(icon.end)\n                    top.linkTo(version.bottom)\n                },\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        Text(\n            text = data.apksize.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(size) {\n                    start.linkTo(time.end)\n                    top.linkTo(version.bottom)\n                },\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        Text(\n            text = data.changelog.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(log) {\n                    start.linkTo(icon.end)\n                    top.linkTo(time.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                }\n                .noRippleClickable {\n                    expanded = !expanded\n                },\n            style = MaterialTheme.typography.bodyMedium,\n            color = MaterialTheme.colorScheme.outline,\n            maxLines = if (expanded) Int.MAX_VALUE else 2,\n            overflow = TextOverflow.Ellipsis,\n        )\n\n        FilledTonalButton(\n            onClick = {\n                onDownloadApk(\n                    data.packageName.orEmpty(),\n                    data.id.orEmpty(),\n                    data.title.orEmpty(),\n                    data.apkversionname.orEmpty(),\n                    data.apkversioncode.orEmpty()\n                )\n            },\n            modifier = Modifier\n                .constrainAs(download) {\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                }\n        ) {\n            Text(text = \"下载\")\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/CardIndicator.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.animation.animateColorAsState\nimport androidx.compose.animation.core.animateDpAsState\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.pager.PagerState\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.remember\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/5\n */\n@Composable\nfun CardIndicator(\n    modifier: Modifier = Modifier,\n    dimension: Dp = 6.dp,\n    defWidth: Float = 1.0f,\n    selectedWidth: Float = 1.0f,\n    defColor: Color = MaterialTheme.colorScheme.primaryContainer,\n    selectedColor: Color = MaterialTheme.colorScheme.primary,\n    pagerState: PagerState,\n    isCarousel: Boolean = false\n) {\n    val currentPage by remember { derivedStateOf { pagerState.currentPage } }\n    val pageCount = with(pagerState.pageCount) {\n        if (isCarousel) this - 2\n        else this\n    }\n\n    LazyRow(\n        modifier = modifier,\n        horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally),\n    ) {\n        items(pageCount) { i ->\n            val isSelected by remember {\n                derivedStateOf {\n                    with(currentPage) {\n                        if (isCarousel)\n                            when (currentPage) {\n                                0 -> pageCount - 1\n                                pageCount + 1 -> 0\n                                else -> this - 1\n                            }\n                        else this\n                    } == i\n                }\n            }\n            val color by animateColorAsState(\n                targetValue = if (isSelected) selectedColor else defColor,\n                label = \"indicatorColor\"\n            )\n            val width by animateDpAsState(\n                targetValue = if (isSelected) dimension.times(selectedWidth)\n                else dimension.times(defWidth),\n                label = \"indicatorWidth\"\n            )\n\n            Box(\n                modifier = Modifier\n                    .size(height = dimension, width = width)\n                    .clip(CircleShape)\n                    .background(color = color)\n                    //.clickable { scope.launch { pagerState.animateScrollToPage(i) } }\n            )\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/CarouselCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport kotlinx.coroutines.delay\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\n@Composable\nfun CarouselCard(\n    modifier: Modifier = Modifier,\n    entities: List<HomeFeedResponse.Entities>?,\n    onOpenLink: (String, String) -> Unit\n) {\n\n    entities?.let {\n        Box {\n            val dataList = it.toMutableList()\n            if (it.size > 1) {\n                dataList.add(0, it.last())\n                dataList.add(dataList[1])\n            }\n\n            val pagerState =\n                rememberPagerState(initialPage = if (it.size > 1) 1 else 0) { dataList.size }\n\n            if (it.size > 1) {\n                LaunchedEffect(pagerState.currentPage) {\n                    if (pagerState.currentPage == 0) {\n                        delay(500)\n                        pagerState.scrollToPage(pagerState.pageCount - 2)\n                    } else if (pagerState.currentPage == pagerState.pageCount - 1) {\n                        delay(500)\n                        pagerState.scrollToPage(1)\n                    }\n                }\n            }\n\n            HorizontalPager(\n                modifier = modifier\n                    .fillMaxWidth()\n                    .wrapContentHeight()\n                    .clip(MaterialTheme.shapes.medium)\n                    .background(cardBg()),\n                state = pagerState,\n                beyondViewportPageCount = if (it.size > 1) it.size else 0,\n            ) { index ->\n                val item = dataList[index]\n                CoilLoader(\n                    url = item.pic,\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .aspectRatio(3f)\n                        .clickable {\n                            onOpenLink(\n                                item.url.orEmpty(),\n                                item.title\n                                    ?.replace(\"_首页轮播\", EMPTY_STRING)\n                                    .orEmpty()\n                            )\n                        },\n                )\n            }\n\n            if (pagerState.pageCount > 1) {\n                CardIndicator(\n                    modifier = Modifier\n                        .align(Alignment.BottomEnd)\n                        .padding(bottom = 5.dp, end = 20.dp),\n                    pagerState = pagerState,\n                    isCarousel = true,\n                    defColor = Color.White.copy(alpha = 0.5f),\n                    selectedColor = Color.White,\n                )\n            }\n\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/ChatLeftCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.widthIn\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.constant.Constants.PREFIX_HTTP\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.ImageView\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.ImageShowUtil\nimport com.example.c001apk.compose.util.longClick\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/19\n */\n@Composable\nfun ChatLeftCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onGetImageUrl: (String) -> Unit,\n    onLongClick: (String, String, String) -> Unit,\n    onViewUser: (String) -> Unit,\n    onClearFocus: () -> Unit,\n) {\n\n    BoxWithConstraints {\n\n        val maxWidth = maxWidth - 142.dp\n\n        Row(\n            modifier = modifier\n                .fillMaxWidth()\n                .longClick {\n                    onLongClick(\n                        data.id.orEmpty(),\n                        data.message.orEmpty(),\n                        data.messagePic.orEmpty()\n                    )\n                }\n                .padding(horizontal = 16.dp, vertical = 12.dp),\n        ) {\n\n            CoilLoader(\n                modifier = Modifier\n                    .clip(CircleShape)\n                    .size(40.dp)\n                    .clickable { onViewUser(data.messageUid.orEmpty()) },\n                url = data.fromUserAvatar\n            )\n\n            if (!data.message.isNullOrEmpty()) {\n                LinkText(\n                    text = data.message,\n                    modifier = Modifier\n                        .widthIn(max = maxWidth)\n                        .padding(start = 10.dp)\n                        .clip(\n                            RoundedCornerShape(\n                                topStart = 4.dp,\n                                topEnd = 12.dp,\n                                bottomStart = 12.dp,\n                                bottomEnd = 12.dp\n                            )\n                        )\n                        .background(cardBg())\n                        .padding(horizontal = 10.dp, vertical = 12.dp)\n                )\n            }\n\n            if (!data.messagePic.isNullOrEmpty()) {\n                if (!data.messagePic.startsWith(PREFIX_HTTP)) {\n                    onGetImageUrl(data.id.orEmpty())\n                }\n                val imageLp by lazy {\n                    ImageShowUtil.getImageLp(\n                        if (data.messagePic.startsWith(PREFIX_HTTP))\n                            data.messagePic.substring(0, data.messagePic.indexOfFirst { it == '?' })\n                        else data.messagePic\n                    )\n                }\n                val imageWidth by lazy { maxWidth / 2f }\n                val imageHeight by lazy { imageWidth * imageLp.second / imageLp.first }\n                ImageView(\n                    url = data.messagePic,\n                    isChat = true,\n                    modifier = Modifier\n                        .padding(start = 10.dp)\n                        .clip(MaterialTheme.shapes.medium)\n                        .background(cardBg())\n                        .width(maxWidth / 2f)\n                        .height(imageHeight),\n                    onClearFocus = onClearFocus,\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/ChatRightCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.widthIn\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.constant.Constants.PREFIX_HTTP\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.ImageView\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.util.ImageShowUtil\nimport com.example.c001apk.compose.util.longClick\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/19\n */\n@Composable\nfun ChatRightCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onGetImageUrl: (String) -> Unit,\n    onLongClick: (String, String, String) -> Unit,\n    onViewUser: (String) -> Unit,\n    onClearFocus: () -> Unit,\n) {\n\n    BoxWithConstraints {\n\n        val maxWidth = maxWidth - 142.dp\n\n        Row(\n            modifier = modifier\n                .fillMaxWidth()\n                .longClick {\n                    onLongClick(\n                        data.id.orEmpty(),\n                        data.message.orEmpty(),\n                        data.messagePic.orEmpty()\n                    )\n                }\n                .padding(horizontal = 16.dp, vertical = 12.dp),\n            horizontalArrangement = Arrangement.End\n        ) {\n\n            if (!data.messagePic.isNullOrEmpty()) {\n                if (!data.messagePic.startsWith(PREFIX_HTTP)) {\n                    onGetImageUrl(data.id.orEmpty())\n                }\n                val imageLp by lazy {\n                    ImageShowUtil.getImageLp(\n                        if (data.messagePic.startsWith(PREFIX_HTTP))\n                            data.messagePic.substring(0, data.messagePic.indexOfFirst { it == '?' })\n                        else data.messagePic\n                    )\n                }\n                val imageWidth by lazy { maxWidth / 2f }\n                val imageHeight by lazy { imageWidth * imageLp.second / imageLp.first }\n                ImageView(\n                    url = data.messagePic,\n                    isChat = true,\n                    modifier = Modifier\n                        .padding(end = 10.dp)\n                        .clip(MaterialTheme.shapes.medium)\n                        .background(MaterialTheme.colorScheme.primaryContainer)\n                        .width(maxWidth / 2f)\n                        .height(imageHeight),\n                    onClearFocus = onClearFocus,\n                )\n\n            }\n\n            if (!data.message.isNullOrEmpty()) {\n                LinkText(\n                    text = data.message,\n                    modifier = Modifier\n                        .widthIn(max = maxWidth)\n                        .padding(end = 10.dp)\n                        .clip(\n                            RoundedCornerShape(\n                                topStart = 12.dp,\n                                topEnd = 4.dp,\n                                bottomStart = 12.dp,\n                                bottomEnd = 12.dp\n                            )\n                        )\n                        .background(MaterialTheme.colorScheme.primaryContainer)\n                        .padding(horizontal = 10.dp, vertical = 12.dp)\n                )\n            }\n\n            CoilLoader(\n                modifier = Modifier\n                    .clip(CircleShape)\n                    .background(MaterialTheme.colorScheme.primary)\n                    .size(40.dp)\n                    .clickable { onViewUser(data.fromuid.orEmpty()) },\n                url = data.fromUserAvatar\n            )\n\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/ChatTimeCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/19\n */\n@Composable\nfun ChatTimeCard(\n    modifier: Modifier = Modifier,\n    title: String\n) {\n\n    Text(\n        text = title,\n        modifier = modifier\n            .fillMaxWidth()\n            .padding(12.dp),\n        color = MaterialTheme.colorScheme.outline,\n        style = MaterialTheme.typography.bodySmall,\n        textAlign = TextAlign.Center,\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/CollectionCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.ffflist.FFFListType\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/15\n */\n@Composable\nfun CollectionCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onViewFFFList: ((String?, String, String?, String?) -> Unit)? = null,\n) {\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n            .clickable {\n                onViewFFFList?.let {\n                    it(\n                        null,\n                        FFFListType.COLLECTION_ITEM.name,\n                        data.id.orEmpty(),\n                        data.title.orEmpty()\n                    )\n                }\n            }\n            .padding(10.dp)\n    ) {\n        val (cover, name, type, follow, size) = createRefs()\n\n        CoilLoader(\n            url = data.coverPic,\n            modifier = Modifier\n                .clip(RoundedCornerShape(8.dp))\n                .aspectRatio(4f / 3f)\n                .constrainAs(cover) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                    height = Dimension.fillToConstraints\n                })\n\n        Text(\n            text = data.title.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(name) {\n                    start.linkTo(cover.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.titleSmall\n        )\n\n        Text(\n            text = data.isOpenTitle.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(type) {\n                    start.linkTo(cover.end)\n                    top.linkTo(name.bottom)\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.bodyMedium\n        )\n\n        Text(\n            text = \"${data.followNum}人关注\",\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(follow) {\n                    start.linkTo(type.end)\n                    top.linkTo(name.bottom)\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.bodyMedium\n        )\n\n        Text(\n            text = \"${data.itemNum}个内容\",\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(size) {\n                    start.linkTo(follow.end)\n                    top.linkTo(name.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.bodyMedium\n        )\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/FeedArticleCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.logic.model.FeedArticleContentBean\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.component.NineImageView\nimport com.example.c001apk.compose.util.longClick\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/8\n */\n@Composable\nfun FeedArticleCard(\n    item: FeedArticleContentBean,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String) -> Unit,\n) {\n\n    when (item.type) {\n        \"text\" -> {\n            LinkText(\n                text = item.message.orEmpty(),\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .longClick {\n                        onCopyText(item.message.orEmpty())\n                    }\n                    .padding(horizontal = 16.dp)\n                    .padding(top = 12.dp),\n                lineSpacingMultiplier = 1.3f,\n                textSize = 16f,\n                onOpenLink = onOpenLink\n            )\n        }\n\n        \"image\" -> {\n            Column(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 16.dp)\n                    .padding(top = 12.dp)\n            ) {\n                NineImageView(\n                    pic = null,\n                    picArr = listOf(item.url.orEmpty()),\n                    feedType = null,\n                    isSingle = true\n                )\n                if (!item.description.isNullOrEmpty()) {\n                    Text(\n                        text = item.description,\n                        style = MaterialTheme.typography.titleSmall.copy(fontSize = 13.sp),\n                        color = MaterialTheme.colorScheme.outline,\n                        modifier = Modifier\n                            .padding(top = 4.dp)\n                            .align(Alignment.CenterHorizontally),\n                    )\n                }\n            }\n        }\n\n        \"shareUrl\" -> {\n            Text(\n                text = item.title.orEmpty(),\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 16.dp)\n                    .padding(top = 12.dp)\n                    .clip(MaterialTheme.shapes.medium)\n                    .background(MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp))\n                    .clickable {\n                        onOpenLink(item.url.orEmpty(), item.title)\n                    }\n                    .padding(10.dp),\n                style = MaterialTheme.typography.titleMedium.copy(fontSize = 14.sp)\n            )\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/FeedCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.border\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.Message\nimport androidx.compose.material.icons.filled.ExpandMore\nimport androidx.compose.material.icons.filled.Link\nimport androidx.compose.material.icons.filled.Smartphone\nimport androidx.compose.material.icons.filled.ThumbUpAlt\nimport androidx.compose.material.icons.filled.ThumbUpOffAlt\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.base.LikeType\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.IconText\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.component.NineImageView\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.DateUtils.fromToday\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.ShareType\nimport com.example.c001apk.compose.util.Utils.richToString\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.getShareText\nimport com.example.c001apk.compose.util.longClick\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun FeedCard(\n    modifier: Modifier = Modifier,\n    isFeedContent: Boolean,\n    isFeedTop: Boolean = false,\n    data: HomeFeedResponse.Data,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    onLike: ((String, Int, LikeType) -> Unit)? = null,\n    onDelete: ((String, LikeType, String?) -> Unit)? = null,\n    onBlockUser: (String) -> Unit,\n) {\n    val horizontal = if (isFeedContent) 16.dp else 10.dp\n    // val vertical = if (isFeedContent) 12.dp else 10.dp\n    Column(\n        modifier = run {\n            val tmp = modifier\n                .fillMaxWidth()\n                .wrapContentHeight()\n                .clip(\n                    if (isFeedContent) RectangleShape\n                    else RoundedCornerShape(12.0.dp)\n                )\n                .background(\n                    if (isFeedContent) MaterialTheme.colorScheme.surface\n                    else cardBg()\n                )\n            if (isFeedContent)\n                tmp.longClick {\n                    onCopyText(data.message)\n                }\n            else\n                tmp.combinedClickable(\n                    onClick = {\n                        onViewFeed(data.id.orEmpty(), false)\n                    },\n                    onLongClick = {\n                        onCopyText(data.message)\n                    }\n                )\n        }\n    ) {\n        if (!isFeedContent) {\n            FeedHeader(\n                modifier = Modifier.padding(start = horizontal),\n                data = data,\n                onViewUser = onViewUser,\n                isFeedContent = false,\n                onReport = onReport,\n                isFeedTop = isFeedTop,\n                onDelete = onDelete,\n                onBlockUser = onBlockUser,\n            )\n        }\n        FeedMessage(\n            modifier = Modifier\n                .padding(horizontal = horizontal)\n                .fillMaxWidth(),\n            data = data,\n            onOpenLink = onOpenLink,\n            isFeedContent = isFeedContent,\n            onViewFeed = onViewFeed,\n            onCopyText = onCopyText,\n        )\n        FeedBottomInfo(\n            modifier = Modifier\n                .padding(horizontal = horizontal)\n                .padding(bottom = if (data.targetRow == null && data.relationRows.isNullOrEmpty()) 12.dp else 0.dp),\n            isFeedContent = isFeedContent,\n            ip = data.ipLocation.orEmpty(),\n            dateline = data.dateline ?: 0,\n            replyNum = data.replynum.orEmpty(),\n            likeNum = data.likenum.orEmpty(),\n            onViewFeed = {\n                onViewFeed(data.id.orEmpty(), true)\n            },\n            onLike = {\n                if (isLogin) {\n                    onLike?.let {\n                        it(data.id.orEmpty(), data.userAction?.like ?: 0, LikeType.FEED)\n                    }\n                }\n            },\n            like = data.userAction?.like\n        )\n        FeedRows(\n            modifier = Modifier.padding(bottom = 10.dp),\n            isFeedContent = isFeedContent,\n            relationRows = data.relationRows,\n            targetRow = data.targetRow,\n            onOpenLink = onOpenLink\n        )\n    }\n}\n\n@Composable\nfun FeedRows(\n    modifier: Modifier = Modifier,\n    isFeedContent: Boolean,\n    relationRows: List<HomeFeedResponse.RelationRows>?,\n    targetRow: HomeFeedResponse.TargetRow?,\n    onOpenLink: (String, String?) -> Unit\n) {\n    val dataList = relationRows?.toMutableList() ?: ArrayList()\n    targetRow?.let {\n        dataList.add(\n            0,\n            HomeFeedResponse.RelationRows(\n                it.id.orEmpty(),\n                it.logo,\n                it.title,\n                it.url,\n                it.targetType.toString()\n            )\n        )\n    }\n\n    if (dataList.isNotEmpty()) {\n        LazyRow(\n            modifier = modifier\n                .fillMaxWidth()\n                .padding(top = 10.dp),\n            horizontalArrangement = Arrangement.spacedBy(10.dp),\n            contentPadding = PaddingValues(horizontal = if (isFeedContent) 16.dp else 10.dp)\n        ) {\n            dataList.forEach {\n                item(key = it.url) {\n                    IconMiniScrollCardItem(\n                        isFeedContent = isFeedContent,\n                        logoUrl = it.logo.orEmpty(),\n                        linkUrl = it.url.orEmpty(),\n                        titleText = it.title.orEmpty(),\n                        onOpenLink = onOpenLink\n                    )\n                }\n            }\n        }\n    }\n\n}\n\n@Composable\nfun FeedBottomInfo(\n    modifier: Modifier = Modifier,\n    isFeedContent: Boolean,\n    ip: String,\n    dateline: Long,\n    replyNum: String,\n    likeNum: String,\n    onViewFeed: () -> Unit,\n    onLike: () -> Unit,\n    like: Int?,\n) {\n\n    Row(\n        modifier = modifier\n            .fillMaxWidth()\n            .padding(top = 10.dp)\n    ) {\n        Text(\n            modifier = Modifier.weight(1f),\n            text = if (isFeedContent) {\n                if (ip.isNotEmpty()) \"发布于 $ip\"\n                else EMPTY_STRING\n            } else fromToday(dateline),\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        IconText(\n            imageVector = Icons.AutoMirrored.Outlined.Message,\n            title = replyNum,\n            onClick = onViewFeed,\n        )\n\n        IconText(\n            modifier = Modifier.padding(start = 10.dp),\n            imageVector = if (like == 1) Icons.Filled.ThumbUpAlt\n            else Icons.Default.ThumbUpOffAlt,\n            title = likeNum,\n            onClick = onLike,\n            isLike = like == 1,\n        )\n    }\n\n}\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun FeedMessage(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit,\n    isFeedContent: Boolean,\n    onViewFeed: (String, Boolean) -> Unit,\n    onCopyText: (String?) -> Unit,\n) {\n\n    if (!data.messageTitle.isNullOrEmpty()) {\n        LinkText(\n            text = data.messageTitle,\n            textSize = 16f,\n            isBold = true,\n            lineSpacingMultiplier = 1.3f,\n            modifier = modifier.padding(top = 10.dp),\n            onOpenLink = onOpenLink\n        )\n    }\n    if (!data.message.isNullOrEmpty()) {\n        LinkText(\n            text = data.message,\n            textSize = 16f,\n            lineSpacingMultiplier = 1.3f,\n            modifier = modifier.padding(top = 10.dp),\n            onOpenLink = onOpenLink\n        )\n    }\n\n    if (!data.picArr.isNullOrEmpty()) {\n        NineImageView(\n            modifier = modifier.padding(top = 10.dp),\n            pic = data.pic,\n            picArr = data.picArr,\n            feedType = data.feedType\n        )\n    }\n\n    if (!data.forwardSourceType.isNullOrEmpty()) {\n        if (data.forwardSourceFeed == null) {\n            Text(\n                text = \"动态已被删除\",\n                style = MaterialTheme.typography.titleSmall,\n                color = MaterialTheme.colorScheme.outline,\n                modifier = modifier\n                    .padding(top = 10.dp)\n                    .clip(MaterialTheme.shapes.medium)\n                    .background(\n                        if (isFeedContent)\n                            cardBg()\n                        else\n                            MaterialTheme.colorScheme.surface\n                    )\n                    .padding(10.dp)\n            )\n        } else {\n            Column(\n                modifier = modifier\n                    .padding(top = 10.dp)\n                    .clip(MaterialTheme.shapes.medium)\n                    .background(\n                        if (isFeedContent)\n                            cardBg()\n                        else\n                            MaterialTheme.colorScheme.surface\n                    )\n                    .combinedClickable(\n                        onClick = {\n                            onOpenLink(data.forwardSourceFeed.url.orEmpty(), null)\n                        },\n                        onLongClick = {\n                            onCopyText(data.forwardSourceFeed.message)\n                        }\n                    )\n                    .padding(10.dp)\n            ) {\n                if (!data.forwardSourceFeed.messageTitle.isNullOrEmpty()) {\n                    LinkText(\n                        text = \"\"\"<a class=\"feed-link-uname\" href=\"/u/${data.forwardSourceFeed.uid}\">@${data.forwardSourceFeed.username}</a>: ${data.forwardSourceFeed.messageTitle}\"\"\",\n                        onOpenLink = onOpenLink,\n                        lineSpacingMultiplier = 1.2f,\n                    )\n                    if (!data.forwardSourceFeed.message.isNullOrEmpty())\n                        LinkText(\n                            text = data.forwardSourceFeed.message,\n                            onOpenLink = onOpenLink,\n                            lineSpacingMultiplier = 1.2f,\n                        )\n                } else {\n                    LinkText(\n                        text = \"\"\"<a class=\"feed-link-uname\" href=\"/u/${data.forwardSourceFeed.uid}\">@${data.forwardSourceFeed.username}</a>: ${data.forwardSourceFeed.message}\"\"\",\n                        onOpenLink = onOpenLink,\n                        lineSpacingMultiplier = 1.2f,\n                    )\n                }\n                if (!data.forwardSourceFeed.picArr.isNullOrEmpty()) {\n                    NineImageView(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(top = 10.dp),\n                        pic = data.forwardSourceFeed.pic,\n                        picArr = data.forwardSourceFeed.picArr,\n                        feedType = data.forwardSourceFeed.feedType\n                    )\n                }\n            }\n        }\n    }\n\n    if (!data.replyRows.isNullOrEmpty()) {\n        val reply = data.replyRows?.getOrNull(0)\n        val replyPic = when (reply?.pic) {\n            EMPTY_STRING -> EMPTY_STRING\n            else -> \"\"\" <a class=\\\"feed-forward-pic\\\" href=${reply?.pic}>查看图片(${reply?.picArr?.size})</a>\"\"\"\n        }\n        LinkText(\n            text = \"\"\"<a class=\"feed-link-uname\" href=\"/u/${reply?.uid}\">${reply?.username}</a>: ${reply?.message}$replyPic\"\"\",\n            onOpenLink = onOpenLink,\n            lineSpacingMultiplier = 1.2f,\n            imgList = reply?.picArr,\n            textSize = 14f,\n            modifier = modifier\n                .padding(top = 10.dp)\n                .fillMaxWidth()\n                .clip(MaterialTheme.shapes.medium)\n                .background(MaterialTheme.colorScheme.surface)\n                .combinedClickable(\n                    onClick = {\n                        onViewFeed(data.id.orEmpty(), true)\n                    },\n                    onLongClick = {\n                        onCopyText(reply?.message)\n                    }\n                )\n                .padding(10.dp)\n        )\n    }\n\n    if (!data.extraUrl.isNullOrEmpty()) {\n        ConstraintLayout(\n            modifier = modifier\n                .padding(top = 10.dp)\n                .fillMaxWidth()\n                .clip(MaterialTheme.shapes.medium)\n                .background(\n                    if (isFeedContent)\n                        cardBg()\n                    else\n                        MaterialTheme.colorScheme.surface\n                )\n                .clickable {\n                    onOpenLink(data.extraUrl, data.extraTitle)\n                }\n                .padding(10.dp)\n        ) {\n            val (pic, title, url) = createRefs()\n\n            if (!data.extraPic.isNullOrEmpty()) {\n                CoilLoader(\n                    url = data.extraPic,\n                    modifier = Modifier\n                        .aspectRatio(1f)\n                        .clip(RoundedCornerShape(8.dp))\n                        .constrainAs(pic) {\n                            top.linkTo(parent.top)\n                            bottom.linkTo(parent.bottom)\n                            start.linkTo(parent.start)\n                            height = Dimension.fillToConstraints\n                        })\n            } else {\n                Box(\n                    modifier = Modifier\n                        .aspectRatio(1f)\n                        .clip(RoundedCornerShape(8.dp))\n                        .constrainAs(pic) {\n                            top.linkTo(parent.top)\n                            bottom.linkTo(parent.bottom)\n                            start.linkTo(parent.start)\n                            height = Dimension.fillToConstraints\n                        }\n                        .background(MaterialTheme.colorScheme.primary),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Image(\n                        imageVector = Icons.Default.Link,\n                        contentDescription = null,\n                        colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onPrimary)\n                    )\n                }\n            }\n\n            if (!data.extraTitle.isNullOrEmpty()) {\n                Text(\n                    text = data.extraTitle,\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis,\n                    style = MaterialTheme.typography.titleSmall.copy(fontSize = 13.sp),\n                    modifier = Modifier\n                        .padding(start = 10.dp)\n                        .constrainAs(title) {\n                            start.linkTo(pic.end)\n                            top.linkTo(parent.top)\n                            end.linkTo(parent.end)\n                            width = Dimension.fillToConstraints\n                        }\n                )\n            }\n\n            Text(\n                text = data.extraUrl,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis,\n                style = MaterialTheme.typography.titleSmall.copy(fontSize = 13.sp),\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .constrainAs(url) {\n                        start.linkTo(pic.end)\n                        top.linkTo(if (!data.extraTitle.isNullOrEmpty()) title.bottom else parent.top)\n                        end.linkTo(parent.end)\n                        width = Dimension.fillToConstraints\n                    }\n            )\n        }\n    }\n\n    /*if (!data.picArr.isNullOrEmpty()) {\n\n        val imageWidth = (minOf(screenWidth, screenHeight) - 46 * density) / 3f / density\n\n        val column = when (data.picArr.size) {\n            in 1..3 -> 1\n            in 4..6 -> 2\n            in 7..9 -> 3\n            else -> 0\n        }\n\n        val context = LocalContext.current\n        LazyVerticalGrid(\n            modifier = Modifier\n                .padding(top = 10.dp)\n                .height((column * imageWidth).dp + 3.dp * (column - 1)),\n            columns = GridCells.Fixed(3)\n        ) {\n            itemsIndexed(data.picArr) { index, item ->\n\n                val startPadding = if (index % 3 == 0) 0.dp else 3.dp\n                val topPadding = if (index > 2) 3.dp else 0.dp\n\n                AsyncImage(\n                    model = ImageRequest.Builder(context)\n                        .data(\"$item$SUFFIX_THUMBNAIL\")\n                        .crossfade(true)\n                        .build(),\n                    contentDescription = null,\n                    contentScale = ContentScale.Crop,\n                    modifier = Modifier\n                        .padding(start = startPadding, top = topPadding)\n                        .clip(MaterialTheme.shapes.medium)\n                        .size(imageWidth.dp)\n                )\n            }\n        }\n    }*/\n}\n\n@Composable\nfun FeedHeader(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onViewUser: (String) -> Unit,\n    isFeedContent: Boolean,\n    isFeedTop: Boolean,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    onDelete: ((String, LikeType, String?) -> Unit)? = null,\n    onBlockUser: ((String) -> Unit)? = null,\n) {\n\n    val vertical = if (isFeedContent) 12.dp else 10.dp\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    val context = LocalContext.current\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .wrapContentHeight()\n    ) {\n        val (avatar, username, from, device, expand, stickTop) = createRefs()\n\n        CoilLoader(\n            url = data.userAvatar,\n            modifier = Modifier\n                .padding(top = vertical)\n                .clip(CircleShape)\n                .aspectRatio(1f, false)\n                .constrainAs(avatar) {\n                    top.linkTo(username.top)\n                    bottom.linkTo(device.bottom)\n                    start.linkTo(parent.start)\n                    height = Dimension.fillToConstraints\n                }\n                .clickable {\n                    onViewUser(data.uid.orEmpty())\n                },\n        )\n\n        Text(\n            modifier = Modifier\n                .padding(\n                    start = 10.dp,\n                    top = vertical,\n                    end = if (isFeedTop) 0.dp else if (!isFeedContent) 10.dp else 16.dp\n                )\n                .constrainAs(username) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(\n                        if (isFeedContent) parent.end\n                        else if (data.isStickTop == 1) stickTop.start\n                        else expand.start\n                    )\n                    width = Dimension.fillToConstraints\n                },\n            text = data.username.orEmpty(),\n            style = MaterialTheme.typography.titleSmall,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n\n        if (!isFeedContent && data.isStickTop == 1) {\n            Text(\n                text = \"置顶\",\n                modifier = Modifier\n                    .padding(top = 10.dp)\n                    .clip(RoundedCornerShape(4.dp))\n                    .background(MaterialTheme.colorScheme.surface)\n                    .border(1.dp, MaterialTheme.colorScheme.primary, RoundedCornerShape(4.dp))\n                    .constrainAs(stickTop) {\n                        top.linkTo(username.top)\n                        bottom.linkTo(username.bottom)\n                        end.linkTo(expand.start)\n                    }\n                    .padding(horizontal = 6.dp),\n                color = MaterialTheme.colorScheme.primary,\n                style = MaterialTheme.typography.titleSmall.copy(fontSize = 10.sp)\n            )\n        }\n\n        if (isFeedContent || !data.infoHtml.isNullOrEmpty()) {\n            Text(\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .constrainAs(from) {\n                        start.linkTo(avatar.end)\n                        top.linkTo(username.bottom)\n                    },\n                text = if (isFeedContent) fromToday(\n                    data.dateline ?: 0\n                ) else data.infoHtml.orEmpty().richToString(),\n                style = MaterialTheme.typography.bodySmall.copy(fontSize = 13.sp),\n                color = MaterialTheme.colorScheme.outline,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n        }\n\n\n        IconText(\n            modifier = Modifier\n                .padding(\n                    start = 10.dp,\n                    end = if (isFeedTop) 0.dp else if (!isFeedContent) 10.dp else 16.dp\n                )\n                .constrainAs(device) {\n                    start.linkTo(if (isFeedContent || !data.infoHtml.isNullOrEmpty()) from.end else avatar.end)\n                    if (isFeedContent || !data.infoHtml.isNullOrEmpty())\n                        bottom.linkTo(from.bottom)\n                    else\n                        top.linkTo(username.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            imageVector = Icons.Default.Smartphone,\n            title = data.deviceTitle?.richToString().orEmpty(),\n            textSize = 13f,\n        )\n\n        if (!isFeedContent) {\n            Box(\n                modifier = Modifier\n                    .aspectRatio(1f, false)\n                    .constrainAs(expand) {\n                        top.linkTo(parent.top)\n                        end.linkTo(parent.end)\n                        bottom.linkTo(device.bottom)\n                        height = Dimension.fillToConstraints\n                    }) {\n\n                IconButton(\n                    onClick = {\n                        dropdownMenuExpanded = true\n                    }\n                ) {\n                    Icon(\n                        imageVector = Icons.Default.ExpandMore,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.outline\n                    )\n                }\n\n                DropdownMenu(\n                    expanded = dropdownMenuExpanded,\n                    onDismissRequest = {\n                        dropdownMenuExpanded = false\n                    },\n                ) {\n                    listOf(\"Copy\", \"Block\").forEachIndexed { index, menu ->\n                        DropdownMenuItem(\n                            text = { Text(menu) },\n                            onClick = {\n                                dropdownMenuExpanded = false\n                                when (index) {\n                                    0 -> context.copyText(\n                                        getShareText(ShareType.FEED, data.id.orEmpty())\n                                    )\n\n                                    1 -> onBlockUser?.let { it(data.uid.orEmpty()) }\n                                }\n                            }\n                        )\n                    }\n                    if (isLogin) {\n                        DropdownMenuItem(\n                            text = { Text(\"Report\") },\n                            onClick = {\n                                dropdownMenuExpanded = false\n                                onReport?.let { it(data.id.orEmpty(), ReportType.FEED) }\n                            }\n                        )\n                    }\n                    if (data.uid == CookieUtil.uid) {\n                        DropdownMenuItem(\n                            text = { Text(\"Delete\") },\n                            onClick = {\n                                dropdownMenuExpanded = false\n                                onDelete?.let { it(data.id.orEmpty(), LikeType.FEED, null) }\n                            }\n                        )\n                    }\n                }\n\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/FeedReplyCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.Message\nimport androidx.compose.material.icons.filled.ExpandMore\nimport androidx.compose.material.icons.filled.ThumbUpAlt\nimport androidx.compose.material.icons.filled.ThumbUpOffAlt\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.base.LikeType\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.IconText\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.component.NineImageView\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.DateUtils.fromToday\nimport com.example.c001apk.compose.util.ReportType\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun FeedReplyCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onViewUser: (String) -> Unit,\n    onShowTotalReply: ((String, String, String?) -> Unit)? = null, // rid, uid, frid?\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    isTotalReply: Boolean = false,\n    isTopReply: Boolean = false,\n    isReply2Reply: Boolean = false,\n    onLike: ((String, Int, LikeType) -> Unit)? = null,\n    onDelete: ((String, LikeType, String?) -> Unit)? = null,\n    onBlockUser: (String, String?) -> Unit,\n    onReply: ((String, String, String, String?) -> Unit)? = null, // rid, uid/fuid, uname, frid?\n) {\n\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n\n    val isFeedReply by lazy { data.fetchType == \"feed_reply\" }\n    val isLikeReply by lazy { data.likeUserInfo != null }\n    val horizontal by lazy { if (isFeedReply) 16.dp else 10.dp }\n    val vertical by lazy { if (isFeedReply) 12.dp else 10.dp }\n\n    ConstraintLayout(\n        modifier = modifier\n            .padding(horizontal = if (isFeedReply) 0.dp else 10.dp)\n            .fillMaxWidth()\n            .clip(if (isFeedReply) RectangleShape else MaterialTheme.shapes.medium)\n            .background(\n                if ((isTotalReply && !isTopReply) || !isFeedReply)\n                    cardBg()\n                else\n                    Color.Transparent\n            )\n            .combinedClickable(\n                onClick = {\n                    if (isLikeReply) {\n                        // no action\n                    } else if (isFeedReply) {\n                        if (isLogin) {\n                            onReply?.let {\n                                it(\n                                    data.id.orEmpty(),\n                                    data.uid.orEmpty(),\n                                    data.userInfo?.username.orEmpty(),\n                                    null\n                                )\n                            }\n                        }\n                    } else {\n                        onOpenLink(data.url.orEmpty(), null)\n                    }\n                },\n                onLongClick = {\n                    onCopyText(data.message)\n                }\n            )\n            .padding(start = horizontal, bottom = vertical)\n    ) {\n\n        val (avatar, username, expand, message, image, dateLine, reply, like, replyRows, likeReply, feed) = createRefs()\n\n        CoilLoader(\n            url = data.likeUserInfo?.userAvatar ?: data.userAvatar,\n            modifier = Modifier\n                .padding(top = vertical)\n                .size(30.dp)\n                .clip(CircleShape)\n                .constrainAs(avatar) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                }\n                .clickable {\n                    onViewUser(data.likeUserInfo?.uid ?: data.uid.orEmpty())\n                }\n        )\n\n        LinkText(\n            text = data.likeUserInfo?.username ?: data.username.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp, top = vertical)\n                .constrainAs(username) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(avatar.top)\n                    end.linkTo(if (!isLikeReply) expand.start else dateLine.start)\n                    width = Dimension.fillToConstraints\n                    if (isLikeReply || data.feed != null)\n                        bottom.linkTo(avatar.bottom)\n                },\n            maxLines = if (!isFeedReply) 1 else if (data.replyRows == null) null else 1,\n            onOpenLink = onOpenLink,\n        )\n\n        LinkText(\n            text = if (isReply2Reply)\n                data.message?.substring(data.message.indexOfFirst { it == ':' } + 1)\n            else if (isLikeReply) \"赞了你的${data.infoHtml}\"\n            else if (!isFeedReply) {\n                if (data.ruid == \"0\") data.message.orEmpty()\n                else \"\"\"回复<a class=\"feed-link-uname\" href=\"/u/${data.ruid}\">${data.rusername}</a>: ${data.message}\"\"\"\n            } else data.message.orEmpty(),\n            modifier = Modifier\n                .padding(\n                    start = if (!isLikeReply && data.feed == null) 10.dp else 0.dp,\n                    top = if (!isLikeReply && data.feed == null) 5.dp else 10.dp,\n                    end = horizontal\n                )\n                .constrainAs(message) {\n                    start.linkTo(if (!isLikeReply && data.feed == null) avatar.end else parent.start)\n                    top.linkTo(if (!isLikeReply && data.feed == null) username.bottom else avatar.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            lineSpacingMultiplier = 1.2f,\n            onOpenLink = onOpenLink,\n        )\n\n        Text(\n            text = fromToday(if (isLikeReply) (data.likeTime ?: 0) else (data.dateline ?: 0)),\n            modifier = Modifier\n                .padding(\n                    start = if (isLikeReply || data.feed != null) 0.dp else 10.dp,\n                    top = 10.dp,\n                    end = if (isLikeReply) 10.dp else 0.dp\n                )\n                .constrainAs(dateLine) {\n                    if (!isLikeReply && data.feed != null)\n                        start.linkTo(parent.start)\n                    else if (isFeedReply)\n                        start.linkTo(avatar.end)\n                    top.linkTo(\n                        if (isLikeReply) parent.top\n                        else {\n                            if (data.feed != null)\n                                feed.bottom\n                            else if (!data.picArr.isNullOrEmpty())\n                                image.bottom\n                            else\n                                message.bottom\n                        }\n                    )\n                    if (isLikeReply)\n                        end.linkTo(parent.end)\n                },\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        if (isLikeReply) {\n            ConstraintLayout(\n                modifier = Modifier\n                    .padding(top = 10.dp, end = 10.dp)\n                    .clip(MaterialTheme.shapes.medium)\n                    .background(MaterialTheme.colorScheme.surface)\n                    .constrainAs(likeReply) {\n                        start.linkTo(parent.start)\n                        top.linkTo(message.bottom)\n                        end.linkTo(parent.end)\n                        width = Dimension.fillToConstraints\n                    }\n                    .clickable {\n                        onOpenLink(data.url.orEmpty(), null)\n                    }\n                    .padding(10.dp)\n            ) {\n                val (pic, name, msg) = createRefs()\n                if (!data.pic.isNullOrEmpty()) {\n                    CoilLoader(\n                        url = data.pic,\n                        modifier = Modifier\n                            .clip(RoundedCornerShape(8.dp))\n                            .aspectRatio(1f)\n                            .constrainAs(pic) {\n                                start.linkTo(parent.start)\n                                top.linkTo(parent.top)\n                                bottom.linkTo(parent.bottom)\n                                height = Dimension.fillToConstraints\n                            }\n                    )\n                }\n\n                Text(\n                    text = \"@${data.username}\",\n                    modifier = Modifier\n                        .padding(start = if (data.pic.isNullOrEmpty()) 0.dp else 10.dp)\n                        .constrainAs(name) {\n                            top.linkTo(parent.top)\n                            start.linkTo(if (data.pic.isNullOrEmpty()) parent.start else pic.end)\n                            end.linkTo(parent.end)\n                            width = Dimension.fillToConstraints\n                        },\n                    maxLines = 1,\n                    overflow = TextOverflow.Ellipsis,\n                    style = MaterialTheme.typography.bodyMedium.copy(fontSize = 13.sp)\n                )\n\n                LinkText(\n                    text = data.message.orEmpty(),\n                    onOpenLink = onOpenLink,\n                    textSize = 12f,\n                    color = MaterialTheme.colorScheme.outline.toArgb(),\n                    maxLines = 1,\n                    modifier = Modifier\n                        .padding(start = if (data.pic.isNullOrEmpty()) 0.dp else 10.dp)\n                        .constrainAs(msg) {\n                            top.linkTo(name.bottom)\n                            start.linkTo(if (data.pic.isNullOrEmpty()) parent.start else pic.end)\n                            end.linkTo(parent.end)\n                            width = Dimension.fillToConstraints\n                        }\n                )\n\n            }\n        }\n\n        if (!isLikeReply) {\n            if (!data.picArr.isNullOrEmpty()) {\n                NineImageView(\n                    pic = data.pic,\n                    picArr = data.picArr,\n                    feedType = data.feedType,\n                    modifier = Modifier\n                        .padding(\n                            start = if (data.feed != null) 0.dp else 10.dp,\n                            top = 10.dp,\n                            end = horizontal\n                        )\n                        .clip(MaterialTheme.shapes.medium)\n                        .constrainAs(image) {\n                            start.linkTo(if (data.feed != null) parent.start else avatar.end)\n                            top.linkTo(message.bottom)\n                            end.linkTo(parent.end)\n                            width = Dimension.fillToConstraints\n                        }\n                )\n            }\n\n            if (data.feed != null) {\n                ConstraintLayout(\n                    modifier = Modifier\n                        .padding(top = 10.dp, end = 10.dp)\n                        .clip(MaterialTheme.shapes.medium)\n                        .background(MaterialTheme.colorScheme.surface)\n                        .constrainAs(feed) {\n                            start.linkTo(parent.start)\n                            top.linkTo(\n                                if (data.picArr.isNullOrEmpty())\n                                    message.bottom\n                                else\n                                    image.bottom\n                            )\n                            end.linkTo(parent.end)\n                            width = Dimension.fillToConstraints\n                        }\n                        .clickable {\n                            onOpenLink(data.feed.url.orEmpty(), null)\n                        }\n                        .padding(10.dp)\n                ) {\n                    val (pic, name, msg) = createRefs()\n                    if (!data.feed.pic.isNullOrEmpty()) {\n                        CoilLoader(\n                            url = data.feed.pic,\n                            modifier = Modifier\n                                .clip(RoundedCornerShape(8.dp))\n                                .aspectRatio(1f)\n                                .constrainAs(pic) {\n                                    start.linkTo(parent.start)\n                                    top.linkTo(parent.top)\n                                    bottom.linkTo(parent.bottom)\n                                    height = Dimension.fillToConstraints\n                                }\n                        )\n                    }\n\n                    Text(\n                        text = \"@${data.feed.username}\",\n                        modifier = Modifier\n                            .padding(start = if (data.feed.pic.isNullOrEmpty()) 0.dp else 10.dp)\n                            .constrainAs(name) {\n                                top.linkTo(parent.top)\n                                start.linkTo(if (data.feed.pic.isNullOrEmpty()) parent.start else pic.end)\n                                end.linkTo(parent.end)\n                                width = Dimension.fillToConstraints\n                            },\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis,\n                        style = MaterialTheme.typography.bodyMedium.copy(fontSize = 13.sp)\n                    )\n\n                    LinkText(\n                        text = data.feed.message.orEmpty(),\n                        onOpenLink = onOpenLink,\n                        textSize = 12f,\n                        color = MaterialTheme.colorScheme.outline.toArgb(),\n                        maxLines = 1,\n                        modifier = Modifier\n                            .padding(start = if (data.feed.pic.isNullOrEmpty()) 0.dp else 10.dp)\n                            .constrainAs(msg) {\n                                top.linkTo(name.bottom)\n                                start.linkTo(if (data.feed.pic.isNullOrEmpty()) parent.start else pic.end)\n                                end.linkTo(parent.end)\n                                width = Dimension.fillToConstraints\n                            }\n                    )\n\n                }\n            }\n\n            IconText(\n                modifier = Modifier\n                    .padding(end = 10.dp, top = 10.dp)\n                    .constrainAs(reply) {\n                        top.linkTo(\n                            if (data.feed != null)\n                                feed.bottom\n                            else if (!data.picArr.isNullOrEmpty())\n                                image.bottom\n                            else\n                                message.bottom\n                        )\n                        end.linkTo(like.start)\n                    },\n                imageVector = Icons.AutoMirrored.Outlined.Message,\n                title = data.replynum.orEmpty(),\n            )\n\n            IconText(\n                modifier = Modifier\n                    .padding(top = 10.dp, end = horizontal)\n                    .constrainAs(like) {\n                        top.linkTo(\n                            if (data.feed != null)\n                                feed.bottom\n                            else if (!data.picArr.isNullOrEmpty())\n                                image.bottom\n                            else\n                                message.bottom\n                        )\n                        end.linkTo(parent.end)\n                    },\n                imageVector = if (data.userAction?.like == 1) Icons.Filled.ThumbUpAlt\n                else Icons.Default.ThumbUpOffAlt,\n                title = data.likenum.orEmpty(),\n                onClick = {\n                    if (isLogin) {\n                        onLike?.let {\n                            it(data.id.orEmpty(), data.userAction?.like ?: 0, LikeType.REPLY)\n                        }\n                    }\n                },\n                isLike = data.userAction?.like == 1,\n            )\n\n            Box(\n                modifier = Modifier\n                    .constrainAs(expand) {\n                        top.linkTo(parent.top)\n                        end.linkTo(parent.end)\n                    }) {\n\n                IconButton(\n                    onClick = {\n                        dropdownMenuExpanded = true\n                    }\n                ) {\n                    Icon(\n                        imageVector = Icons.Default.ExpandMore,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.outline\n                    )\n                }\n\n                DropdownMenu(\n                    expanded = dropdownMenuExpanded,\n                    onDismissRequest = {\n                        dropdownMenuExpanded = false\n                    },\n                ) {\n                    listOf(\"Block\", \"Show Reply\").forEachIndexed { index, menu ->\n                        DropdownMenuItem(\n                            text = { Text(menu) },\n                            onClick = {\n                                dropdownMenuExpanded = false\n                                when (index) {\n                                    0 -> onBlockUser(data.uid.orEmpty(), null)\n\n                                    1 -> onShowTotalReply?.let {\n                                        it(\n                                            data.id.orEmpty(),\n                                            data.uid.orEmpty(),\n                                            null,\n                                        )\n                                    }\n                                }\n                            }\n                        )\n                    }\n                    if (isLogin) {\n                        DropdownMenuItem(\n                            text = { Text(\"Report\") },\n                            onClick = {\n                                dropdownMenuExpanded = false\n                                onReport?.let { it(data.id.orEmpty(), ReportType.REPLY) }\n                            }\n                        )\n                    }\n                    if (data.uid == CookieUtil.uid) {\n                        DropdownMenuItem(\n                            text = { Text(\"Delete\") },\n                            onClick = {\n                                dropdownMenuExpanded = false\n                                onDelete?.let {\n                                    it(data.id.orEmpty(), LikeType.REPLY, null)\n                                }\n                            }\n                        )\n                    }\n                }\n\n            }\n\n            if (!isTotalReply\n                && (!data.replyRows.isNullOrEmpty() || (data.replyRowsMore ?: 0) > 0)\n            ) {\n                ReplyRows(\n                    modifier = Modifier\n                        .padding(start = 10.dp, top = 10.dp, end = 16.dp)\n                        .clip(MaterialTheme.shapes.medium)\n                        .background(cardBg())\n                        .constrainAs(replyRows) {\n                            top.linkTo(dateLine.bottom)\n                            start.linkTo(avatar.end)\n                            end.linkTo(parent.end)\n                            width = Dimension.fillToConstraints\n                        },\n                    data = data.replyRows,\n                    replyRowsMore = data.replyRowsMore ?: 0,\n                    replyNum = data.replynum ?: EMPTY_STRING,\n                    onShowTotalReply = { id ->\n                        onShowTotalReply?.let {\n                            it(\n                                id ?: data.id.orEmpty(),\n                                data.uid.orEmpty(),\n                                if (!id.isNullOrEmpty()) data.id.orEmpty() else null\n                            )\n                        }\n                    },\n                    onOpenLink = onOpenLink,\n                    onCopyText = onCopyText,\n                    onReport = onReport,\n                    onBlockUser = { uid ->\n                        onBlockUser(uid, if (data.uid == uid) null else data.id.orEmpty())\n                    },\n                    onDelete = { id, likeType ->\n                        onDelete?.let {\n                            it(id, likeType, data.id.orEmpty())\n                        }\n                    },\n                    onReply = { rid, runame ->\n                        onReply?.let {\n                            it(rid, data.uid.orEmpty(), runame, data.id.orEmpty())\n                        }\n                    }\n                )\n            }\n        }\n\n    }\n}\n\n\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun ReplyRows(\n    modifier: Modifier = Modifier,\n    data: List<HomeFeedResponse.Data>?,\n    replyRowsMore: Int,\n    replyNum: String,\n    onShowTotalReply: (String?) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    onBlockUser: (String) -> Unit,\n    onDelete: ((String, LikeType) -> Unit)? = null,\n    onReply: (String, String) -> Unit\n) {\n\n    var dropdownMenuExpanded by remember { mutableIntStateOf(-1) }\n\n    Column(\n        modifier = modifier\n    ) {\n        data?.forEachIndexed { index, reply ->\n            Box {\n                LinkText(\n                    text = reply.message.orEmpty(),\n                    lineSpacingMultiplier = 1.2f,\n                    textSize = 14f,\n                    onOpenLink = onOpenLink,\n                    isReply = true,\n                    onShowTotalReply = {\n                        onShowTotalReply(null)\n                    },\n                    imgList = reply.picArr,\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .combinedClickable(\n                            onClick = {\n                                if (isLogin) {\n                                    onReply(\n                                        reply.id.orEmpty(),\n                                        reply.username.orEmpty()\n                                    )\n                                }\n                            },\n                            onLongClick = {\n                                dropdownMenuExpanded = index\n                            }\n                        )\n                        .padding(horizontal = 10.dp, vertical = 4.dp)\n                )\n                DropdownMenu(\n                    expanded = dropdownMenuExpanded == index,\n                    onDismissRequest = { dropdownMenuExpanded = -1 }\n                ) {\n                    listOf(\n                        \"Copy\",\n                        \"Block\",\n                        \"Show Reply\"\n                    ).forEachIndexed { index, menu ->\n                        DropdownMenuItem(\n                            text = { Text(menu) },\n                            onClick = {\n                                dropdownMenuExpanded = -1\n                                when (index) {\n                                    0 -> onCopyText(reply.message)\n\n                                    1 -> onBlockUser(reply.uid.orEmpty())\n\n                                    2 -> onShowTotalReply(reply.id.orEmpty())\n                                }\n                            }\n                        )\n                    }\n                    if (isLogin) {\n                        DropdownMenuItem(\n                            text = { Text(\"Report\") },\n                            onClick = {\n                                dropdownMenuExpanded = -1\n                                onReport?.let { it(reply.id.orEmpty(), ReportType.REPLY) }\n                            }\n                        )\n                    }\n                    if (reply.uid == CookieUtil.uid) {\n                        DropdownMenuItem(\n                            text = { Text(\"Delete\") },\n                            onClick = {\n                                dropdownMenuExpanded = -1\n                                onDelete?.let { it(reply.id.orEmpty(), LikeType.REPLY) }\n                            }\n                        )\n                    }\n                }\n            }\n\n        }\n        if (replyRowsMore != 0) {\n            LinkText(\n                text = \"查看更多回复($replyNum)\",\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .clickable {\n                        onShowTotalReply(null)\n                    }\n                    .padding(horizontal = 10.dp, vertical = 4.dp),\n                color = MaterialTheme.colorScheme.primary.toArgb(),\n                textSize = 14f,\n                onOpenLink = onOpenLink,\n            )\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/FeedReplySortCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.VerticalDivider\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@Composable\nfun FeedReplySortCard(\n    modifier: Modifier = Modifier,\n    replyCount: String,\n    selected: Int = 0,\n    updateSortReply: (Int) -> Unit,\n) {\n\n    Column(\n        modifier = modifier\n            .fillMaxWidth()\n            .height(IntrinsicSize.Min)\n            .background(MaterialTheme.colorScheme.surface),\n    ) {\n        HorizontalDivider()\n\n        Row(\n            verticalAlignment = Alignment.CenterVertically\n        ) {\n\n            Text(\n                text = \"共 $replyCount 回复\",\n                modifier = Modifier\n                    .weight(1f)\n                    .padding(start = 16.dp)\n                    .padding(vertical = 2.dp),\n                style = MaterialTheme.typography.titleSmall.copy(fontSize = 13.sp),\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis,\n            )\n\n            VerticalDivider()\n\n            FeedReplySortCardItem(\n                title = \"默认\",\n                isSelected = selected == 0,\n                updateSortReply = {\n                    updateSortReply(0)\n                }\n            )\n\n            FeedReplySortCardItem(\n                title = \"最新\",\n                isSelected = selected == 1,\n                updateSortReply = {\n                    updateSortReply(1)\n                }\n            )\n\n            FeedReplySortCardItem(\n                title = \"热门\",\n                isSelected = selected == 2,\n                updateSortReply = {\n                    updateSortReply(2)\n                }\n            )\n\n            FeedReplySortCardItem(\n                modifier = Modifier.padding(end = 16.dp),\n                title = \"楼主\",\n                isSelected = selected == 3,\n                updateSortReply = {\n                    updateSortReply(3)\n                }\n            )\n\n        }\n\n        HorizontalDivider()\n    }\n\n}\n\n@Composable\nfun FeedReplySortCardItem(\n    modifier: Modifier = Modifier,\n    title: String,\n    isSelected: Boolean,\n    updateSortReply: () -> Unit,\n) {\n    Text(\n        text = title,\n        style = MaterialTheme.typography.titleSmall.copy(fontSize = 13.sp),\n        maxLines = 1,\n        overflow = TextOverflow.Ellipsis,\n        modifier = Modifier\n            .clickable {\n                updateSortReply()\n            }\n            .background(\n                if (isSelected)\n                    MaterialTheme.colorScheme.secondaryContainer\n                else\n                    MaterialTheme.colorScheme.surface\n            )\n            .padding(horizontal = 8.dp, vertical = 2.dp),\n    )\n\n    VerticalDivider(modifier = modifier)\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/HistoryCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.wrapContentHeight\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.ExpandMore\nimport androidx.compose.material.icons.filled.Smartphone\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.logic.model.FeedEntity\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.IconText\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.DateUtils.fromToday\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.ShareType\nimport com.example.c001apk.compose.util.Utils.richToString\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.getShareText\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/17\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun HistoryCard(\n    modifier: Modifier = Modifier,\n    data: FeedEntity,\n    onViewUser: (String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onDelete: (String) -> Unit,\n    onBlockUser: (String) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onCopyText: (String?) -> Unit,\n) {\n\n    Column(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n            .combinedClickable(\n                onClick = {\n                    onViewFeed(data.id, false)\n                },\n                onLongClick = {\n                    onCopyText(data.message)\n                }\n            )\n            .padding(start = 10.dp, bottom = 10.dp)\n    ) {\n\n        HistoryHeader(\n            data = data,\n            onViewUser = onViewUser,\n            onReport = onReport,\n            onDelete = onDelete,\n            onBlockUser = onBlockUser\n        )\n\n        LinkText(\n            text = data.message,\n            textSize = 16f,\n            lineSpacingMultiplier = 1.3f,\n            modifier = modifier.padding(top = 10.dp, end = 10.dp),\n            onOpenLink = onOpenLink\n        )\n\n    }\n\n}\n\n@Composable\nfun HistoryHeader(\n    modifier: Modifier = Modifier,\n    data: FeedEntity,\n    onViewUser: (String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onDelete: (String) -> Unit,\n    onBlockUser: (String) -> Unit,\n) {\n\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    val context = LocalContext.current\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .wrapContentHeight()\n    ) {\n        val (avatar, username, dateline, device, expand) = createRefs()\n\n        CoilLoader(\n            url = data.avatar,\n            modifier = Modifier\n                .padding(top = 10.dp)\n                .clip(CircleShape)\n                .aspectRatio(1f, false)\n                .constrainAs(avatar) {\n                    top.linkTo(username.top)\n                    bottom.linkTo(device.bottom)\n                    start.linkTo(parent.start)\n                    height = Dimension.fillToConstraints\n                }\n                .clickable {\n                    onViewUser(data.uid)\n                },\n        )\n\n        Text(\n            modifier = Modifier\n                .padding(start = 10.dp, top = 10.dp, end = 10.dp)\n                .constrainAs(username) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(expand.start)\n                    width = Dimension.fillToConstraints\n                },\n            text = data.uname,\n            style = MaterialTheme.typography.titleSmall,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n\n        Text(\n            text = fromToday(data.pubDate.toLongOrNull() ?: 0),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(dateline) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(username.bottom)\n                },\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 13.sp),\n            color = MaterialTheme.colorScheme.outline,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n\n        IconText(\n            modifier = Modifier\n                .padding(start = 10.dp, end = 10.dp)\n                .constrainAs(device) {\n                    start.linkTo(dateline.end)\n                    bottom.linkTo(dateline.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            imageVector = Icons.Default.Smartphone,\n            title = data.device.richToString(),\n            textSize = 13f,\n        )\n\n        Box(\n            modifier = Modifier\n                .aspectRatio(1f, false)\n                .constrainAs(expand) {\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                    bottom.linkTo(device.bottom)\n                    height = Dimension.fillToConstraints\n                }) {\n\n            IconButton(\n                onClick = {\n                    dropdownMenuExpanded = true\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.Default.ExpandMore,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.outline\n                )\n            }\n\n            DropdownMenu(\n                expanded = dropdownMenuExpanded,\n                onDismissRequest = {\n                    dropdownMenuExpanded = false\n                },\n            ) {\n                listOf(\"Copy\", \"Block\").forEachIndexed { index, menu ->\n                    DropdownMenuItem(\n                        text = { Text(menu) },\n                        onClick = {\n                            dropdownMenuExpanded = false\n                            when (index) {\n                                0 -> context.copyText(getShareText(ShareType.FEED, data.id))\n\n                                1 -> onBlockUser(data.uid)\n                            }\n                        }\n                    )\n                }\n                if (isLogin) {\n                    DropdownMenuItem(\n                        text = { Text(\"Report\") },\n                        onClick = {\n                            dropdownMenuExpanded = false\n                            onReport(data.id, ReportType.FEED)\n                        }\n                    )\n                }\n                DropdownMenuItem(\n                    text = { Text(\"Delete\") },\n                    onClick = {\n                        dropdownMenuExpanded = false\n                        onDelete(data.id)\n                    }\n                )\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/IconLinkGridCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/5\n */\n@Composable\nfun IconLinkGridCard(\n    modifier: Modifier = Modifier,\n    entities: List<HomeFeedResponse.Entities>?,\n    onOpenLink: (String, String?) -> Unit\n) {\n\n    entities?.let {\n        val pagerState = rememberPagerState { it.size / 5 }\n        Column(\n            modifier = modifier\n                .fillMaxWidth()\n                .clip(MaterialTheme.shapes.medium)\n                .background(cardBg())\n        ) {\n            HorizontalPager(\n                modifier = Modifier.fillMaxWidth(),\n                state = pagerState\n            ) { index ->\n\n                Row(modifier = Modifier.fillMaxWidth()) {\n                    (0..4).forEach {\n                        IconLinkGridCardItem(\n                            Modifier.weight(1f),\n                            entities.getOrNull(index * 5 + it)?.pic.orEmpty(),\n                            entities.getOrNull(index * 5 + it)?.url.orEmpty(),\n                            entities.getOrNull(index * 5 + it)?.title.orEmpty(),\n                            onOpenLink\n                        )\n                    }\n                }\n            }\n\n            if (pagerState.pageCount > 1) {\n                CardIndicator(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(bottom = 6.dp),\n                    dimension = 5.dp,\n                    defWidth = 1.5f,\n                    selectedWidth = 2f,\n                    defColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.4f),\n                    pagerState = pagerState,\n                )\n            }\n\n        }\n    }\n\n}\n\n@Composable\nfun IconLinkGridCardItem(\n    modifier: Modifier = Modifier,\n    pic: String,\n    url: String,\n    title: String,\n    onOpenLink: (String, String?) -> Unit\n) {\n\n    Column(\n        modifier = modifier\n            .clickable {\n                onOpenLink(url, title)\n            },\n        horizontalAlignment = Alignment.CenterHorizontally\n    ) {\n\n        CoilLoader(\n            url = pic,\n            modifier = Modifier\n                .padding(top = 4.dp)\n                .size(30.dp)\n        )\n\n        Text(\n            text = title,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            fontSize = 13.sp,\n            modifier = Modifier.padding(bottom = 2.dp)\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/IconMiniGridCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/7\n */\n@Composable\nfun IconMiniGridCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit\n) {\n\n\n    Column(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n            .padding(vertical = 5.dp)\n    ) {\n\n        if (!data.title.isNullOrEmpty()) {\n            TitleCard(\n                modifier = Modifier.padding(bottom = 5.dp),\n                url = data.url.orEmpty(),\n                title = data.title,\n                onOpenLink = onOpenLink,\n            )\n        }\n\n        data.entities?.let {\n            val columnCount = it.size / 2\n            (0 until columnCount).forEach { column ->\n                Row(\n                    modifier = Modifier.fillMaxWidth()\n                ) {\n                    (0..1).forEach { row ->\n                        IconMiniScrollCardItem(\n                            modifier = Modifier.weight(1f),\n                            isFeedContent = true,\n                            isGridCard = true,\n                            logoUrl = it.getOrNull(column * 2 + row)?.logo.orEmpty(),\n                            linkUrl = it.getOrNull(column * 2 + row)?.url.orEmpty(),\n                            titleText = it.getOrNull(column * 2 + row)?.title.orEmpty(),\n                            onOpenLink = onOpenLink\n                        )\n                    }\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/IconMiniScrollCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@Composable\nfun IconMiniScrollCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit\n) {\n\n    LazyRow(\n        modifier = modifier.fillMaxWidth(),\n        contentPadding = PaddingValues(horizontal = 10.dp),\n        horizontalArrangement = Arrangement.spacedBy(10.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n\n        if (!data.title.isNullOrEmpty()) {\n            item(key = \"title\") {\n                Text(\n                    text = data.title,\n                    fontSize = 15.sp\n                )\n            }\n        }\n\n        data.entities?.forEach {\n            item(key = it.id) {\n                IconMiniScrollCardItem(\n                    isFeedContent = true,\n                    logoUrl = it.logo.orEmpty(),\n                    linkUrl = it.url.orEmpty(),\n                    titleText = it.title.orEmpty(),\n                    onOpenLink = onOpenLink\n                )\n            }\n        }\n\n    }\n\n}\n\n@Composable\nfun IconMiniScrollCardItem(\n    modifier: Modifier = Modifier,\n    isFeedContent: Boolean,\n    logoUrl: String,\n    linkUrl: String,\n    titleText: String,\n    onOpenLink: (String, String?) -> Unit,\n    isGridCard: Boolean = false,\n) {\n\n    ConstraintLayout(\n        modifier = modifier\n            .clip(if (isGridCard) RectangleShape else RoundedCornerShape(8.dp))\n            .background(\n                if (isFeedContent) cardBg()\n                else MaterialTheme.colorScheme.surface\n            )\n            .clickable {\n                onOpenLink(linkUrl, titleText)\n            }\n            .padding(start = if (isGridCard) 10.dp else 5.dp, end = 5.dp)\n            .padding(vertical = 5.dp)\n    ) {\n        val (logo, title) = createRefs()\n\n        CoilLoader(\n            url = logoUrl,\n            modifier = Modifier\n                .clip(RoundedCornerShape(4.dp))\n                .aspectRatio(1f)\n                .constrainAs(logo) {\n                    start.linkTo(parent.start)\n                    top.linkTo(title.top)\n                    bottom.linkTo(title.bottom)\n                    height = Dimension.fillToConstraints\n                }\n        )\n\n        Text(\n            text = titleText,\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 13.sp),\n            modifier = Modifier\n                .fillMaxWidth()\n                .padding(start = 5.dp)\n                .constrainAs(title) {\n                    start.linkTo(logo.end)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                    if (isGridCard) {\n                        end.linkTo(parent.end)\n                        width = Dimension.fillToConstraints\n                    }\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/IconScrollCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@Composable\nfun IconScrollCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit,\n) {\n\n    BoxWithConstraints {\n\n        val itemWidth = (maxWidth - 30.dp) / 9f * 2\n\n        Column(\n            modifier = modifier\n                .fillMaxWidth()\n                .clip(MaterialTheme.shapes.medium)\n                .background(cardBg())\n        ) {\n\n            if (!data.title.isNullOrEmpty()) {\n                TitleCard(\n                    modifier = Modifier.padding(top = 10.dp),\n                    url = data.url.orEmpty(),\n                    title = data.title,\n                    onOpenLink = onOpenLink,\n                )\n            }\n\n            data.entities?.let {\n                LazyRow(\n                    modifier = Modifier.fillMaxWidth(),\n                    contentPadding = PaddingValues(horizontal = 10.dp, vertical = 5.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    it.forEach { item ->\n                        item(key = item.uid) {\n                            IconScrollCardItem(\n                                url = item.url.orEmpty(),\n                                avatar = item.userAvatar.orEmpty(),\n                                username = item.username.orEmpty(),\n                                onOpenLink = onOpenLink,\n                                itemWidth = itemWidth,\n                            )\n                        }\n                    }\n\n                }\n            }\n        }\n    }\n\n}\n\n@Composable\nfun IconScrollCardItem(\n    modifier: Modifier = Modifier,\n    url: String,\n    avatar: String,\n    username: String,\n    onOpenLink: (String, String?) -> Unit,\n    itemWidth: Dp,\n) {\n\n    Column(\n        modifier = modifier\n            .width(itemWidth)\n            .clip(MaterialTheme.shapes.medium)\n            .clickable {\n                onOpenLink(url, null)\n            }\n            .padding(5.dp),\n        horizontalAlignment = Alignment.CenterHorizontally\n    ) {\n\n        CoilLoader(\n            url = avatar,\n            modifier = Modifier\n                .size(itemWidth / 3f * 2)\n                .clip(CircleShape)\n        )\n\n        Text(\n            text = username,\n            fontSize = 13.sp,\n            modifier = Modifier.padding(top = 5.dp),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/ImageSquareScrollCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@Composable\nfun ImageSquareScrollCard(\n    modifier: Modifier = Modifier,\n    entities: List<HomeFeedResponse.Entities>?,\n    onOpenLink: (String, String?) -> Unit,\n) {\n\n    BoxWithConstraints {\n\n        val itemWidth = (maxWidth - 60.dp) / 5f\n\n        LazyRow(\n            modifier = modifier,\n            contentPadding = PaddingValues(horizontal = 10.dp),\n            horizontalArrangement = Arrangement.spacedBy(10.dp)\n        ) {\n\n            entities?.forEach {\n                item(it.title) {\n                    ImageSquareScrollCardItem(\n                        pic = it.pic.orEmpty(),\n                        url = it.url.orEmpty(),\n                        title = it.title.orEmpty(),\n                        onOpenLink = onOpenLink,\n                        itemWidth = itemWidth,\n                    )\n                }\n            }\n\n        }\n    }\n\n}\n\n@Composable\nfun ImageSquareScrollCardItem(\n    modifier: Modifier = Modifier,\n    pic: String,\n    url: String,\n    title: String,\n    onOpenLink: (String, String?) -> Unit,\n    itemWidth: Dp,\n) {\n\n    Box(\n        modifier = modifier\n            .size(itemWidth)\n            .clip(MaterialTheme.shapes.medium)\n            .clickable {\n                onOpenLink(url, title)\n            }\n    ) {\n\n        CoilLoader(\n            url = pic,\n            colorFilter = 0x8D000000,\n            modifier = Modifier.fillMaxSize()\n        )\n\n        Text(\n            text = title,\n            color = Color.White,\n            fontSize = 13.sp,\n            fontWeight = FontWeight.Bold,\n            modifier = Modifier.align(Alignment.Center),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n        )\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/ImageTextScrollCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.BoxWithConstraints\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyRow\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.Dp\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@Composable\nfun ImageTextScrollCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit,\n) {\n\n    BoxWithConstraints {\n\n        val itemWidth = (maxWidth - 20.dp) / 3f * 2\n\n        Column(\n            modifier = modifier.fillMaxWidth()\n        ) {\n            if (!data.title.isNullOrEmpty()) {\n                TitleCard(\n                    modifier = Modifier.padding(start = 10.dp, end = 10.dp, bottom = 10.dp),\n                    url = data.url.orEmpty(),\n                    title = data.title,\n                    onOpenLink = onOpenLink,\n                )\n            }\n\n            data.entities?.let {\n                LazyRow(\n                    modifier = Modifier.fillMaxWidth(),\n                    horizontalArrangement = Arrangement.spacedBy(10.dp),\n                    contentPadding = PaddingValues(horizontal = 10.dp)\n                ) {\n                    it.forEach { item ->\n                        item(key = item.id) {\n                            ImageTextScrollCardItem(\n                                url = item.url.orEmpty(),\n                                pic = item.pic.orEmpty(),\n                                title = item.title.orEmpty(),\n                                onOpenLink = onOpenLink,\n                                itemWidth = itemWidth,\n                            )\n                        }\n                    }\n\n                }\n            }\n\n        }\n    }\n\n}\n\n@Composable\nfun ImageTextScrollCardItem(\n    modifier: Modifier = Modifier,\n    url: String,\n    pic: String,\n    title: String,\n    onOpenLink: (String, String?) -> Unit,\n    itemWidth: Dp,\n) {\n\n    Column(\n        modifier = modifier\n            .width(itemWidth)\n            .clip(MaterialTheme.shapes.medium)\n            .clickable {\n                onOpenLink(url, title)\n            }\n    ) {\n        CoilLoader(\n            url = pic,\n            modifier = Modifier\n                .fillMaxWidth()\n                .aspectRatio(2.22f)\n        )\n\n        Text(\n            text = title,\n            maxLines = 2,\n            minLines = 2,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.bodyMedium.copy(fontWeight = FontWeight.Bold),\n            modifier = Modifier\n                .fillMaxWidth()\n                .background(cardBg())\n                .padding(horizontal = 10.dp, vertical = 5.dp)\n        )\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/LoadingCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.RectangleShape\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.logic.state.FooterState\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.logic.state.State\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/5\n */\n\nenum class Type {\n    TEXT,\n    INDICATOR,\n    NONE\n}\n\n@Composable\nfun LoadingCard(\n    modifier: Modifier = Modifier,\n    state: State,\n    onClick: (() -> Unit)? = null,\n    isFeed: Boolean = false\n) {\n\n    val type: Pair<Type, String?> = when (state) {\n        FooterState.End -> Pair(Type.TEXT, \"END\")\n        is FooterState.Error -> Pair(Type.TEXT, state.errMsg)\n        FooterState.Loading -> Pair(Type.INDICATOR, null)\n        FooterState.Success -> Pair(Type.NONE, null)\n        LoadingState.Empty -> Pair(Type.TEXT, \"EMPTY\")\n        is LoadingState.Error -> Pair(Type.TEXT, state.errMsg)\n        LoadingState.Loading -> Pair(Type.INDICATOR, null)\n        is LoadingState.Success<*> -> Pair(Type.NONE, null)\n    }\n\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .height(if (type.first == Type.NONE) 0.dp else 80.dp)\n            .clip(\n                if (isFeed) RectangleShape\n                else MaterialTheme.shapes.medium\n            )\n            .clickable(\n                enabled = onClick != null,\n                onClick = onClick ?: {}\n            )\n    ) {\n        when (type.first) {\n            Type.TEXT -> Text(\n                modifier = Modifier\n                    .padding(16.dp)\n                    .align(Alignment.Center),\n                text = type.second ?: \"EMPTY\",\n                textAlign = TextAlign.Center,\n                color = MaterialTheme.colorScheme.outline,\n            )\n\n            Type.INDICATOR -> CircularProgressIndicator(\n                modifier = Modifier.align(Alignment.Center),\n                strokeCap = StrokeCap.Round\n            )\n\n            Type.NONE -> {}\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/MessageCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material3.Badge\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.DateUtils.fromToday\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/13\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun MessageCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onOpenLink: (String, String?) -> Unit,\n    onViewUser: (String) -> Unit,\n    onHandleMessage: ((String, Int) -> Unit)? = null,\n    onViewChat: ((String, String, String) -> Unit)? = null,\n) {\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(\n                if (data.isTop == 1) MaterialTheme.colorScheme.primaryContainer\n                else cardBg()\n            )\n            .combinedClickable(\n                onClick = {\n                    onViewChat?.let {\n                        it(\n                            data.ukey.orEmpty(),\n                            data.messageUid.orEmpty(),\n                            data.messageUsername.orEmpty()\n                        )\n                    }\n                },\n                onLongClick = {\n                    onHandleMessage?.let { it(data.ukey.orEmpty(), data.isTop ?: 0) }\n                }\n            )\n            .padding(10.dp)\n    ) {\n\n        val (avatar, name, message, time, badge) = createRefs()\n\n        CoilLoader(\n            url = data.messageUserAvatar,\n            modifier = Modifier\n                .aspectRatio(1f)\n                .clip(CircleShape)\n                .constrainAs(avatar) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                    height = Dimension.fillToConstraints\n                }\n                .clickable {\n                    onViewUser(data.messageUid.orEmpty())\n                }\n        )\n\n        Text(\n            text = data.messageUsername.orEmpty(),\n            modifier = Modifier\n                .padding(horizontal = 10.dp)\n                .constrainAs(name) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(time.start)\n                    width = Dimension.fillToConstraints\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.titleMedium.copy(fontSize = 14.sp)\n        )\n\n        Text(\n            text = fromToday(data.dateline ?: 0),\n            modifier = Modifier\n                .constrainAs(time) {\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                },\n            color = MaterialTheme.colorScheme.outline,\n            style = MaterialTheme.typography.titleSmall.copy(fontSize = 13.sp)\n        )\n\n        LinkText(\n            text = data.message.orEmpty(),\n            onOpenLink = onOpenLink,\n            maxLines = 1,\n            modifier = Modifier\n                .padding(start = 10.dp, end = if (data.unreadNum != 0) 10.dp else 0.dp)\n                .constrainAs(message) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(name.bottom)\n                    end.linkTo(if (data.unreadNum != 0) badge.start else parent.end)\n                    width = Dimension.fillToConstraints\n                }\n        )\n\n        if (data.unreadNum != 0) {\n            Badge(\n                modifier = Modifier\n                    .constrainAs(badge) {\n                        top.linkTo(name.bottom)\n                        end.linkTo(parent.end)\n                    }\n            ) {\n                Text(text = data.unreadNum.toString())\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/MessageFFFCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.VerticalDivider\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.ui.ffflist.FFFListType\nimport com.example.c001apk.compose.ui.history.HistoryType\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n\ndata class FFFCardItem(\n    val title: String,\n    val value: String?,\n    val imageVector: ImageVector?,\n    val type: String,\n)\n\n@Composable\nfun MessageFFFCard(\n    modifier: Modifier = Modifier,\n    fffList: List<String>,\n    onViewFFFList: (String, String) -> Unit,\n) {\n    FFFCardRow(\n        modifier = modifier\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg()),\n        dataList = listOf(\n            FFFCardItem(\n                title = \"动态\",\n                value = fffList.getOrNull(0),\n                imageVector = null,\n                FFFListType.FEED.name\n            ),\n            FFFCardItem(\n                title = \"关注\",\n                value = fffList.getOrNull(1),\n                imageVector = null,\n                FFFListType.FOLLOW.name\n            ),\n            FFFCardItem(\n                title = \"粉丝\",\n                value = fffList.getOrNull(2),\n                imageVector = null,\n                FFFListType.FAN.name\n            )\n        ),\n        onViewFFFList = { type ->\n            onViewFFFList(CookieUtil.uid, type)\n        },\n        onViewHistory = {},\n    )\n}\n\n@Composable\nfun FFFCardRow(\n    modifier: Modifier = Modifier,\n    dataList: List<FFFCardItem>,\n    onViewFFFList: (String) -> Unit,\n    onViewHistory: (String) -> Unit,\n) {\n    Row(\n        modifier = modifier\n            .height(IntrinsicSize.Min)\n            .fillMaxWidth()\n    ) {\n        dataList.forEachIndexed { index, item ->\n            FFFCardItem(\n                modifier = Modifier.weight(1f),\n                onViewFFFList = {\n                    when (item.type) {\n                        FFFListType.FAV.name -> {\n                            onViewHistory(HistoryType.FAV.name)\n                        }\n\n                        FFFListType.HISTORY.name -> {\n                            onViewHistory(HistoryType.HISTORY.name)\n                        }\n\n                        else -> {\n                            if (CookieUtil.isLogin) {\n                                onViewFFFList(item.type)\n                            }\n                        }\n                    }\n                },\n                title = item.title,\n                value = item.value,\n                imageVector = item.imageVector\n            )\n            if (index != 2)\n                VerticalDivider(modifier = Modifier.padding(vertical = 14.dp))\n        }\n    }\n}\n\n@Composable\nfun FFFCardItem(\n    modifier: Modifier = Modifier,\n    onViewFFFList: () -> Unit,\n    title: String,\n    value: String?,\n    imageVector: ImageVector?,\n) {\n    Column(\n        modifier = modifier\n            .clickable {\n                onViewFFFList()\n            }\n            .padding(vertical = 5.dp),\n        horizontalAlignment = Alignment.CenterHorizontally\n    ) {\n        if (imageVector == null) {\n            Text(\n                text = value ?: EMPTY_STRING,\n                style = MaterialTheme.typography.titleMedium.copy(\n                    fontSize = 20.sp,\n                    fontWeight = FontWeight.Bold,\n                    fontFamily = FontFamily.Monospace\n                ),\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n        } else {\n            Image(\n                imageVector = imageVector,\n                contentDescription = null,\n                colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant)\n            )\n        }\n\n        Text(\n            text = title,\n            style = MaterialTheme.typography.bodyMedium,\n            modifier = Modifier.alpha(if (value.isNullOrEmpty() && imageVector == null) 0f else 1f),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/MessageHeaderCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.aspectRatio\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.Logout\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.LinearProgressIndicator\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ProgressIndicatorDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.alpha\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.decode\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@Composable\nfun MessageHeaderCard(\n    modifier: Modifier = Modifier,\n    isLogin: Boolean,\n    userAvatar: String,\n    userName: String,\n    level: String,\n    experience: String,\n    nextLevelExperience: String,\n    onLogin: () -> Unit,\n    onLogout: () -> Unit,\n    onViewUser: (String) -> Unit\n) {\n\n    val animatedProgress by animateFloatAsState(\n        targetValue = (experience.toFloatOrNull() ?: 0f) / (nextLevelExperience.toFloatOrNull()\n            ?: 1f),\n        animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, label = EMPTY_STRING\n    )\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .padding(10.dp)\n    ) {\n        val startGuide = createGuidelineFromStart(0.75f)\n\n        val (login, avatar, username, levelView, experienceView, indicator, logout) = createRefs()\n\n        if (!isLogin) {\n            FilledTonalButton(\n                onClick = { onLogin() },\n                modifier = Modifier\n                    .constrainAs(login) {\n                        start.linkTo(parent.start)\n                        top.linkTo(parent.top)\n                        end.linkTo(parent.end)\n                        bottom.linkTo(parent.bottom)\n                    }\n            ) {\n                Text(text = \"点击登录\")\n            }\n        }\n\n        if (isLogin) {\n            CoilLoader(\n                url = userAvatar,\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .clip(CircleShape)\n                    .aspectRatio(1f)\n                    .constrainAs(avatar) {\n                        start.linkTo(parent.start)\n                        top.linkTo(username.top)\n                        bottom.linkTo(indicator.bottom)\n                        height = Dimension.fillToConstraints\n                    }\n                    .clickable { onViewUser(CookieUtil.uid) }\n            )\n        }\n\n        Text(\n            text = userName.decode,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.titleMedium.copy(\n                fontSize = 15.sp,\n                fontWeight = FontWeight.Bold\n            ),\n            modifier = Modifier\n                .padding(horizontal = 10.dp)\n                .constrainAs(username) {\n                    start.linkTo(avatar.end)\n                    end.linkTo(if (isLogin) logout.start else parent.end)\n                    top.linkTo(parent.top)\n                    width = Dimension.fillToConstraints\n                }\n        )\n\n        Text(\n            text = \"Lv.${level}\",\n            style = MaterialTheme.typography.bodyMedium.copy(\n                fontSize = 12.sp,\n            ),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(levelView) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(username.bottom)\n                }\n                .alpha(if (isLogin) 1f else 0f)\n        )\n\n        Text(\n            text = \"${experience}/${nextLevelExperience}\",\n            style = MaterialTheme.typography.bodyMedium.copy(\n                fontSize = 12.sp,\n            ),\n            modifier = Modifier\n                .constrainAs(experienceView) {\n                    top.linkTo(username.bottom)\n                    end.linkTo(startGuide)\n                }\n                .alpha(if (isLogin) 1f else 0f)\n        )\n\n        LinearProgressIndicator(\n            progress = { animatedProgress },\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(indicator) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(levelView.bottom)\n                    end.linkTo(startGuide)\n                    width = Dimension.fillToConstraints\n                }\n                .alpha(if (isLogin) 1f else 0f)\n        )\n\n        if (isLogin) {\n            IconButton(\n                onClick = {\n                    onLogout()\n                },\n                modifier = Modifier\n                    .constrainAs(logout) {\n                        top.linkTo(parent.top)\n                        end.linkTo(parent.end)\n                        bottom.linkTo(parent.bottom)\n                    }\n            ) {\n                Icon(\n                    Icons.AutoMirrored.Default.Logout,\n                    contentDescription = null\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/MessageListCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight\nimport androidx.compose.material.icons.automirrored.outlined.Message\nimport androidx.compose.material.icons.filled.AlternateEmail\nimport androidx.compose.material.icons.outlined.Mail\nimport androidx.compose.material.icons.outlined.PersonAdd\nimport androidx.compose.material.icons.outlined.ThumbUp\nimport androidx.compose.material3.Badge\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.ColorFilter\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n\nval backgroundList = listOf(\"#2196f3\", \"#00bcd4\", \"#4caf50\", \"#f44336\", \"#ff9800\")\nval iconList = listOf(\n    Icons.Default.AlternateEmail,\n    Icons.AutoMirrored.Outlined.Message,\n    Icons.Outlined.ThumbUp,\n    Icons.Outlined.PersonAdd,\n    Icons.Outlined.Mail\n)\nval titleList = listOf(\"@我的动态\", \"@我的评论\", \"我收到的赞\", \"好友关注\", \"私信\")\n\n@Composable\nfun MessageListCard(\n    modifier: Modifier = Modifier,\n    background: String,\n    imageVector: ImageVector,\n    title: String,\n    count: Int?,\n    onViewNotice: () -> Unit,\n) {\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n            .clickable {\n                if (CookieUtil.isLogin) {\n                    onViewNotice()\n                }\n            }\n            .padding(10.dp),\n    ) {\n\n        val (icon, name, badge, arrow) = createRefs()\n\n        MessageCardLogo(\n            background = background,\n            imageVector = imageVector,\n            modifier = Modifier\n                .constrainAs(icon) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                }\n        )\n\n        Text(\n            text = title,\n            style = MaterialTheme.typography.titleMedium.copy(fontSize = 14.sp),\n            modifier = Modifier\n                .padding(start = 10.dp)\n                .constrainAs(name) {\n                    start.linkTo(icon.end)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                }\n        )\n\n        count?.let {\n            if (it > 0) {\n                Badge(\n                    modifier = Modifier\n                        .constrainAs(badge) {\n                            start.linkTo(name.start)\n                            end.linkTo(arrow.start)\n                            top.linkTo(parent.top)\n                            bottom.linkTo(parent.bottom)\n                        }\n                ) {\n                    Text(text = it.toString())\n                }\n            }\n        }\n\n        Image(\n            imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,\n            contentDescription = null,\n            colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.outline),\n            modifier = Modifier\n                .constrainAs(arrow) {\n                    end.linkTo(parent.end)\n                    top.linkTo(parent.top)\n                    bottom.linkTo(parent.bottom)\n                }\n        )\n\n    }\n\n}\n\n@Composable\nprivate fun MessageCardLogo(\n    modifier: Modifier = Modifier,\n    background: String,\n    imageVector: ImageVector,\n) {\n    Box(\n        modifier = modifier\n            .size(40.dp)\n            .clip(CircleShape)\n            .background(Color(android.graphics.Color.parseColor(background))),\n        contentAlignment = Alignment.Center\n    ) {\n        Image(\n            imageVector = imageVector,\n            contentDescription = null,\n            colorFilter = ColorFilter.tint(Color.White)\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/MessageWidgetCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.Archive\nimport androidx.compose.material.icons.outlined.ChatBubbleOutline\nimport androidx.compose.material.icons.outlined.FavoriteBorder\nimport androidx.compose.material.icons.outlined.History\nimport androidx.compose.material.icons.outlined.MyLocation\nimport androidx.compose.material.icons.outlined.StarOutline\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport com.example.c001apk.compose.ui.ffflist.FFFListType\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.CookieUtil\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@Composable\nfun MessageWidgetCard(\n    modifier: Modifier = Modifier,\n    onViewFFFList: (String?, String) -> Unit,\n    onViewHistory: (String) -> Unit,\n) {\n    Column(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n\n    ) {\n        FFFCardRow(\n            dataList = listOf(\n                FFFCardItem(\n                    title = \"本地收藏\",\n                    value = null,\n                    imageVector = Icons.Outlined.Archive,\n                    FFFListType.FAV.name\n                ),\n                FFFCardItem(\n                    title = \"浏览历史\",\n                    value = null,\n                    imageVector = Icons.Outlined.History,\n                    FFFListType.HISTORY.name\n                ),\n                FFFCardItem(\n                    title = \"我的常去\",\n                    value = null,\n                    imageVector = Icons.Outlined.MyLocation,\n                    FFFListType.RECENT.name\n                )\n            ),\n            onViewFFFList = { type ->\n                onViewFFFList(CookieUtil.uid, type)\n            },\n            onViewHistory = onViewHistory\n        )\n        FFFCardRow(\n            dataList = listOf(\n                FFFCardItem(\n                    title = \"我的收藏\",\n                    value = null,\n                    imageVector = Icons.Outlined.StarOutline,\n                    FFFListType.COLLECTION.name\n                ),\n                FFFCardItem(\n                    title = \"我的赞\",\n                    value = null,\n                    imageVector = Icons.Outlined.FavoriteBorder,\n                    FFFListType.LIKE.name\n                ),\n                FFFCardItem(\n                    title = \"我的回复\",\n                    value = null,\n                    imageVector = Icons.Outlined.ChatBubbleOutline,\n                    FFFListType.REPLY.name\n                )\n            ),\n            onViewFFFList = { type ->\n                onViewFFFList(\n                    if (type == FFFListType.COLLECTION.name) null else CookieUtil.uid,\n                    type\n                )\n            },\n            onViewHistory = {}\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/NotificationCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.shape.CircleShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.ExpandMore\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport androidx.constraintlayout.compose.Dimension\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.CoilLoader\nimport com.example.c001apk.compose.ui.component.LinkText\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport com.example.c001apk.compose.util.DateUtils\nimport com.example.c001apk.compose.util.ReportType\nimport org.jsoup.Jsoup\nimport org.jsoup.nodes.Document\nimport org.jsoup.select.Elements\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun NotificationCard(\n    modifier: Modifier = Modifier,\n    data: HomeFeedResponse.Data,\n    onViewUser: (String) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onReport: ((String, ReportType) -> Unit)? = null,\n    onDeleteNotice: ((String) -> Unit)? = null,\n) {\n\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    val isFollow by lazy { data.type == \"contacts_follow\" }\n\n    ConstraintLayout(\n        modifier = modifier\n            .fillMaxWidth()\n            .padding(horizontal = 10.dp)\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg())\n            .combinedClickable(\n                onClick = {\n                    if (isFollow) {\n                        onOpenLink(data.url.orEmpty(), null)\n                    } else {\n                        val doc: Document = Jsoup.parse(data.note.orEmpty())\n                        val links: Elements = doc.select(\"a[href]\")\n                        onOpenLink(\n                            links\n                                .getOrNull(0)\n                                ?.attr(\"href\")\n                                .orEmpty(),\n                            null\n                        )\n                    }\n                },\n                onLongClick = {\n                    onDeleteNotice?.let { it(data.id.orEmpty()) }\n                }\n            )\n    ) {\n\n        val (avatar, username, expand, message, dateLine) = createRefs()\n\n        CoilLoader(\n            url = data.fromUserAvatar,\n            modifier = Modifier\n                .padding(start = 10.dp, top = 10.dp)\n                .size(30.dp)\n                .clip(CircleShape)\n                .constrainAs(avatar) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                }\n                .clickable {\n                    onViewUser(data.fromuid.orEmpty())\n                }\n        )\n\n        Text(\n            text = data.fromusername.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp, top = 10.dp, end = 10.dp)\n                .constrainAs(username) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(parent.top)\n                    end.linkTo(expand.start)\n                    width = Dimension.fillToConstraints\n                },\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            style = MaterialTheme.typography.titleSmall,\n        )\n\n        LinkText(\n            text = data.note.orEmpty(),\n            modifier = Modifier\n                .padding(start = 10.dp, top = 5.dp, end = 10.dp)\n                .constrainAs(message) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(username.bottom)\n                    end.linkTo(parent.end)\n                    width = Dimension.fillToConstraints\n                },\n            lineSpacingMultiplier = 1.2f,\n            onOpenLink = if (isFollow) null else onOpenLink,\n        )\n\n        Text(\n            text = DateUtils.fromToday(data.dateline ?: 0),\n            modifier = Modifier\n                .padding(start = 10.dp, top = 10.dp, bottom = 10.dp)\n                .constrainAs(dateLine) {\n                    start.linkTo(avatar.end)\n                    top.linkTo(message.bottom)\n                },\n            style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n            color = MaterialTheme.colorScheme.outline\n        )\n\n        Box(\n            modifier = Modifier\n                .constrainAs(expand) {\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                }) {\n\n            IconButton(\n                onClick = {\n                    dropdownMenuExpanded = true\n                }\n            ) {\n                Icon(\n                    imageVector = Icons.Default.ExpandMore,\n                    contentDescription = null,\n                    tint = MaterialTheme.colorScheme.outline\n                )\n            }\n\n            DropdownMenu(\n                expanded = dropdownMenuExpanded,\n                onDismissRequest = {\n                    dropdownMenuExpanded = false\n                },\n            ) {\n                listOf(\"Block\", \"Report\").forEachIndexed { index, menu ->\n                    DropdownMenuItem(\n                        text = { Text(menu) },\n                        onClick = {\n                            dropdownMenuExpanded = false\n                            when (index) {\n                                1 -> onReport?.let { it(data.fromuid.orEmpty(), ReportType.USER) }\n                            }\n                        }\n                    )\n                }\n            }\n\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/SearchHistoryCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.ExperimentalFoundationApi\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.combinedClickable\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/16\n */\n@OptIn(ExperimentalFoundationApi::class)\n@Composable\nfun SearchHistoryCard(\n    modifier: Modifier = Modifier,\n    data: String,\n    onSearch: () -> Unit,\n    onDelete: () -> Unit,\n) {\n\n    Text(\n        modifier = modifier\n            .clip(RoundedCornerShape(20.dp))\n            .background(cardBg())\n            .combinedClickable(\n                onClick = onSearch,\n                onLongClick = onDelete\n            )\n            .padding(horizontal = 10.dp, vertical = 4.dp),\n        text = data,\n        style = MaterialTheme.typography.titleSmall,\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/TextCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.ui.theme.cardBg\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/15\n */\n@Composable\nfun TextCard(\n    modifier: Modifier = Modifier,\n    text: String\n) {\n    Box(\n        modifier = modifier\n            .fillMaxWidth()\n            .height(80.dp)\n            .clip(MaterialTheme.shapes.medium)\n            .background(cardBg()),\n        contentAlignment = Alignment.Center\n    ) {\n        Text(\n            modifier = Modifier.padding(16.dp),\n            text = text,\n            color = MaterialTheme.colorScheme.outline,\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/TitleCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowForwardIos\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.util.noRippleClickable\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@Composable\nfun TitleCard(\n    modifier: Modifier = Modifier,\n    url: String,\n    title: String,\n    onOpenLink: (String, String?) -> Unit,\n) {\n\n    Row(\n        modifier = modifier\n            .fillMaxWidth()\n            .clip(MaterialTheme.shapes.medium)\n            .noRippleClickable(enabled = url.isNotEmpty()) {\n                onOpenLink(url, title)\n            }\n            .padding(horizontal = 10.dp),\n        verticalAlignment = Alignment.CenterVertically\n    ) {\n        Text(\n            text = title,\n            fontWeight = FontWeight.Bold,\n            fontSize = 16.sp,\n            modifier = Modifier.weight(1f)\n        )\n\n        if (url.isNotEmpty()) {\n            Icon(\n                imageVector = Icons.AutoMirrored.Filled.ArrowForwardIos,\n                contentDescription = null,\n                tint = MaterialTheme.colorScheme.outline,\n                modifier = Modifier.size(15.dp)\n            )\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/cards/UserInfoCard.kt",
    "content": "package com.example.c001apk.compose.ui.component.cards\n\nimport androidx.compose.foundation.BorderStroke\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.outlined.Mail\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedIconButton\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.text.font.FontStyle\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.constraintlayout.compose.ConstraintLayout\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.ui.component.ImageView\nimport com.example.c001apk.compose.ui.ffflist.FFFListType\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.DateUtils.fromToday\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.noRippleClickable\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\n\n@Composable\nfun UserInfoCard(\n    data: HomeFeedResponse.Data,\n    onFollow: (String, Int) -> Unit,\n    onPMUser: (String, String) -> Unit,\n    onViewFFFList: (String, String) -> Unit,\n) {\n\n    val context = LocalContext.current\n    //  val userPreferences = LocalUserPreferences.current\n\n    ConstraintLayout {\n\n        val (cover, avatar, username, uidLevel, bio, lff, active, pm, followBtn) = createRefs()\n\n        ImageView(\n            modifier = Modifier\n                .fillMaxWidth()\n                .height(150.dp)\n                .constrainAs(cover) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                },\n            url = data.cover.orEmpty(),\n            isCover = true,\n        )\n\n        /*AsyncImage(\n            model = ImageRequest.Builder(context)\n                .data(data.cover)\n                .transformations(\n                    ColorFilterTransformation(\n                        Color.parseColor(\n                            if (userPreferences.isDarkMode())\n                                \"#8D000000\"\n                            else\n                                \"#5DFFFFFF\"\n                        )\n                    )\n                )\n                .crossfade(true)\n                .build(),\n            contentDescription = null,\n            contentScale = ContentScale.Crop,\n            modifier = Modifier\n                .fillMaxWidth()\n                .height(150.dp)\n                .constrainAs(cover) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                    end.linkTo(parent.end)\n                }\n        )*/\n\n        /*AsyncImage(\n            model = ImageRequest.Builder(context)\n                .data(data.userAvatar)\n                .crossfade(true)\n                .build(),\n            contentDescription = null,\n            contentScale = ContentScale.Crop,\n            modifier = Modifier\n                .padding(start = 20.dp, top = 110.dp)\n                .height(80.dp)\n                .width(80.dp)\n                .constrainAs(avatar) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                }\n                .clip(CircleShape)\n                .border(4.dp, MaterialTheme.colorScheme.surface, CircleShape)\n        )*/\n\n        ImageView(\n            modifier = Modifier\n                .padding(start = 20.dp, top = 110.dp)\n                .height(80.dp)\n                .width(80.dp)\n                .constrainAs(avatar) {\n                    start.linkTo(parent.start)\n                    top.linkTo(parent.top)\n                },\n            url = data.userAvatar.orEmpty(),\n            isRound = true,\n            borderWidth = 4f,\n            borderColor = MaterialTheme.colorScheme.surface.toArgb()\n        )\n\n        Text(\n            text = data.username.orEmpty(),\n            textAlign = TextAlign.Start,\n            style = MaterialTheme.typography.titleMedium.copy(\n                fontSize = 15.sp,\n                fontWeight = FontWeight.Bold\n            ),\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            modifier = Modifier\n                .padding(top = 5.dp)\n                .fillMaxWidth()\n                .padding(horizontal = 20.dp)\n                .constrainAs(username) {\n                    start.linkTo(parent.start)\n                    top.linkTo(avatar.bottom)\n                }\n                .noRippleClickable {\n                    context.copyText(data.username.orEmpty())\n                }\n        )\n\n        Row(\n            modifier = Modifier\n                .padding(start = 20.dp, top = 5.dp)\n                .constrainAs(uidLevel) {\n                    start.linkTo(parent.start)\n                    top.linkTo(username.bottom)\n                }\n        ) {\n            Text(\n                text = \"uid: ${data.uid}\",\n                textAlign = TextAlign.Center,\n                style = MaterialTheme.typography.bodyMedium,\n                modifier = Modifier\n                    .align(Alignment.CenterVertically)\n                    .noRippleClickable {\n                        context.copyText(data.uid.orEmpty())\n                    }\n            )\n\n            Text(\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .clip(MaterialTheme.shapes.medium)\n                    .background(MaterialTheme.colorScheme.secondaryContainer)\n                    .align(Alignment.CenterVertically)\n                    .padding(horizontal = 6.dp),\n                text = \"Lv.${data.level}\",\n                textAlign = TextAlign.Center,\n                style = MaterialTheme.typography.bodySmall.copy(fontWeight = FontWeight.Bold),\n                fontStyle = FontStyle.Italic,\n                color = MaterialTheme.colorScheme.onSecondaryContainer\n            )\n        }\n\n        if (!data.bio.isNullOrEmpty()) {\n            Text(\n                text = data.bio,\n                textAlign = TextAlign.Start,\n                style = MaterialTheme.typography.bodyMedium,\n                modifier = Modifier\n                    .padding(top = 5.dp)\n                    .fillMaxWidth()\n                    .padding(horizontal = 20.dp)\n                    .constrainAs(bio) {\n                        start.linkTo(parent.start)\n                        end.linkTo(parent.end)\n                        top.linkTo(uidLevel.bottom)\n                    }\n                    .noRippleClickable {\n                        context.copyText(data.bio)\n                    },\n            )\n        }\n\n        Row(\n            modifier = Modifier\n                .padding(start = 20.dp, top = 5.dp)\n                .constrainAs(lff) {\n                    start.linkTo(parent.start)\n                    top.linkTo(if (data.bio.isNullOrEmpty()) uidLevel.bottom else bio.bottom)\n                }\n        ) {\n            Text(\n                text = \"${data.feed?.id ?: 0}动态\",\n                style = MaterialTheme.typography.bodyMedium,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n\n            Text(\n                modifier = Modifier.padding(start = 10.dp),\n                text = \"${data.beLikeNum}赞\",\n                style = MaterialTheme.typography.bodyMedium,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n\n            Text(\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .noRippleClickable {\n                        onViewFFFList(data.uid.orEmpty(), FFFListType.USER_FOLLOW.name)\n                    },\n                text = \"${data.follow}关注\",\n                style = MaterialTheme.typography.bodyMedium,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n\n            Text(\n                modifier = Modifier\n                    .padding(start = 10.dp)\n                    .noRippleClickable {\n                        onViewFFFList(data.uid.orEmpty(), FFFListType.FAN.name)\n                    },\n                text = \"${data.fans}粉丝\",\n                style = MaterialTheme.typography.bodyMedium,\n                maxLines = 1,\n                overflow = TextOverflow.Ellipsis\n            )\n        }\n\n        Text(\n            text = \"${fromToday(data.logintime ?: 0)}活跃\",\n            textAlign = TextAlign.Start,\n            style = MaterialTheme.typography.bodyMedium,\n            maxLines = 1,\n            overflow = TextOverflow.Ellipsis,\n            modifier = Modifier\n                .padding(top = 5.dp)\n                .fillMaxWidth()\n                .padding(horizontal = 20.dp)\n                .constrainAs(active) {\n                    start.linkTo(parent.start)\n                    end.linkTo(parent.end)\n                    top.linkTo(lff.bottom)\n                },\n        )\n\n        FilledTonalButton(\n            onClick = {\n                onFollow(data.uid.orEmpty(), data.isFollow ?: 0)\n            },\n            modifier = Modifier\n                .padding(top = 10.dp, end = 20.dp)\n                .constrainAs(followBtn) {\n                    end.linkTo(parent.end)\n                    top.linkTo(cover.bottom)\n                }\n        ) {\n            Text(text = if (data.isFollow == 1) \"取消关注\" else \"关注\")\n        }\n\n        OutlinedIconButton(\n            onClick = {\n                if (CookieUtil.isLogin) {\n                    onPMUser(data.uid.orEmpty(), data.username.orEmpty())\n                }\n            },\n            modifier = Modifier\n                .padding(top = 10.dp, end = 10.dp)\n                .constrainAs(pm) {\n                    top.linkTo(followBtn.top)\n                    bottom.linkTo(followBtn.bottom)\n                    end.linkTo(followBtn.start)\n                },\n            border = BorderStroke(1.dp, MaterialTheme.colorScheme.outline)\n        ) {\n            Icon(\n                imageVector = Icons.Outlined.Mail,\n                contentDescription = null\n            )\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/icons/Swatch.kt",
    "content": "/*\n * ImageToolbox is an image editor for android\n * Copyright (c) 2024 T8RIN (Malik Mukhametzyanov)\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n *\n * You should have received a copy of the Apache License\n * along with this program.  If not, see <http://www.apache.org/licenses/LICENSE-2.0>.\n */\n\npackage com.example.c001apk.compose.ui.component.icons\n\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.PathFillType.Companion.NonZero\nimport androidx.compose.ui.graphics.SolidColor\nimport androidx.compose.ui.graphics.StrokeCap.Companion.Butt\nimport androidx.compose.ui.graphics.StrokeJoin.Companion.Miter\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.graphics.vector.ImageVector.Builder\nimport androidx.compose.ui.graphics.vector.path\nimport androidx.compose.ui.unit.dp\n\nval Icons.Rounded.Swatch: ImageVector by lazy {\n    Builder(\n        name = \"Swatch\", defaultWidth = 24.0.dp, defaultHeight = 24.0.dp,\n        viewportWidth = 24.0f, viewportHeight = 24.0f\n    ).apply {\n        path(\n            fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,\n            strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,\n            pathFillType = NonZero\n        ) {\n            moveTo(20.0f, 14.0f)\n            horizontalLineTo(6.0f)\n            curveTo(3.8f, 14.0f, 2.0f, 15.8f, 2.0f, 18.0f)\n            reflectiveCurveTo(3.8f, 22.0f, 6.0f, 22.0f)\n            horizontalLineTo(20.0f)\n            curveTo(21.1f, 22.0f, 22.0f, 21.1f, 22.0f, 20.0f)\n            verticalLineTo(16.0f)\n            curveTo(22.0f, 14.9f, 21.1f, 14.0f, 20.0f, 14.0f)\n            moveTo(6.0f, 20.0f)\n            curveTo(4.9f, 20.0f, 4.0f, 19.1f, 4.0f, 18.0f)\n            reflectiveCurveTo(4.9f, 16.0f, 6.0f, 16.0f)\n            reflectiveCurveTo(8.0f, 16.9f, 8.0f, 18.0f)\n            reflectiveCurveTo(7.1f, 20.0f, 6.0f, 20.0f)\n            moveTo(6.3f, 12.0f)\n            lineTo(13.0f, 5.3f)\n            curveTo(13.8f, 4.5f, 15.0f, 4.5f, 15.8f, 5.3f)\n            lineTo(18.6f, 8.1f)\n            curveTo(19.4f, 8.9f, 19.4f, 10.1f, 18.6f, 10.9f)\n            lineTo(17.7f, 12.0f)\n            horizontalLineTo(6.3f)\n            moveTo(2.0f, 13.5f)\n            verticalLineTo(4.0f)\n            curveTo(2.0f, 2.9f, 2.9f, 2.0f, 4.0f, 2.0f)\n            horizontalLineTo(8.0f)\n            curveTo(9.1f, 2.0f, 10.0f, 2.9f, 10.0f, 4.0f)\n            verticalLineTo(5.5f)\n            lineTo(2.0f, 13.5f)\n            close()\n        }\n    }\n        .build()\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/settings/BasicListItem.kt",
    "content": "package com.example.c001apk.compose.ui.component.settings\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.painter.Painter\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun BasicListItem(\n    modifier: Modifier = Modifier,\n    headlineText: String? = null,\n    supportingText: String? = null,\n    leadingImageVector: ImageVector? = null,\n    leadingPainter: Painter? = null,\n    leadingText: String? = null,\n    trailingContent: @Composable () -> Unit = {},\n    onClick: (() -> Unit)? = null,\n) {\n    ListItem(\n        modifier = onClick?.let {\n            modifier.clickable(\n                onClick = onClick\n            )\n        } ?: modifier,\n        headlineContent = {\n            if (headlineText != null) {\n                Text(headlineText)\n            }\n        },\n        supportingContent = {\n            if (supportingText != null) {\n                Text(supportingText)\n            }\n        },\n        leadingContent = {\n            if (leadingText != null) {\n                Text(\n                    text = leadingText,\n                    color = MaterialTheme.colorScheme.primary,\n                    style = MaterialTheme.typography.titleSmall\n                )\n            } else if (leadingImageVector != null) {\n                Icon(imageVector = leadingImageVector, contentDescription = null)\n            } else if (leadingPainter != null) {\n                Image(\n                    painter = leadingPainter, contentDescription = null, modifier = Modifier\n                        .clip(MaterialTheme.shapes.medium)\n                        .size(24.dp)\n                )\n            }\n        },\n        trailingContent = trailingContent\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/settings/DropdownListItem.kt",
    "content": "package com.example.c001apk.compose.ui.component.settings\n\nimport androidx.compose.foundation.background\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\n\n/**\n * Used for selections\n * If label is null, then value will be displayed on the screen\n */\ndata class SelectionItem<T>(val label: String, val value: T)\n\n@Composable\nfun <T> DropdownListItem(\n    value: T?,\n    leadingImageVector: ImageVector? = null,\n    headlineText: String,\n    selections: List<SelectionItem<T>>,\n    onValueChanged: (index: Int, value: T) -> Unit,\n) {\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    BasicListItem(\n        headlineText = headlineText,\n        supportingText = selections.find { it.value == value }?.label ?: EMPTY_STRING,\n        onClick = { dropdownMenuExpanded = true },\n        leadingImageVector = leadingImageVector,\n        trailingContent = {\n            DropdownMenu(\n                expanded = dropdownMenuExpanded,\n                onDismissRequest = { dropdownMenuExpanded = false },\n            ) {\n                selections.forEachIndexed { index, selection ->\n                    DropdownMenuItem(\n                        modifier = Modifier.background(\n                            if (selection.value == value) MaterialTheme.colorScheme.surfaceVariant\n                            else Color.Transparent\n                        ),\n                        text = { Text(selection.label) },\n                        onClick = {\n                            dropdownMenuExpanded = false\n                            onValueChanged(index, selection.value)\n                        }\n                    )\n                }\n            }\n        },\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/settings/StateBasicListItem.kt",
    "content": "package com.example.c001apk.compose.ui.component.settings\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.painter.Painter\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport androidx.compose.ui.unit.dp\n\n@Composable\nfun StateBasicListItem(\n    modifier: Modifier = Modifier,\n    isEnable: Boolean = true,\n    headlineText: String? = null,\n    supportingText: String? = null,\n    leadingImageVector: ImageVector? = null,\n    leadingPainter: Painter? = null,\n    leadingText: String? = null,\n    trailingContent: @Composable () -> Unit = {},\n    onClick: (() -> Unit)? = null,\n) {\n    val textColor =\n        if (isEnable)\n            MaterialTheme.colorScheme.onSurface\n        else\n            MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)\n    val iconColor =\n        if (isEnable)\n            MaterialTheme.colorScheme.onSurfaceVariant\n        else\n            MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f)\n    ListItem(\n        modifier = onClick?.let {\n            if (isEnable) {\n                modifier.clickable(onClick = onClick)\n            } else modifier\n        } ?: modifier,\n        headlineContent = {\n            if (headlineText != null) {\n                Text(headlineText, color = textColor)\n            }\n        },\n        supportingContent = {\n            if (supportingText != null) {\n                Text(supportingText, color = iconColor)\n            }\n        },\n        leadingContent = {\n            if (leadingText != null) {\n                Text(\n                    text = leadingText,\n                    color = MaterialTheme.colorScheme.primary,\n                    style = MaterialTheme.typography.titleSmall\n                )\n            } else if (leadingImageVector != null) {\n                Icon(\n                    imageVector = leadingImageVector,\n                    contentDescription = null,\n                    tint = iconColor\n                )\n            } else if (leadingPainter != null) {\n                Image(\n                    painter = leadingPainter, contentDescription = null, modifier = Modifier\n                        .clip(MaterialTheme.shapes.medium)\n                        .size(24.dp)\n                )\n            }\n        },\n        trailingContent = trailingContent\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/settings/StateDropdownListItem.kt",
    "content": "package com.example.c001apk.compose.ui.component.settings\n\nimport androidx.compose.foundation.background\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\n\n/**\n * Used for selections\n * If label is null, then value will be displayed on the screen\n */\n@Composable\nfun <T> StateDropdownListItem(\n    isEnable: Boolean = true,\n    value: T?,\n    leadingImageVector: ImageVector? = null,\n    headlineText: String,\n    selections: List<SelectionItem<T>>,\n    onValueChanged: (index: Int, value: T) -> Unit,\n) {\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    StateBasicListItem(\n        isEnable = isEnable,\n        headlineText = headlineText,\n        supportingText = selections.find { it.value == value }?.label ?: EMPTY_STRING,\n        onClick = { dropdownMenuExpanded = true },\n        leadingImageVector = leadingImageVector,\n        trailingContent = {\n            DropdownMenu(\n                expanded = dropdownMenuExpanded,\n                onDismissRequest = { dropdownMenuExpanded = false },\n            ) {\n                selections.forEachIndexed { index, selection ->\n                    DropdownMenuItem(\n                        modifier = Modifier.background(\n                            if (selection.value == value) MaterialTheme.colorScheme.surfaceVariant\n                            else Color.Transparent\n                        ),\n                        text = { Text(selection.label) },\n                        onClick = {\n                            dropdownMenuExpanded = false\n                            onValueChanged(index, selection.value)\n                        }\n                    )\n                }\n            }\n        },\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/component/settings/SwitchListItem.kt",
    "content": "package com.example.c001apk.compose.ui.component.settings\n\nimport androidx.compose.material3.Switch\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.graphics.vector.ImageVector\n\n@Composable\nfun SwitchListItem(\n    value: Boolean,\n    leadingImageVector: ImageVector,\n    headlineText: String,\n    supportingText: String? = null,\n    onValueChanged: (value: Boolean) -> Unit\n) {\n    BasicListItem(\n        leadingImageVector = leadingImageVector,\n        headlineText = headlineText,\n        supportingText = supportingText,\n        trailingContent = {\n            Switch(\n                checked = value,\n                onCheckedChange = {\n                    onValueChanged(it)\n                },\n            )\n        }\n    ) {\n        onValueChanged(!value)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/coolpic/CoolPicContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.coolpic\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@Composable\nfun CoolPicContentScreen(\n    title: String,\n    type: String,\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    paddingValues: PaddingValues,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<CoolPicContentViewModel, CoolPicContentViewModel.ViewModelFactory>(key = title + type) { factory ->\n            factory.create(title, type)\n        }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = paddingValues,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let{\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/coolpic/CoolPicContentViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.coolpic\n\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@HiltViewModel(assistedFactory = CoolPicContentViewModel.ViewModelFactory::class)\nclass CoolPicContentViewModel @AssistedInject constructor(\n    @Assisted(\"title\") val title: String,\n    @Assisted(\"type\") val type: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"title\") title: String,\n            @Assisted(\"type\") type: String,\n        ): CoolPicContentViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() = networkRepo.getCoolPic(title, type, page, lastItem)\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/coolpic/CoolPicScreen.kt",
    "content": "package com.example.c001apk.compose.ui.coolpic\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SecondaryTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.util.ReportType\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun CoolPicScreen(\n    onBackClick: () -> Unit,\n    title: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val tabList = listOf(\"精选\", \"热门\", \"最新\")\n    val typeList = listOf(\"recommend\", \"hot\", \"newest\")\n    val pagerState = rememberPagerState(\n        pageCount = { tabList.size }\n    )\n    val layoutDirection = LocalLayoutDirection.current\n    val scope = rememberCoroutineScope()\n    var refreshState by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = { Text(text = title) },\n            )\n        }\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(top = paddingValues.calculateTopPadding())\n        ) {\n            SecondaryTabRow(\n                modifier = Modifier\n                    .padding(\n                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                    ),\n                selectedTabIndex = pagerState.currentPage,\n                indicator = {\n                    TabRowDefaults.SecondaryIndicator(\n                        Modifier\n                            .tabIndicatorOffset(pagerState.currentPage, matchContentSize = true)\n                            .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                    )\n                },\n                divider = {}\n            ) {\n                tabList.forEachIndexed { index, tab ->\n                    Tab(\n                        selected = pagerState.currentPage == index,\n                        onClick = {\n                            if (pagerState.currentPage == index) {\n                                refreshState = true\n                            }\n                            scope.launch { pagerState.animateScrollToPage(index) }\n                        },\n                        text = { Text(text = tab) }\n                    )\n                }\n            }\n\n            HorizontalDivider()\n\n            HorizontalPager(state = pagerState) { index ->\n                CoolPicContentScreen(\n                    title = title,\n                    type = typeList[index],\n                    refreshState = refreshState,\n                    resetRefreshState = { refreshState = false },\n                    paddingValues = PaddingValues(\n                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                        bottom = paddingValues.calculateBottomPadding(),\n                    ),\n                    onViewUser = onViewUser,\n                    onViewFeed = onViewFeed,\n                    onOpenLink = onOpenLink,\n                    onCopyText = onCopyText,\n                    onReport = onReport,\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/dyh/DyhContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.dyh\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@Composable\nfun DyhContentScreen(\n    id: String,\n    type: String,\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    paddingValues: PaddingValues,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<DyhContentViewModel, DyhContentViewModel.ViewModelFactory>(key = id + type) { factory ->\n            factory.create(id, type)\n        }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = paddingValues,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let{\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/dyh/DyhContentViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.dyh\n\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@HiltViewModel(assistedFactory = DyhContentViewModel.ViewModelFactory::class)\nclass DyhContentViewModel @AssistedInject constructor(\n    @Assisted(\"id\") val id: String,\n    @Assisted(\"type\") val type: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"id\") id: String,\n            @Assisted(\"type\") type: String,\n        ): DyhContentViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() = networkRepo.getDyhDetail(id, type, page, lastItem)\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/dyh/DyhScreen.kt",
    "content": "package com.example.c001apk.compose.ui.dyh\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SecondaryTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.util.ReportType\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun DyhScreen(\n    onBackClick: () -> Unit,\n    id: String,\n    title: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val tabList = listOf(\"精选\", \"广场\")\n    val typeList = listOf(\"all\", \"square\")\n    val pagerState = rememberPagerState(\n        pageCount = { tabList.size }\n    )\n    val layoutDirection = LocalLayoutDirection.current\n    val scope = rememberCoroutineScope()\n    var refreshState by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = { Text(text = title) },\n            )\n        }\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(top = paddingValues.calculateTopPadding())\n        ) {\n            SecondaryTabRow(\n                modifier = Modifier\n                    .padding(\n                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                    ),\n                selectedTabIndex = pagerState.currentPage,\n                indicator = {\n                    TabRowDefaults.SecondaryIndicator(\n                        Modifier\n                            .tabIndicatorOffset(pagerState.currentPage, matchContentSize = true)\n                            .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                    )\n                },\n                divider = {}\n            ) {\n                tabList.forEachIndexed { index, tab ->\n                    Tab(\n                        selected = pagerState.currentPage == index,\n                        onClick = {\n                            if (pagerState.currentPage == index) {\n                                refreshState = true\n                            }\n                            scope.launch { pagerState.animateScrollToPage(index) }\n                        },\n                        text = { Text(text = tab) }\n                    )\n                }\n            }\n\n            HorizontalDivider()\n\n            HorizontalPager(state = pagerState) { index ->\n                DyhContentScreen(\n                    id = id,\n                    type = typeList[index],\n                    refreshState = refreshState,\n                    resetRefreshState = { refreshState = false },\n                    paddingValues = PaddingValues(\n                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                        bottom = paddingValues.calculateBottomPadding(),\n                    ),\n                    onViewUser = onViewUser,\n                    onViewFeed = onViewFeed,\n                    onOpenLink = onOpenLink,\n                    onCopyText = onCopyText,\n                    onReport = onReport,\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/FeedScreen.kt",
    "content": "package com.example.c001apk.compose.ui.feed\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Intent\nimport android.os.Build.VERSION.SDK_INT\nimport androidx.activity.compose.rememberLauncherForActivityResult\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.Spring.StiffnessLow\nimport androidx.compose.animation.core.spring\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.navigationBarsPadding\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.layout.wrapContentSize\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.Reply\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.ModalBottomSheet\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.material3.rememberModalBottomSheetState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.LikeType\nimport com.example.c001apk.compose.ui.component.ArticleItem\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.FooterCard\nimport com.example.c001apk.compose.ui.component.ItemCard\nimport com.example.c001apk.compose.ui.component.cards.FeedCard\nimport com.example.c001apk.compose.ui.component.cards.FeedHeader\nimport com.example.c001apk.compose.ui.component.cards.FeedReplyCard\nimport com.example.c001apk.compose.ui.component.cards.FeedReplySortCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.feed.reply.ReplyActivity\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.ShareType\nimport com.example.c001apk.compose.util.Utils.richToString\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.getAllLinkAndText\nimport com.example.c001apk.compose.util.getShareText\nimport com.example.c001apk.compose.util.isScrollingUp\nimport com.example.c001apk.compose.util.makeToast\nimport com.example.c001apk.compose.util.noRippleClickable\nimport com.example.c001apk.compose.util.shareText\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FeedScreen(\n    modifier: Modifier = Modifier,\n    isCompat: Boolean = true,\n    onBackClick: () -> Unit,\n    id: String,\n    isViewReply: Boolean,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<FeedViewModel, FeedViewModel.ViewModelFactory>(key = if (isCompat) id else \"KEY\") { factory ->\n            factory.create(id, isViewReply)\n        }\n\n\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    val state = rememberPullToRefreshState()\n    val lazyListState = rememberLazyListState()\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n\n    val scope = rememberCoroutineScope()\n    val bottomSheetState = rememberModalBottomSheetState(true)\n    var openBottomSheet by remember { mutableStateOf(false) }\n    var viewReply by remember { mutableStateOf(false) }\n    var selected by rememberSaveable { mutableIntStateOf(0) }\n    val shouldShowSortCard by remember {\n        derivedStateOf { lazyListState.firstVisibleItemIndex > viewModel.itemSize - 1 }\n    }\n    val shouldShowTopCard by remember {\n        derivedStateOf { lazyListState.firstVisibleItemIndex > 0 }\n    }\n\n    fun onShowTotalReply(id: String, uid: String, frid: String?) {\n        openBottomSheet = true\n        viewModel.replyId = id\n        viewModel.replyUid = uid\n        viewModel.frid = frid\n        viewModel.resetReplyState()\n        viewModel.fetchTotalReply()\n    }\n\n    fun setReplyType(index: Int) {\n        selected = index\n        viewModel.listType = when (index) {\n            0 -> \"lastupdate_desc\"\n            1 -> \"dateline_desc\"\n            2 -> \"popular\"\n            else -> EMPTY_STRING\n        }\n        viewModel.fromFeedAuthor = if (index == 3) 1 else 0\n    }\n\n    LaunchedEffect(id) {\n        if (id != viewModel.id) {\n            viewReply = false\n            setReplyType(0)\n            viewModel.refresh(id, isViewReply)\n        }\n    }\n\n    val articleList = remember(key1 = viewModel.feedState) { viewModel.articleList }\n\n    val replyLauncher =\n        rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->\n            if (result.resultCode == RESULT_OK) {\n                val data = if (SDK_INT >= 33)\n                    result.data?.getParcelableExtra(\n                        \"response_data\", HomeFeedResponse.Data::class.java\n                    )\n                else\n                    result.data?.getParcelableExtra(\"response_data\")\n                data?.let {\n                    viewModel.updateReply(it)\n                    context.makeToast(\"回复成功\")\n                    if (viewModel.replyType == \"feed\" && shouldShowSortCard) {\n                        scope.launch {\n                            lazyListState.scrollToItem(viewModel.itemSize)\n                        }\n                    }\n                }\n            }\n        }\n\n    fun launchReply() {\n        val intent = Intent(context, ReplyActivity::class.java)\n        intent.putExtra(\"type\", viewModel.replyType)\n        intent.putExtra(\"rid\", viewModel.replyId)\n        intent.putExtra(\"username\", viewModel.replyName)\n        val options = ActivityOptionsCompat.makeCustomAnimation(\n            context, R.anim.anim_bottom_sheet_slide_up, R.anim.anim_bottom_sheet_slide_down\n        )\n        replyLauncher.launch(intent, options)\n    }\n\n    Scaffold(\n        modifier = modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(\n                        WindowInsetsSides.Top\n                                + if (isCompat) WindowInsetsSides.Start else WindowInsetsSides.End\n                    ),\n                navigationIcon = {\n                    BackButton {\n                        if (!isCompat) {\n                            viewModel.resetState()\n                        }\n                        onBackClick()\n                    }\n                },\n                title = {\n                    Box(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .noRippleClickable {\n                                scope.launch {\n                                    lazyListState.scrollToItem(\n                                        if (shouldShowSortCard || viewReply) {\n                                            viewReply = false\n                                            0\n                                        } else {\n                                            viewReply = true\n                                            viewModel.itemSize\n                                        }\n                                    )\n                                }\n                            },\n                        contentAlignment = Alignment.CenterStart,\n                    ) {\n                        AnimatedVisibility(\n                            visible = !shouldShowTopCard,\n                            enter = fadeIn(animationSpec = spring(stiffness = StiffnessLow)),\n                            exit = fadeOut(animationSpec = spring(stiffness = StiffnessLow))\n                        ) {\n                            Text(text = viewModel.feedTypeName)\n                        }\n                    }\n                    if (viewModel.feedState is LoadingState.Success) {\n                        AnimatedVisibility(\n                            visible = shouldShowTopCard,\n                            enter = fadeIn(animationSpec = spring(stiffness = StiffnessLow)),\n                            exit = fadeOut(animationSpec = spring(stiffness = StiffnessLow))\n                        ) {\n                            FeedHeader(\n                                data = (viewModel.feedState as LoadingState.Success).response,\n                                onViewUser = onViewUser,\n                                isFeedContent = true,\n                                onReport = onReport,\n                                isFeedTop = true,\n                                onBlockUser = {},\n                            )\n                        }\n                    }\n                },\n                actions = {\n                    if (viewModel.feedState is LoadingState.Success) {\n                        Box(Modifier.wrapContentSize(Alignment.TopEnd)) {\n                            IconButton(onClick = { dropdownMenuExpanded = true }) {\n                                Icon(\n                                    Icons.Default.MoreVert,\n                                    contentDescription = null\n                                )\n                            }\n                            DropdownMenu(\n                                expanded = dropdownMenuExpanded,\n                                onDismissRequest = { dropdownMenuExpanded = false }\n                            ) {\n                                listOf(\"Copy\", \"Share\")\n                                    .forEachIndexed { index, menu ->\n                                        DropdownMenuItem(\n                                            text = { Text(menu) },\n                                            onClick = {\n                                                dropdownMenuExpanded = false\n                                                when (index) {\n                                                    0 -> context.copyText(\n                                                        getShareText(ShareType.FEED, id)\n                                                    )\n\n                                                    1 -> context.shareText(\n                                                        getShareText(ShareType.FEED, id)\n                                                    )\n                                                }\n                                            }\n                                        )\n                                    }\n                                DropdownMenuItem(\n                                    text = { Text(if (viewModel.isFav) \"UnFav\" else \"Fav\") },\n                                    onClick = {\n                                        dropdownMenuExpanded = false\n                                        viewModel.onFav()\n                                    }\n                                )\n                                DropdownMenuItem(\n                                    text = { Text(if (viewModel.isBlocked) \"UnBlock\" else \"Block\") },\n                                    onClick = {\n                                        dropdownMenuExpanded = false\n                                        viewModel.blockUser()\n                                    }\n                                )\n                                if (isLogin) {\n                                    DropdownMenuItem(\n                                        text = { Text(\"Report\") },\n                                        onClick = {\n                                            dropdownMenuExpanded = false\n                                            onReport(id, ReportType.FEED)\n                                        }\n                                    )\n                                }\n                            }\n                        }\n                    }\n                }\n            )\n        },\n        floatingActionButton = {\n            if (isLogin && viewModel.feedState is LoadingState.Success) {\n                AnimatedVisibility(\n                    modifier = if (!isCompat) Modifier.navigationBarsPadding() else Modifier,\n                    visible = lazyListState.isScrollingUp(),\n                    enter = slideInVertically { it * 2 },\n                    exit = slideOutVertically { it * 2 }\n                ) {\n                    FloatingActionButton(\n                        onClick = {\n                            viewModel.replyId = viewModel.id\n                            viewModel.replyUid = viewModel.feedUid\n                            viewModel.replyName = viewModel.feedUsername\n                            viewModel.replyType = \"feed\"\n                            launchReply()\n                        }\n                    ) {\n                        Icon(\n                            imageVector = Icons.AutoMirrored.Filled.Reply,\n                            contentDescription = null\n                        )\n                    }\n                }\n            }\n        },\n    ) { paddingValues ->\n\n        PullToRefreshBox(\n            modifier = Modifier.padding(\n                top = paddingValues.calculateTopPadding(),\n                end = if (isCompat) 0.dp else paddingValues.calculateRightPadding(layoutDirection),\n                start = if (isCompat) paddingValues.calculateLeftPadding(layoutDirection) else 0.dp\n            ),\n            state = state,\n            isRefreshing = viewModel.isRefreshing,\n            onRefresh = {\n                viewModel.isPull = true\n                viewModel.refresh()\n            },\n            indicator = {\n                PullToRefreshDefaults.Indicator(\n                    modifier = Modifier.align(Alignment.TopCenter),\n                    isRefreshing = viewModel.isRefreshing,\n                    state = state,\n                    color = MaterialTheme.colorScheme.primary,\n                )\n            }\n        ) {\n            LazyColumn(\n                modifier = Modifier.fillMaxSize(),\n                contentPadding = PaddingValues(bottom = paddingValues.calculateBottomPadding()),\n                state = lazyListState\n            ) {\n                when (viewModel.feedState) {\n                    LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                        item(key = \"feedState\") {\n                            Box(modifier = Modifier.fillParentMaxSize()) {\n                                LoadingCard(\n                                    modifier = Modifier.align(Alignment.Center),\n                                    state = viewModel.feedState,\n                                    onClick = if (viewModel.feedState is LoadingState.Loading) null\n                                    else viewModel::refresh,\n                                    isFeed = true,\n                                )\n                            }\n                        }\n                    }\n\n                    is LoadingState.Success -> {\n                        val response = (viewModel.feedState as LoadingState.Success).response\n                        if (!articleList.isNullOrEmpty()) {\n                            ArticleItem(\n                                response = response,\n                                articleList = articleList,\n                                onOpenLink = onOpenLink,\n                                onCopyText = onCopyText,\n                                onLike = {\n                                    if (isLogin) {\n                                        viewModel.onLike(\n                                            response.id.orEmpty(),\n                                            response.userAction?.like ?: 0,\n                                            LikeType.FEED\n                                        )\n                                    }\n                                },\n                                onViewUser = onViewUser,\n                            )\n                        } else {\n                            item(key = \"header\") {\n                                FeedHeader(\n                                    modifier = Modifier.padding(horizontal = 16.dp),\n                                    data = response,\n                                    onViewUser = onViewUser,\n                                    isFeedContent = true,\n                                    isFeedTop = false,\n                                )\n                            }\n                            item(key = \"feed\") {\n                                FeedCard(\n                                    isFeedContent = true,\n                                    data = response,\n                                    onViewUser = onViewUser,\n                                    onViewFeed = onViewFeed,\n                                    onOpenLink = onOpenLink,\n                                    onCopyText = onCopyText,\n                                    onReport = onReport,\n                                    onLike = viewModel::onLike,\n                                    onDelete = { id, deleteType, frid ->\n                                        viewModel.frid = frid\n                                        viewModel.onDelete(id, deleteType)\n                                    },\n                                    onBlockUser = {},\n                                )\n                            }\n                        }\n\n                        item(key = \"sort\") {\n                            FeedReplySortCard(\n                                replyCount = viewModel.replyCount,\n                                selected = selected,\n                                updateSortReply = { index ->\n                                    setReplyType(index)\n                                    if (shouldShowSortCard)\n                                        viewModel.isViewReply = true\n                                    viewModel.refresh()\n                                }\n                            )\n                            HorizontalDivider()\n                        }\n\n                        if (viewModel.listType == \"lastupdate_desc\") {\n                            if (!response.topReplyRows.isNullOrEmpty()) {\n                                response.topReplyRows?.getOrNull(0)?.let { reply ->\n                                    item(key = \"topReplyRows\") {\n                                        FeedReplyCard(\n                                            data = reply,\n                                            onViewUser = onViewUser,\n                                            onShowTotalReply = ::onShowTotalReply,\n                                            onOpenLink = onOpenLink,\n                                            onCopyText = onCopyText,\n                                            onReport = onReport,\n                                            onLike = viewModel::onLike,\n                                            onDelete = { id, deleteType, frid ->\n                                                viewModel.frid = frid\n                                                viewModel.onDelete(id, deleteType)\n                                            },\n                                            onBlockUser = { uid, frid ->\n                                                viewModel.frid = frid\n                                                viewModel.onBlockUser(uid)\n                                            },\n                                            onReply = { rid, uid, username, frid ->\n                                                viewModel.replyId = rid\n                                                viewModel.replyUid = uid\n                                                viewModel.replyName = username\n                                                viewModel.frid = frid\n                                                viewModel.replyType = \"reply\"\n                                                launchReply()\n                                            },\n                                        )\n                                        HorizontalDivider()\n                                    }\n                                }\n                            }\n\n                            if (!response.replyMeRows.isNullOrEmpty()) {\n                                response.replyMeRows?.getOrNull(0)?.let { reply ->\n                                    item(key = \"replyMeRows\") {\n                                        FeedReplyCard(\n                                            data = reply,\n                                            onViewUser = onViewUser,\n                                            onShowTotalReply = ::onShowTotalReply,\n                                            onOpenLink = onOpenLink,\n                                            onCopyText = onCopyText,\n                                            onReport = onReport,\n                                            onLike = viewModel::onLike,\n                                            onDelete = { id, deleteType, frid ->\n                                                viewModel.frid = frid\n                                                viewModel.onDelete(id, deleteType)\n                                            },\n                                            onBlockUser = { uid, frid ->\n                                                viewModel.frid = frid\n                                                viewModel.onBlockUser(uid)\n                                            },\n                                            onReply = { rid, uid, username, frid ->\n                                                viewModel.replyId = rid\n                                                viewModel.replyUid = uid\n                                                viewModel.replyName = username\n                                                viewModel.frid = frid\n                                                viewModel.replyType = \"reply\"\n                                                launchReply()\n                                            },\n                                        )\n                                        HorizontalDivider()\n                                    }\n                                }\n                            }\n                        }\n                        if (viewModel.isViewReply) {\n                            viewModel.isViewReply = false\n                            scope.launch {\n                                lazyListState.scrollToItem(viewModel.itemSize)\n                            }\n                        }\n                    }\n                }\n\n                if (viewModel.feedState is LoadingState.Success) {\n\n                    ItemCard(\n                        loadingState = viewModel.loadingState,\n                        loadMore = viewModel::loadMore,\n                        isEnd = viewModel.isEnd,\n                        onViewUser = onViewUser,\n                        onViewFeed = onViewFeed,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText,\n                        onShowTotalReply = ::onShowTotalReply,\n                        onReport = onReport,\n                        onLike = viewModel::onLike,\n                        onDelete = { id, deleteType, frid ->\n                            viewModel.frid = frid\n                            viewModel.onDelete(id, deleteType)\n                        },\n                        onBlockUser = { uid, frid ->\n                            viewModel.frid = frid\n                            viewModel.onBlockUser(uid)\n                        },\n                        onFollowUser = viewModel::onFollowUser,\n                        onReply = { rid, uid, username, frid ->\n                            viewModel.replyId = rid\n                            viewModel.replyUid = uid\n                            viewModel.replyName = username\n                            viewModel.frid = frid\n                            viewModel.replyType = \"reply\"\n                            launchReply()\n                        },\n                    )\n\n                    FooterCard(\n                        footerState = viewModel.footerState,\n                        loadMore = viewModel::loadMore,\n                        isFeed = true\n                    )\n\n                }\n\n            }\n            if (shouldShowSortCard) {\n                FeedReplySortCard(\n                    replyCount = viewModel.replyCount,\n                    selected = selected,\n                    updateSortReply = { index ->\n                        setReplyType(index)\n                        viewModel.isViewReply = true\n                        viewModel.refresh()\n                    }\n                )\n            }\n\n        }\n\n    }\n\n    fun resetBottomSheet() {\n        openBottomSheet = false\n        viewModel.resetReplyState()\n    }\n\n    if (openBottomSheet) {\n\n        ModalBottomSheet(\n            onDismissRequest = {\n                resetBottomSheet()\n            },\n            sheetState = bottomSheetState,\n        ) {\n            LazyColumn(modifier = Modifier.fillMaxSize()) {\n\n                ItemCard(\n                    loadingState = viewModel.replyLoadingState,\n                    loadMore = viewModel::loadMoreReply,\n                    isEnd = viewModel.isEndReply,\n                    onViewUser = { uid ->\n                        resetBottomSheet()\n                        onViewUser(uid)\n                    },\n                    onViewFeed = { id, isViewReply ->\n                        resetBottomSheet()\n                        onViewFeed(id, isViewReply)\n                    },\n                    onOpenLink = { url, title ->\n                        resetBottomSheet()\n                        onOpenLink(url, title)\n                    },\n                    onCopyText = {\n                        context.copyText(it?.getAllLinkAndText?.richToString())\n                    },\n                    onReport = { id, type ->\n                        resetBottomSheet()\n                        onReport(id, type)\n                    },\n                    isTotalReply = true,\n                    onLike = { id, like, likeType ->\n                        viewModel.onLikeReply(id, like, likeType)\n                    },\n                    onDelete = { id, deleteType, _ ->\n                        viewModel.onDeleteRely(id, deleteType)\n                    },\n                    onBlockUser = { uid, _ ->\n                        viewModel.onBlockReplyUser(uid)\n                    },\n                    isReply2Reply = !viewModel.frid.isNullOrEmpty(),\n                    onShowTotalReply = { id, uid, frid ->\n                        viewModel.replyId = id\n                        viewModel.replyUid = uid\n                        viewModel.frid = frid\n                        viewModel.resetReplyState()\n                        viewModel.fetchTotalReply()\n                    },\n                    onReply = { rid, uid, username, _ ->\n                        viewModel.isSheet = true\n                        viewModel.replyId = rid\n                        viewModel.replyUid = uid\n                        viewModel.replyName = username\n                        viewModel.frid = null\n                        viewModel.replyType = \"reply\"\n                        launchReply()\n                    },\n                )\n\n                FooterCard(\n                    footerState = viewModel.replyFooterState,\n                    loadMore = viewModel::loadMoreReply,\n                    isFeed = true\n                )\n            }\n        }\n    }\n\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/FeedViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.feed\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.FeedArticleContentBean\nimport com.example.c001apk.compose.logic.model.FeedEntity\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.HistoryFavoriteRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.FooterState\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport com.example.c001apk.compose.ui.base.LikeType\nimport com.example.c001apk.compose.ui.history.HistoryType\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.google.gson.Gson\nimport com.google.gson.reflect.TypeToken\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@HiltViewModel(assistedFactory = FeedViewModel.ViewModelFactory::class)\nclass FeedViewModel @AssistedInject constructor(\n    @Assisted var id: String,\n    @Assisted var isViewReply: Boolean,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n    private val historyFavoriteRepo: HistoryFavoriteRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(id: String, isViewReply: Boolean): FeedViewModel\n    }\n\n    var feedState by mutableStateOf<LoadingState<HomeFeedResponse.Data>>(LoadingState.Loading)\n        private set\n\n    var itemSize = 2\n\n    lateinit var replyType: String\n    private var discussMode: Int = 1\n    var listType: String = \"lastupdate_desc\"\n    var feedType: String = \"feed\"\n    private var blockStatus = 0\n    var fromFeedAuthor = 0\n\n    private var topId: String? = null\n    private var meId: String? = null\n\n    var articleList: List<FeedArticleContentBean>? = null\n\n    init {\n        fetchFeedData()\n    }\n\n    var replyCount by mutableStateOf(\"0\")\n    var isFav by mutableStateOf(false)\n    var isBlocked by mutableStateOf(false)\n\n    private suspend fun recordHistory(\n        username: String,\n        avatar: String,\n        device: String,\n        message: String,\n        pubDate: String,\n        type: HistoryType = HistoryType.HISTORY\n    ) {\n        when (type) {\n            HistoryType.FAV -> historyFavoriteRepo.insertFavorite(\n                FeedEntity(id, feedUid, username, avatar, device, message, pubDate)\n            )\n\n            HistoryType.HISTORY -> historyFavoriteRepo.insertHistory(\n                FeedEntity(id, feedUid, username, avatar, device, message, pubDate)\n            )\n        }\n\n    }\n\n    private fun fetchFeedData() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getFeedContent(\"/v6/feed/detail?id=$id\")\n                .collect { state ->\n                    if (state is LoadingState.Success) {\n                        val response = state.response\n\n                        feedUid = response.uid.orEmpty()\n                        feedUsername = response.username.orEmpty()\n                        replyCount = response.replynum.orEmpty()\n                        feedTypeName = response.feedTypeName.orEmpty()\n                        feedType = response.feedType.orEmpty()\n\n                        if (response.messageRawOutput != \"null\") {\n                            val listType =\n                                object : TypeToken<List<FeedArticleContentBean?>?>() {}.type\n                            val message: List<FeedArticleContentBean> =\n                                Gson().fromJson(response.messageRawOutput, listType)\n\n                            articleList = message.filter {\n                                it.type in listOf(\"text\", \"image\", \"shareUrl\")\n                            }\n\n                            if (!response.messageCover.isNullOrEmpty()) itemSize++\n                            if (!response.title.isNullOrEmpty()) itemSize++\n                            if (response.targetRow != null || !response.relationRows.isNullOrEmpty())\n                                itemSize++\n                            itemSize += articleList?.size ?: 0\n                        }\n\n                        if (!response.topReplyRows.isNullOrEmpty()) {\n                            response.topReplyRows?.getOrNull(0)?.let { reply ->\n                                topId = reply.id\n                                val unameTag = when (reply.uid) {\n                                    feedUid -> \" [楼主]\"\n                                    else -> EMPTY_STRING\n                                }\n                                reply.username = \"${reply.username}$unameTag [置顶]\"\n                                if (!reply.replyRows.isNullOrEmpty()) {\n                                    reply.replyRows = reply.replyRows?.map {\n                                        it.copy(\n                                            message = generateMess(\n                                                it,\n                                                feedUid,\n                                                reply.uid\n                                            )\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                        if (!response.replyMeRows.isNullOrEmpty()) {\n                            response.replyMeRows?.getOrNull(0)?.let { reply ->\n                                meId = reply.id\n                                if (!reply.replyRows.isNullOrEmpty()) {\n                                    reply.replyRows = reply.replyRows?.map {\n                                        it.copy(\n                                            message = generateMess(\n                                                it,\n                                                feedUid,\n                                                reply.uid\n                                            )\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                        fetchData()\n\n                        if (CookieUtil.recordHistory && !historyFavoriteRepo.checkHistory(id)) {\n                            recordHistory(\n                                username = response.username.orEmpty(),\n                                avatar = response.userAvatar.orEmpty(),\n                                device = response.deviceTitle.orEmpty(),\n                                message = with(response.message.orEmpty()) {\n                                    if (this.length > 150) this.substring(0, 150) else this\n                                },\n                                pubDate = response.dateline.toString()\n                            )\n                        }\n                        isFav = historyFavoriteRepo.checkFavorite(id)\n                        isBlocked = blackListRepo.checkUid(feedUid)\n                    }\n                    feedState = state\n                    isRefreshing = false\n                }\n        }\n    }\n\n    lateinit var feedUid: String\n    lateinit var feedUsername: String\n    var feedTypeName by mutableStateOf(EMPTY_STRING)\n\n    override suspend fun customFetchData() =\n        networkRepo.getFeedContentReply(\n            id, listType, page, firstItem, lastItem, discussMode,\n            feedType, blockStatus, fromFeedAuthor\n        )\n\n    override fun handleResponse(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        response.forEach { item ->\n            item.username =\n                \"${item.username}${if (item.uid == feedUid) \" [楼主]\" else EMPTY_STRING}\"\n            if (!item.replyRows.isNullOrEmpty()) {\n                item.replyRows = item.replyRows?.map {\n                    it.copy(\n                        message = generateMess(\n                            it,\n                            feedUid,\n                            item.uid\n                        )\n                    )\n                }\n            }\n        }\n\n        return response.distinctBy { it.entityId }\n            .filterNot {\n                if (listType == \"lastupdate_desc\")\n                    it.id in listOf(topId, meId)\n                else false\n            }\n    }\n\n    var isPull = false\n    override fun refresh() {\n        if (!isRefreshing && !isLoadMore) {\n            if (feedState is LoadingState.Success) {\n                resetParams()\n                isRefreshing = true\n                fetchData()\n            } else {\n                if (isPull) {\n                    isPull = false\n                    viewModelScope.launch {\n                        isRefreshing = true\n                        delay(50)\n                        isRefreshing = false\n                    }\n                }\n                feedState = LoadingState.Loading\n                fetchFeedData()\n            }\n        }\n    }\n\n    private fun generateMess(\n        reply: HomeFeedResponse.Data,\n        feedUid: String?,\n        uid: String?\n    ): String = run {\n        val replyTag =\n            when (reply.uid) {\n                feedUid -> \" [楼主] \"\n                uid -> \" [层主] \"\n                else -> EMPTY_STRING\n            }\n\n        val rReplyTag =\n            when (reply.ruid) {\n                feedUid -> \" [楼主] \"\n                uid -> \" [层主] \"\n                else -> EMPTY_STRING\n            }\n\n        val rReplyUser =\n            when (reply.ruid) {\n                uid -> EMPTY_STRING\n                else -> \"\"\"<a class=\"feed-link-uname\" href=\"/u/${reply.ruid}\">${reply.rusername}${rReplyTag}</a>\"\"\"\n            }\n\n        val replyPic =\n            when (reply.pic) {\n                EMPTY_STRING -> EMPTY_STRING\n                else -> \"\"\" <a class=\\\"feed-forward-pic\\\" href=${reply.pic}>查看图片(${reply.picArr?.size})</a>\"\"\"\n            }\n\n        \"\"\"<a class=\"feed-link-uname\" href=\"/u/${reply.uid}\">${reply.username}${replyTag}</a>回复${rReplyUser}: ${reply.message}${replyPic}\"\"\"\n\n    }\n\n    var isSheet: Boolean = false\n    var frid: String? = null\n    lateinit var replyId: String\n    lateinit var replyUid: String\n    lateinit var replyName: String\n    private var replyPage = 1\n    private var replyLastItem: String? = null\n    var isEndReply = false\n    private var isLoadMoreReply = false\n    var replyLoadingState by mutableStateOf<LoadingState<List<HomeFeedResponse.Data>>>(LoadingState.Loading)\n        private set\n    var replyFooterState by mutableStateOf<FooterState>(FooterState.Success)\n        private set\n\n    private fun getReplyTop(): HomeFeedResponse.Data? {\n        val replyTop =\n            when (frid ?: replyId) {\n                topId ->\n                    (feedState as LoadingState.Success).response.topReplyRows?.getOrNull(0)\n\n                meId ->\n                    (feedState as LoadingState.Success).response.replyMeRows?.getOrNull(0)\n\n                else ->\n                    (loadingState as LoadingState.Success).response.find {\n                        it.id == (frid ?: replyId)\n                    }\n            }\n        return if (!frid.isNullOrEmpty())\n            replyTop?.replyRows?.find { it.id == replyId }\n        else replyTop\n    }\n\n    fun fetchTotalReply() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getReply2Reply(replyId, replyPage, replyLastItem)\n                .collect { state ->\n                    when (state) {\n                        LoadingState.Empty -> {\n                            if (replyLoadingState is LoadingState.Success && !isRefreshing)\n                                replyFooterState = FooterState.End\n                            else {\n                                replyLoadingState = getReplyTop()?.let {\n                                    LoadingState.Success(listOf(it))\n                                } ?: state\n                                replyFooterState = FooterState.End\n                            }\n                            isEndReply = true\n                        }\n\n                        is LoadingState.Error -> {\n                            if (replyLoadingState is LoadingState.Success)\n                                replyFooterState = FooterState.Error(state.errMsg)\n                            else\n                                replyLoadingState = state\n                            isEndReply = true\n                        }\n\n                        LoadingState.Loading -> {\n                            if (replyLoadingState is LoadingState.Success)\n                                replyFooterState = FooterState.Loading\n                            else\n                                replyLoadingState = state\n                        }\n\n                        is LoadingState.Success -> {\n                            replyPage++\n                            var response = state.response.filter {\n                                it.entityType == \"feed_reply\"\n                            }\n                            response = response.map { reply ->\n                                reply.copy(\n                                    username = generateName(reply, replyUid)\n                                )\n                            }\n                            if (replyPage == 2) {\n                                getReplyTop()?.let {\n                                    response = listOf(it) + response\n                                }\n                            }\n                            replyLastItem = response.lastOrNull()?.id\n                            replyLoadingState =\n                                if (isLoadMoreReply)\n                                    LoadingState.Success(\n                                        (((replyLoadingState as? LoadingState.Success)?.response\n                                            ?: emptyList()) + response).distinctBy { it.entityId }\n                                    )\n                                else\n                                    LoadingState.Success(response)\n                            replyFooterState = FooterState.Success\n                        }\n                    }\n                    isLoadMoreReply = false\n                }\n        }\n    }\n\n    private fun generateName(data: HomeFeedResponse.Data, uid: String): String = run {\n        val replyTag =\n            when (data.uid) {\n                feedUid -> \" [楼主] \"\n                uid -> \" [层主] \"\n                else -> EMPTY_STRING\n            }\n\n        val rReplyTag =\n            when (data.ruid) {\n                feedUid -> \" [楼主] \"\n                uid -> \" [层主] \"\n                else -> EMPTY_STRING\n            }\n\n        if (data.ruid == \"0\")\n            \"\"\"<a class=\"feed-link-uname\" href=\"/u/${data.uid}\">${data.username}$replyTag</a>\"\"\"\n        else\n            \"\"\"<a class=\"feed-link-uname\" href=\"/u/${data.uid}\">${data.username}$replyTag</a>回复<a class=\"feed-link-uname\" href=\"/u/${data.rusername}\">${data.rusername}$rReplyTag</a>\"\"\"\n    }\n\n    fun resetReplyState() {\n        replyPage = 1\n        isLoadMoreReply = false\n        isEndReply = false\n        replyLastItem = null\n        frid = null\n        replyLoadingState = LoadingState.Loading\n        replyFooterState = FooterState.Success\n    }\n\n    fun loadMoreReply() {\n        if (!isLoadMoreReply) {\n            isEndReply = false\n            isLoadMoreReply = true\n            fetchTotalReply()\n            if (replyLoadingState is LoadingState.Success) {\n                replyFooterState = FooterState.Loading\n            } else {\n                replyLoadingState = LoadingState.Loading\n            }\n        }\n    }\n\n    override fun handleLikeResponse(id: String, like: Int, count: String?): Boolean? {\n        return if (id in listOf(this.id, topId, meId)) {\n            var response = (feedState as LoadingState.Success).response\n            val isLike = if (like == 1) 0 else 1\n            when (id) {\n                this.id -> {\n                    response = response.copy(\n                        likenum = count,\n                        userAction = HomeFeedResponse.UserAction(isLike)\n                    )\n                }\n\n                topId -> {\n                    response.topReplyRows?.getOrNull(0)?.let {\n                        response = response.copy(\n                            topReplyRows = listOf(\n                                it.copy(\n                                    likenum = count,\n                                    userAction = HomeFeedResponse.UserAction(isLike)\n                                )\n                            )\n                        )\n                    }\n\n                }\n\n                meId -> {\n                    response.replyMeRows?.getOrNull(0)?.let {\n                        response = response.copy(\n                            replyMeRows = listOf(\n                                it.copy(\n                                    likenum = count,\n                                    userAction = HomeFeedResponse.UserAction(isLike)\n                                )\n                            )\n                        )\n                    }\n\n                }\n            }\n            feedState = LoadingState.Success(response)\n            true\n        } else null\n    }\n\n    fun blockUser() {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (isBlocked)\n                blackListRepo.deleteUid(feedUid)\n            else\n                blackListRepo.saveUid(feedUid)\n            isBlocked = !isBlocked\n        }\n    }\n\n    fun onFav() {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (isFav) {\n                historyFavoriteRepo.deleteFavorite(id)\n            } else {\n                val response = (feedState as LoadingState.Success).response\n                recordHistory(\n                    username = response.username.orEmpty(),\n                    avatar = response.userAvatar.orEmpty(),\n                    device = response.deviceTitle.orEmpty(),\n                    message = with(response.message.orEmpty()) {\n                        if (this.length > 150) this.substring(0, 150) else this\n                    },\n                    pubDate = response.dateline.toString(),\n                    type = HistoryType.FAV\n                )\n            }\n            isFav = !isFav\n        }\n    }\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n    override fun handleBlockUser(\n        uid: String,\n        response: List<HomeFeedResponse.Data>\n    ): List<HomeFeedResponse.Data>? {\n        var feedResponse = (feedState as LoadingState.Success).response\n        feedResponse.topReplyRows?.getOrNull(0)?.let { reply ->\n            feedResponse = if (reply.uid == uid)\n                feedResponse.copy(topReplyRows = null)\n            else\n                feedResponse.copy(\n                    topReplyRows = listOf(\n                        reply.copy(\n                            replyRows = reply.replyRows?.filterNot { it.uid == uid }\n                        )\n                    )\n                )\n        }\n        feedResponse.replyMeRows?.getOrNull(0)?.let { reply ->\n            feedResponse = if (reply.uid == uid)\n                feedResponse.copy(replyMeRows = null)\n            else\n                feedResponse.copy(\n                    replyMeRows = listOf(\n                        reply.copy(\n                            replyRows = reply.replyRows?.filterNot { it.uid == uid }\n                        )\n                    )\n                )\n        }\n        feedState = LoadingState.Success(feedResponse)\n        return if (frid.isNullOrEmpty()) null\n        else response.map { item ->\n            if (item.id == frid) {\n                item.copy(replyRows = item.replyRows?.filterNot { it.uid == uid })\n            } else item\n        }\n    }\n\n    fun onBlockReplyUser(uid: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            blackListRepo.saveUid(uid)\n\n            if (replyLoadingState is LoadingState.Success) {\n                val response =\n                    (replyLoadingState as LoadingState.Success).response.filterNot { it.uid == uid }\n                replyLoadingState = LoadingState.Success(response)\n            }\n        }\n    }\n\n    fun onDeleteRely(id: String, deleteType: LikeType) {\n        val url = if (deleteType == LikeType.FEED) \"/v6/feed/deleteFeed\"\n        else \"/v6/feed/deleteReply\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postDelete(url, id)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (!response.message.isNullOrEmpty()) {\n                            toastText = response.message\n                        } else if (response.data?.count == \"删除成功\") {\n                            toastText = response.data.count\n                            val dataList =\n                                (replyLoadingState as LoadingState.Success).response.filterNot { it.id == id }\n                            replyLoadingState = LoadingState.Success(dataList)\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    fun onLikeReply(id: String, like: Int, likeType: LikeType) {\n        val isLike = when (likeType) {\n            LikeType.FEED -> if (like == 1) \"unlike\" else \"like\"\n            LikeType.REPLY -> if (like == 1) \"unLikeReply\" else \"likeReply\"\n        }\n        val likeUrl = \"/v6/feed/$isLike\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postLike(likeUrl, id)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (response.data != null) {\n                            if (!response.message.isNullOrEmpty()) {\n                                toastText = response.message\n                            } else if (handleLikeResponse(id, like, response.data.count) == null) {\n                                val dataList =\n                                    (replyLoadingState as LoadingState.Success).response.map {\n                                        if (it.id == id) {\n                                            it.copy(\n                                                likenum = response.data.count,\n                                                userAction = it.userAction?.copy(like = if (like == 1) 0 else 1)\n                                            )\n                                        } else it\n                                    }\n                                replyLoadingState = LoadingState.Success(dataList)\n                            }\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    fun updateReply(data: HomeFeedResponse.Data) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (isSheet) {\n                isSheet = false\n                val reply = data.copy(username = generateName(data, replyUid))\n                var response = (replyLoadingState as? LoadingState.Success)?.response ?: emptyList()\n                val position = response.map { it.id }.indexOf(replyId)\n                response = response.toMutableList().also {\n                    it.add(position + 1, reply)\n                }\n                replyLoadingState = LoadingState.Success(response)\n            } else {\n                val reply = data.copy(message = generateMess(data, feedUid, replyUid))\n                if ((frid ?: replyId) in listOf(topId, meId)) {\n                    var feedResponse = (feedState as LoadingState.Success).response\n                    if ((frid ?: replyId) == topId)\n                        feedResponse.topReplyRows?.getOrNull(0)?.let {\n                            feedResponse = feedResponse.copy(\n                                topReplyRows = listOf(\n                                    it.copy(\n                                        replyRows = (it.replyRows ?: emptyList()) + listOf(reply)\n                                    )\n                                )\n                            )\n                        }\n                    else\n                        feedResponse.replyMeRows?.getOrNull(0)?.let {\n                            feedResponse = feedResponse.copy(\n                                replyMeRows = listOf(\n                                    it.copy(\n                                        replyRows = (it.replyRows ?: emptyList()) + listOf(reply)\n                                    )\n                                )\n                            )\n                        }\n                    feedState = LoadingState.Success(feedResponse)\n                } else {\n                    var response = (loadingState as? LoadingState.Success)?.response ?: emptyList()\n                    response = if (replyType == \"feed\") {\n                        listOf(data) + response\n                    } else {\n                        response.map { item ->\n                            if (item.id == (frid ?: replyId)) {\n                                item.copy(\n                                    replyRows = (item.replyRows ?: emptyList()) + listOf(reply)\n                                )\n                            } else item\n                        }\n                    }\n                    loadingState = LoadingState.Success(response)\n                }\n            }\n        }\n    }\n\n    override fun handleDeleteResponse(\n        id: String,\n        response: List<HomeFeedResponse.Data>\n    ): List<HomeFeedResponse.Data>? {\n        return if (!frid.isNullOrEmpty()) {\n            when (frid) {\n                topId, meId -> {\n                    var feedResponse = (feedState as LoadingState.Success).response\n                    if (frid == topId)\n                        feedResponse.topReplyRows?.getOrNull(0)?.let { reply ->\n                            feedResponse = feedResponse.copy(\n                                topReplyRows = listOf(\n                                    reply.copy(replyRows = reply.replyRows?.filterNot { it.id == id })\n                                )\n                            )\n                        }\n                    else\n                        feedResponse.replyMeRows?.getOrNull(0)?.let { reply ->\n                            feedResponse = feedResponse.copy(\n                                replyMeRows = listOf(\n                                    reply.copy(replyRows = reply.replyRows?.filterNot { it.id == id })\n                                )\n                            )\n                        }\n                    feedState = LoadingState.Success(feedResponse)\n                    null\n                }\n\n                else -> {\n                    response.map { item ->\n                        if (item.id == frid)\n                            item.copy(replyRows = item.replyRows?.filterNot { it.id == id })\n                        else item\n                    }\n                }\n            }\n        } else {\n            if (id in listOf(topId, meId)) {\n                var feedResponse = (feedState as LoadingState.Success).response\n                feedResponse = if (id == topId)\n                    feedResponse.copy(topReplyRows = null)\n                else\n                    feedResponse.copy(replyMeRows = null)\n                feedState = LoadingState.Success(feedResponse)\n                null\n            } else super.handleDeleteResponse(id, response)\n        }\n    }\n\n    fun refresh(id: String, isViewReply: Boolean) {\n        this.id = id\n        this.isViewReply = isViewReply\n        resetParams()\n        isRefreshing = false\n        articleList = null\n        itemSize = 2\n        resetState()\n        refresh()\n    }\n\n    private fun resetParams() {\n        page = 1\n        isEnd = false\n        isLoadMore = false\n        firstItem = null\n        lastItem = null\n    }\n\n    fun resetState() {\n        feedTypeName = EMPTY_STRING\n        feedState = LoadingState.Loading\n        loadingState = LoadingState.Loading\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/reply/ReplyActivity.kt",
    "content": "package com.example.c001apk.compose.ui.feed.reply\n\nimport android.annotation.SuppressLint\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.Intent\nimport android.graphics.Color\nimport android.graphics.drawable.GradientDrawable\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.CountDownTimer\nimport android.util.Log\nimport android.view.Gravity\nimport android.view.KeyEvent\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.View.OnTouchListener\nimport android.view.View.VISIBLE\nimport android.view.WindowManager\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.ImageView\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport android.widget.Toast\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.PickVisualMediaRequest\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.content.res.AppCompatResources\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.HapticFeedbackConstantsCompat\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.viewpager2.widget.ViewPager2\nimport coil.load\nimport com.example.c001apk.compose.BuildConfig\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.databinding.ActivityReplyBinding\nimport com.example.c001apk.compose.databinding.ItemCaptchaBinding\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareModel\nimport com.example.c001apk.compose.ui.feed.reply.emoji.EmojiPagerAdapter\nimport com.example.c001apk.compose.util.CookieUtil.materialYou\nimport com.example.c001apk.compose.util.EmojiTextWatcher\nimport com.example.c001apk.compose.util.EmojiUtils\nimport com.example.c001apk.compose.util.EmojiUtils.coolBList\nimport com.example.c001apk.compose.util.EmojiUtils.emojiList\nimport com.example.c001apk.compose.util.FastDeleteAtUserKeyListener\nimport com.example.c001apk.compose.util.OSSUtil.getImageDimensionsAndMD5\nimport com.example.c001apk.compose.util.OSSUtil.toHex\nimport com.example.c001apk.compose.util.OnTextInputListener\nimport com.example.c001apk.compose.util.OssUploadUtil.ossUpload\nimport com.example.c001apk.compose.util.dp\nimport com.example.c001apk.compose.util.makeToast\nimport com.example.c001apk.compose.view.SmoothInputLayout\nimport com.google.android.material.color.DynamicColors\nimport com.google.android.material.color.MaterialColors\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport com.google.android.material.elevation.SurfaceColors\nimport dagger.hilt.android.AndroidEntryPoint\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport java.util.UUID\n\n\n/*\n* Copyright (C) 2018 AlexMofer\n*\n* Licensed under the Apache License, Version 2.0 (the \"License\");\n* you may not use this file except in compliance with the License.\n* You may obtain a copy of the License at\n*\n*      http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing, software\n* distributed under the License is distributed on an \"AS IS\" BASIS,\n* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n* See the License for the specific language governing permissions and\n* limitations under the License.\n*/ /**\n * 输入面板\n */\n\n@AndroidEntryPoint\nclass ReplyActivity : AppCompatActivity(),\n    View.OnClickListener, OnTouchListener, SmoothInputLayout.OnVisibilityChangeListener,\n    SmoothInputLayout.OnKeyboardChangeListener {\n\n    private lateinit var binding: ActivityReplyBinding\n    private val viewModel by viewModels<ReplyViewModel>()\n    private val type: String? by lazy { intent.getStringExtra(\"type\") }\n    private val rid: String? by lazy { intent.getStringExtra(\"rid\") }\n    private val username: String? by lazy { intent.getStringExtra(\"username\") }\n\n    private val targetType: String? by lazy { intent.getStringExtra(\"targetType\") }\n    private val targetId: String? by lazy { intent.getStringExtra(\"targetId\") }\n    private val title: String? by lazy { intent.getStringExtra(\"title\") }\n\n    private val imm by lazy {\n        getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n    }\n    private val color by lazy { SurfaceColors.SURFACE_1.getColor(this) }\n    private val recentList = ArrayList<List<Pair<String, Int>>>()\n    private val list = listOf(recentList, emojiList, coolBList)\n    private lateinit var pickMultipleMedia: ActivityResultLauncher<PickVisualMediaRequest>\n    private var uriList: MutableList<Uri> = ArrayList()\n    private var imageList = ArrayList<OSSUploadPrepareModel>()\n    private var typeList = ArrayList<String>()\n    private var md5List = ArrayList<ByteArray?>()\n    private var dialog: AlertDialog? = null\n    private lateinit var atTopicResultLauncher: ActivityResultLauncher<Intent>\n    private var isFromAt = false\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        if (materialYou)\n            DynamicColors.applyToActivityIfAvailable(this)\n        super.onCreate(savedInstanceState)\n        binding = ActivityReplyBinding.inflate(layoutInflater)\n        setContentView(binding.root)\n\n        viewModel.type = type\n        viewModel.rid = rid\n\n        initView()\n        initEditText()\n        initPage()\n        initEmojiPanel()\n        initObserve()\n        initPhotoPick()\n        initAtUser()\n\n    }\n\n    private fun initAtUser() {\n        atTopicResultLauncher =\n            registerForActivityResult(ActivityResultContracts.StartActivityForResult())\n            { result: ActivityResult ->\n                if (result.resultCode == RESULT_OK) {\n                    val list = result.data?.getStringExtra(\"data\")\n                    if (isFromAt) {\n                        isFromAt = false\n                        with(binding.editText.selectionStart) {\n                            binding.editText.editableText.replace(this - 1, this, list)\n                        }\n                    } else {\n                        binding.editText.editableText.append(list)\n                    }\n                }\n            }\n    }\n\n    override fun onResume() {\n        super.onResume()\n        lifecycleScope.launch(Dispatchers.Main) {\n            delay(150)\n            showInput()\n        }\n    }\n\n\n    private fun initPhotoPick() {\n        pickMultipleMedia =\n            registerForActivityResult(ActivityResultContracts.PickMultipleVisualMedia(9)) { uris ->\n                if (uris.isNotEmpty()) {\n                    runCatching {\n                        uris.forEach { uri ->\n                            if (uriList.size == 9) {\n                                Toast.makeText(this, \"最多选择9张图片\", Toast.LENGTH_SHORT).show()\n                                return@registerForActivityResult\n                            }\n\n                            val result = getImageDimensionsAndMD5(contentResolver, uri)\n                            val md5Byte = result.second\n                            val md5 = md5Byte?.toHex() ?: \"\"\n                            val width = result.first?.first ?: 0\n                            val height = result.first?.second ?: 0\n                            val type = result.first?.third ?: \"\"\n\n                            typeList.add(type)\n                            md5List.add(md5Byte)\n                            imageList.add(\n                                OSSUploadPrepareModel(\n                                    name = \"${\n                                        UUID.randomUUID().toString().replace(\"-\", \"\")\n                                    }.${if (type.startsWith(\"image/\")) type.substring(6) else type}\",\n                                    resolution = \"${width}x${height}\",\n                                    md5 = md5,\n                                )\n                            )\n                            uriList.add(uri)\n\n                            val imageView = ImageView(this).apply {\n                                layoutParams = LinearLayout.LayoutParams(\n                                    (65.dp * width.toFloat() / height.toFloat()).toInt(), 65.dp\n                                ).apply {\n                                    setMargins(5.dp, 0, 0, 0)\n                                }\n                                setOnClickListener {\n                                    with(binding.imageLayout.indexOfChild(this)) {\n                                        binding.imageLayout.removeViewAt(this)\n                                        uriList.removeAt(this)\n                                        typeList.removeAt(this)\n                                        md5List.removeAt(this)\n                                        imageList.removeAt(this)\n                                        binding.imageLayout.isVisible = uriList.isNotEmpty()\n                                    }\n                                }\n                            }\n                            //Glide.with(this).load(uri).into(imageView)\n                            imageView.load(uri) {\n                                crossfade(true)\n                            }\n                            binding.imageLayout.addView(imageView)\n                        }\n                    }.onFailure {\n                        MaterialAlertDialogBuilder(this)\n                            .setTitle(\"获取图片信息失败\")\n                            .setMessage(it.message)\n                            .setPositiveButton(android.R.string.ok, null)\n                            .setNegativeButton(\"Log\") { _, _ ->\n                                MaterialAlertDialogBuilder(this)\n                                    .setTitle(\"Log\")\n                                    .setMessage(it.stackTraceToString())\n                                    .setPositiveButton(android.R.string.ok, null)\n                                    .show()\n                            }\n                            .show()\n                    }\n                }\n                binding.imageLayout.isVisible = uriList.isNotEmpty()\n            }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    private fun initView() {\n        binding.emojiBtn?.setOnClickListener(this)\n        binding.imageBtn.setOnClickListener(this)\n        binding.atBtn.setOnClickListener(this)\n        binding.tagBtn.setOnClickListener(this)\n        binding.keyboardBtn?.setOnClickListener(this)\n        binding.checkBox.setOnClickListener(this)\n        binding.publish.setOnClickListener(this)\n        binding.editText.setOnTouchListener(this)\n        binding.out.setOnTouchListener(this)\n        (binding.main as? SmoothInputLayout)?.setOnVisibilityChangeListener(this)\n        (binding.main as? SmoothInputLayout)?.setOnKeyboardChangeListener(this)\n        val radius = listOf(16.dp.toFloat(), 16.dp.toFloat(), 0f, 0f)\n        val radiusBg = GradientDrawable().apply {\n            shape = GradientDrawable.RECTANGLE\n            setColor(this@ReplyActivity.color)\n            cornerRadii = floatArrayOf(\n                radius[0], radius[0],\n                radius[1], radius[1],\n                radius[2], radius[2],\n                radius[3], radius[3]\n            )\n        }\n        if (binding.main is SmoothInputLayout) {\n            binding.inputLayout.background = radiusBg\n            binding.emojiLayout.setBackgroundColor(color)\n        } else\n            binding.bottomLayout?.background = radiusBg\n    }\n\n    private fun initObserve() {\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.uploadImage.collect { responseData ->\n                    responseData?.let {\n                        viewModel.replyAndFeedData[\"pic\"] =\n                            responseData.fileInfo.joinToString(separator = \",\") {\n                                responseData.uploadPrepareInfo.uploadImagePrefix + \"/\" + it.uploadFileName\n                            }\n                        ossUpload(\n                            this@ReplyActivity, responseData, uriList, typeList, md5List,\n                            iOnSuccess = { index ->\n                                Log.i(\"OSSUpload\", \"uploadSuccess\")\n                                if (index == uriList.lastIndex) {\n                                    if (type == \"createFeed\")\n                                        viewModel.onPostCreateFeed()\n                                    else\n                                        viewModel.onPostReply()\n                                }\n                            },\n                            iOnFailure = {\n                                Log.i(\"OSSUpload\", \"uploadFailed\")\n                                runOnUiThread {\n                                    closeDialog()\n                                    Toast.makeText(\n                                        this@ReplyActivity,\n                                        \"图片上传失败\",\n                                        Toast.LENGTH_SHORT\n                                    )\n                                        .show()\n                                }\n                            },\n                            closeDialog = {\n                                closeDialog()\n                            }\n                        )\n                        viewModel.resetUpload()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.recentList.collect {\n                    viewModel.size = it.size\n                    viewModel.last = it.lastOrNull()?.data\n                    if (binding.emojiPanel.currentItem == 0 && recentList.isNotEmpty())\n                        return@collect\n                    recentList.clear()\n                    if (it.isEmpty()) {\n                        if (viewModel.isInit) {\n                            viewModel.isInit = false\n                            binding.emojiPanel.setCurrentItem(1, false)\n                        }\n                        recentList.add(0, emptyList())\n                    } else {\n                        recentList.add(0, it.map { item ->\n                            Pair(item.data, EmojiUtils.emojiMap[item.data] ?: R.mipmap.ic_launcher)\n                        })\n                    }\n                    binding.emojiPanel.adapter?.notifyItemChanged(0)\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.postFinished.collect {\n                    if (it) {\n                        closeDialog()\n                        val intent = Intent()\n                        if (type == \"createFeed\") {\n                            this@ReplyActivity.makeToast(\"发布成功\")\n                        } else {\n                            intent.putExtra(\"response_data\", viewModel.responseData)\n                        }\n                        setResult(RESULT_OK, intent)\n                        finish()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.captchaImg.collect { img ->\n                    img?.let {\n                        closeDialog()\n                        val binding = ItemCaptchaBinding.inflate(\n                            LayoutInflater.from(this@ReplyActivity), null, false\n                        )\n                        binding.captchaImg.setImageBitmap(img)\n                        binding.captchaText.highlightColor = ColorUtils.setAlphaComponent(\n                            MaterialColors.getColor(\n                                this@ReplyActivity,\n                                com.google.android.material.R.attr.colorPrimaryDark,\n                                0\n                            ), 128\n                        )\n                        MaterialAlertDialogBuilder(this@ReplyActivity).apply {\n                            setView(binding.root)\n                            setTitle(\"captcha\")\n                            setNegativeButton(android.R.string.cancel, null)\n                            setPositiveButton(\"验证并继续\") { _, _ ->\n                                viewModel.requestValidateData = HashMap()\n                                viewModel.requestValidateData[\"type\"] = \"err_request_captcha\"\n                                viewModel.requestValidateData[\"code\"] =\n                                    binding.captchaText.text.toString()\n                                viewModel.requestValidateData[\"mobile\"] = \"\"\n                                viewModel.requestValidateData[\"idcard\"] = \"\"\n                                viewModel.requestValidateData[\"name\"] = \"\"\n                                viewModel.onPostRequestValidate()\n                            }\n                        }.create().apply {\n                            window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE)\n                            binding.captchaText.requestFocus()\n                        }.show()\n                        viewModel.resetCaptcha()\n                    }\n                }\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.CREATED) {\n                viewModel.toastText.collect { text ->\n                    text?.let {\n                        this@ReplyActivity.makeToast(it)\n                        viewModel.resetToast()\n                        closeDialog()\n                    }\n                }\n            }\n        }\n\n    }\n\n    private fun closeDialog() {\n        dialog?.dismiss()\n        dialog = null\n    }\n\n    private fun initPage() {\n        binding.checkBox.text = if (type == \"createFeed\") \"仅自己可见\"\n        else \"回复并转发\"\n        binding.title.text = if (type == \"createFeed\") \"发布动态\"\n        else \"回复\"\n        if (type != \"createFeed\" && !username.isNullOrEmpty())\n            binding.editText.hint = \"回复: $username\"\n        binding.publish.isClickable = false\n        title?.let {\n            binding.editText.editableText.append(\"#${title}# \")\n        }\n    }\n\n    private fun initEmojiPanel() {\n        for (i in 0..2) {\n            binding.indicator.addView(\n                TextView(this).apply {\n                    layoutParams = LinearLayout.LayoutParams(\n                        0,\n                        LinearLayout.LayoutParams.MATCH_PARENT\n                    ).apply {\n                        weight = 1f\n                    }\n                    gravity = Gravity.CENTER\n                    text = listOf(\"最近\", \"默认\", \"酷币\")[i]\n                    background = AppCompatResources.getDrawable(\n                        this@ReplyActivity,\n                        R.drawable.selector_bg_trans\n                    )\n                    setOnClickListener {\n                        binding.emojiPanel.setCurrentItem(i, false)\n                    }\n                    if (i == 0 && BuildConfig.DEBUG) {\n                        setOnLongClickListener {\n                            viewModel.deleteAll()\n                            true\n                        }\n                    }\n                }\n            )\n            if (i != 2) {\n                binding.indicator.addView(\n                    View(this).apply {\n                        layoutParams = LinearLayout.LayoutParams(\n                            1.dp,\n                            LinearLayout.LayoutParams.MATCH_PARENT\n                        )\n                        setBackgroundColor(\n                            MaterialColors.getColor(\n                                this@ReplyActivity,\n                                com.google.android.material.R.attr.colorSurfaceVariant, 0\n                            )\n                        )\n                    }\n                )\n            }\n        }\n        binding.emojiPanel.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                super.onPageSelected(position)\n                for (i in 0 until binding.indicator.childCount) {\n                    with(binding.indicator.getChildAt(i)) {\n                        if (this is TextView) {\n                            background = AppCompatResources.getDrawable(\n                                this@ReplyActivity,\n                                if (i / 2 == position) R.drawable.selector_emoji_indicator_selected\n                                else R.drawable.selector_emoji_indicator\n                            )\n                            setTextColor(\n                                if (i / 2 == position)\n                                    MaterialColors.getColor(\n                                        this@ReplyActivity,\n                                        com.google.android.material.R.attr.colorOnPrimary, 0\n                                    )\n                                else\n                                    MaterialColors.getColor(\n                                        this@ReplyActivity,\n                                        com.google.android.material.R.attr.colorControlNormal, 0\n                                    )\n                            )\n                        }\n                    }\n                }\n            }\n        })\n\n        binding.emojiPanel.adapter = EmojiPagerAdapter(\n            list,\n            onClickEmoji = {\n                with(binding.editText) {\n                    if (it == \"[c001apk]\") {\n                        onBackSpace()\n                    } else {\n                        editableText.replace(selectionStart, selectionEnd, it)\n                        viewModel.updateRecentEmoji(it)\n                    }\n                }\n            },\n            onCountStart = {\n                countDownTimer.start()\n            },\n            onCountStop = {\n                countDownTimer.cancel()\n            }\n        )\n    }\n\n    private val countDownTimer: CountDownTimer = object : CountDownTimer(100000, 50) {\n        override fun onTick(millisUntilFinished: Long) {\n            onBackSpace()\n        }\n\n        override fun onFinish() {}\n    }\n\n    private fun onBackSpace() {\n        dispatchKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))\n        ViewCompat.performHapticFeedback(binding.editText, HapticFeedbackConstantsCompat.CONFIRM)\n    }\n\n    private fun initEditText() {\n        binding.editText.apply {\n            highlightColor = ColorUtils.setAlphaComponent(\n                MaterialColors.getColor(\n                    this@ReplyActivity,\n                    com.google.android.material.R.attr.colorPrimaryDark,\n                    0\n                ), 128\n            )\n            addTextChangedListener(EmojiTextWatcher(\n                this@ReplyActivity, binding.editText.textSize,\n                MaterialColors.getColor(\n                    this@ReplyActivity,\n                    com.google.android.material.R.attr.colorPrimary,\n                    0\n                )\n            ) {\n                if (binding.editText.text.toString().trim().isBlank()) {\n                    binding.publish.isClickable = false\n                    binding.publish.setTextColor(getColor(android.R.color.darker_gray))\n                } else {\n                    binding.publish.isClickable = true\n                    binding.publish.setTextColor(\n                        MaterialColors.getColor(\n                            this@ReplyActivity,\n                            com.google.android.material.R.attr.colorPrimary,\n                            0\n                        )\n                    )\n                }\n            })\n            addTextChangedListener(OnTextInputListener(\"@\") {\n                isFromAt = true\n                launchAtTopic(\"user\")\n            })\n            setOnKeyListener(FastDeleteAtUserKeyListener)\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        closeDialog()\n        countDownTimer.cancel()\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        window.statusBarColor = Color.TRANSPARENT\n        window.navigationBarColor = SurfaceColors.SURFACE_1.getColor(this)\n    }\n\n    private fun showInput() {\n        if (binding.main is SmoothInputLayout)\n            (binding.main as? SmoothInputLayout)?.showKeyboard()\n        else\n            binding.editText.let {\n                it.requestFocus()\n                it.requestFocusFromTouch()\n                imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)\n            }\n    }\n\n    private fun showEmoji() {\n        (binding.main as? SmoothInputLayout)?.showEmojiPanel(true)\n    }\n\n    @SuppressLint(\"InflateParams\")\n    override fun onClick(view: View) {\n        when (view.id) {\n            R.id.atBtn -> {\n                ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM)\n                launchAtTopic(\"user\")\n            }\n\n            R.id.tagBtn -> {\n                ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM)\n                launchAtTopic(\"topic\")\n            }\n\n            R.id.imageBtn -> {\n                ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM)\n                launchPick()\n            }\n\n            R.id.keyboardBtn -> {\n                ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM)\n                with(binding.main as? SmoothInputLayout) {\n                    if (binding.emojiLayout.isVisible) {\n                        this?.closeEmojiPanel()\n                    } else if (this?.isKeyBoardOpen == true) {\n                        closeKeyboard(false)\n                    } else {\n                        showInput()\n                    }\n                }\n            }\n\n            R.id.emojiBtn -> {\n                ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM)\n                if (binding.emojiBtn?.isSelected == true) {\n                    showInput()\n                } else {\n                    showEmoji()\n                }\n            }\n\n            R.id.checkBox ->\n                ViewCompat.performHapticFeedback(view, HapticFeedbackConstantsCompat.CONFIRM)\n\n            R.id.publish -> {\n                if (type == \"createFeed\") {\n                    viewModel.replyAndFeedData[\"id\"] = \"\"\n                    viewModel.replyAndFeedData[\"message\"] = binding.editText.text.toString()\n                    viewModel.replyAndFeedData[\"type\"] = \"feed\"\n                    viewModel.replyAndFeedData[\"status\"] =\n                        if (binding.checkBox.isChecked) \"-1\" else \"1\"\n\n                    targetType?.let {\n                        if (it == \"apk\")\n                            viewModel.replyAndFeedData[\"type\"] = \"comment\"\n                        viewModel.replyAndFeedData[\"targetType\"] = it\n                    }\n                    targetId?.let {\n                        viewModel.replyAndFeedData[\"targetId\"] = it\n                    }\n\n                    if (uriList.isNotEmpty()) {\n                        viewModel.onPostOSSUploadPrepare(imageList)\n                    } else {\n                        viewModel.onPostCreateFeed()\n                    }\n                } else {\n                    viewModel.replyAndFeedData[\"message\"] = binding.editText.text.toString()\n                    viewModel.replyAndFeedData[\"replyAndForward\"] =\n                        if (binding.checkBox.isChecked) \"1\" else \"0\"\n                    if (uriList.isNotEmpty()) {\n                        viewModel.onPostOSSUploadPrepare(imageList)\n                    } else {\n                        viewModel.onPostReply()\n                    }\n                }\n                showDialog()\n            }\n\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showDialog() {\n        dialog = MaterialAlertDialogBuilder(\n            this,\n            R.style.ThemeOverlay_MaterialAlertDialog_Rounded\n        ).apply {\n            setView(\n                LayoutInflater.from(this@ReplyActivity)\n                    .inflate(R.layout.dialog_refresh, null, false)\n            )\n            setCancelable(false)\n        }.create()\n        dialog?.show()\n        val decorView: View? = dialog?.window?.decorView\n        val paddingTop: Int = decorView?.paddingTop ?: 0\n        val paddingBottom: Int = decorView?.paddingBottom ?: 0\n        val paddingLeft: Int = decorView?.paddingLeft ?: 0\n        val paddingRight: Int = decorView?.paddingRight ?: 0\n        val width = 80.dp + paddingLeft + paddingRight\n        val height = 80.dp + paddingTop + paddingBottom\n        dialog?.window?.setLayout(width, height)\n    }\n\n    private fun launchAtTopic(type: String) {\n        /* val intent = Intent(this, AtTopicActivity::class.java)\n         intent.putExtra(\"type\", type)\n         val options = ActivityOptionsCompat.makeCustomAnimation(\n             this, R.anim.right_in, R.anim.left_out\n         )\n         atTopicResultLauncher.launch(intent, options)*/\n    }\n\n    private fun launchPick() {\n        (binding.main as? SmoothInputLayout)?.closeKeyboard(false)\n        val options = ActivityOptionsCompat.makeCustomAnimation(\n            this, R.anim.anim_bottom_sheet_slide_up, R.anim.anim_bottom_sheet_slide_down\n        )\n        try {\n            pickMultipleMedia.launch(\n                PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly),\n                options\n            )\n        } catch (e: ActivityNotFoundException) {\n            makeToast(\"Activity Not Found\")\n            e.printStackTrace()\n        }\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {\n        when (view.id) {\n            R.id.out -> {\n                finish()\n            }\n        }\n        return false\n    }\n\n    override fun onVisibilityChange(visibility: Int) { // 0->visible, 8->gone\n        binding.emojiBtn?.isSelected = visibility == VISIBLE\n        binding.emojiBtn?.setImageResource(\n            if (visibility == VISIBLE) R.drawable.outline_keyboard_show_24\n            else R.drawable.outline_emoji_emotions_24\n        )\n        if (binding.emojiBtn?.isSelected == true)\n            binding.keyboardBtn?.setImageResource(R.drawable.outline_keyboard_hide_24)\n        if (binding.emojiBtn?.isSelected == false && (binding.main as? SmoothInputLayout)?.isKeyBoardOpen == false)\n            binding.keyboardBtn?.setImageResource(R.drawable.outline_keyboard_show_24)\n    }\n\n    override fun onKeyboardChanged(open: Boolean) {\n        binding.keyboardBtn?.setImageResource(\n            if (open || binding.emojiBtn?.isSelected == true) R.drawable.outline_keyboard_hide_24\n            else R.drawable.outline_keyboard_show_24\n        )\n    }\n\n    override fun finish() {\n        super.finish()\n        overridePendingTransition(\n            R.anim.anim_bottom_sheet_slide_up,\n            R.anim.anim_bottom_sheet_slide_down\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/reply/ReplyViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.feed.reply\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareModel\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareResponse\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.repository.RecentEmojiRepo\nimport com.google.gson.Gson\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n@HiltViewModel\nclass ReplyViewModel @Inject constructor(\n    private val recentEmojiRepo: RecentEmojiRepo,\n    private val networkRepo: NetworkRepo\n) : ViewModel() {\n\n    val recentList = recentEmojiRepo.loadAllListFlow()\n\n    var isInit = true\n    var type: String? = null\n    var rid: String? = null\n\n    var responseData: HomeFeedResponse.Data? = null\n    var toastText = MutableStateFlow<String?>(null)\n        private set\n\n    fun resetToast() {\n        toastText.value = null\n    }\n\n    var replyAndFeedData = HashMap<String, String>()\n    fun onPostReply() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postReply(replyAndFeedData, rid.toString(), type.toString())\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (response.data != null) {\n                            responseData = response.data\n                            postFinished.value = true\n                        } else {\n                            toastText.value = response.message\n                            if (response.messageStatus == \"err_request_captcha\") {\n                                onGetValidateCaptcha()\n                            }\n                        }\n                    } else {\n                        toastText.value = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    lateinit var requestValidateData: HashMap<String, String?>\n    fun onPostRequestValidate() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postRequestValidate(requestValidateData)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (response.data != null) {\n                            toastText.value = response.data.count\n                            if (response.data.count == \"验证通过\") {\n                                if (type == \"createFeed\")\n                                    onPostCreateFeed()\n                                else\n                                    onPostReply()\n                            }\n                        } else if (response.message != null) {\n                            toastText.value = response.message\n                            if (response.message == \"请输入正确的图形验证码\") {\n                                onGetValidateCaptcha()\n                            }\n                        }\n                    } else {\n                        toastText.value = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n\n    var captchaImg = MutableStateFlow<Bitmap?>(null)\n        private set\n\n    fun resetCaptcha() {\n        captchaImg.value = null\n    }\n\n    private fun onGetValidateCaptcha() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getValidateCaptcha(\"/v6/account/captchaImage?${System.currentTimeMillis() / 1000}&w=270=&h=113\")\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        val responseBody = response.body()\n                        val bitmap = BitmapFactory.decodeStream(responseBody?.byteStream())\n                        captchaImg.value = bitmap\n                    } else {\n                        toastText.value = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    var postFinished = MutableStateFlow(false)\n    fun onPostCreateFeed() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postCreateFeed(replyAndFeedData)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (response.data?.id != null) {\n                            postFinished.value = true\n                        } else {\n                            toastText.value = response.message\n                            if (response.messageStatus == \"err_request_captcha\") {\n                                onGetValidateCaptcha()\n                            }\n                        }\n                    } else {\n                        toastText.value = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    var size = 0\n    var last: String? = null\n    fun updateRecentEmoji(data: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (recentEmojiRepo.checkEmoji(data)) {\n                recentEmojiRepo.updateEmoji(data)\n            } else {\n                if (size == 27)\n                    last?.let {\n                        recentEmojiRepo.updateEmoji(it, data)\n                    }\n                else\n                    recentEmojiRepo.insertEmoji(StringEntity(data))\n            }\n        }\n    }\n\n    fun deleteAll() {\n        viewModelScope.launch(Dispatchers.IO) {\n            recentEmojiRepo.deleteAll()\n        }\n    }\n\n    var uploadImage = MutableStateFlow<OSSUploadPrepareResponse.Data?>(null)\n        private set\n\n    fun resetUpload() {\n        uploadImage.value = null\n    }\n\n    fun onPostOSSUploadPrepare(imageList: List<OSSUploadPrepareModel>) {\n        val ossUploadPrepareData = hashMapOf(\n            \"uploadBucket\" to \"image\",\n            \"uploadDir\" to \"feed\",\n            \"is_anonymous\" to \"0\",\n            \"uploadFileList\" to Gson().toJson(imageList),\n            \"toUid\" to \"\"\n        )\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postOSSUploadPrepare(ossUploadPrepareData)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (data.message != null) {\n                            toastText.value = data.message\n                        } else if (data.data != null) {\n                            uploadImage.value = data.data\n                        }\n                    } else {\n                        toastText.value = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/reply/emoji/EmojiChildPagerAdapter.kt",
    "content": "package com.example.c001apk.compose.ui.feed.reply.emoji\n\nimport android.view.Gravity\nimport android.view.ViewGroup\nimport android.widget.GridView\nimport androidx.recyclerview.widget.RecyclerView\nimport com.example.c001apk.compose.util.dp\n\nclass EmojiChildPagerAdapter(\n    private val emojiList: List<List<Pair<String, Int>>>,\n    private val onClickEmoji: (String) -> Unit,\n    private val onCountStart: () -> Unit,\n    private val onCountStop: () -> Unit\n) : RecyclerView.Adapter<EmojiChildPagerAdapter.ViewHolder>() {\n\n    class ViewHolder(val view: GridView) : RecyclerView.ViewHolder(view)\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        val view = GridView(parent.context).apply {\n            layoutParams = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT,\n                ViewGroup.LayoutParams.MATCH_PARENT\n            ).apply {\n                gravity = Gravity.CENTER\n            }\n            numColumns = 7\n            verticalSpacing = 4.dp\n        }\n        return ViewHolder(view)\n    }\n\n    override fun getItemCount() = emojiList.size\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.view.adapter = EmojiGridAdapter(\n            emojiList[position].toList(),\n            onClickEmoji,\n            onCountStart,\n            onCountStop\n        )\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/reply/emoji/EmojiGridAdapter.kt",
    "content": "package com.example.c001apk.compose.ui.feed.reply.emoji\n\nimport android.annotation.SuppressLint\nimport android.os.Build.VERSION.SDK_INT\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.BaseAdapter\nimport android.widget.ImageView\nimport androidx.appcompat.content.res.AppCompatResources\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.util.dp\n\n\nclass EmojiGridAdapter(\n    private val emojiList: List<Pair<String, Int>>,\n    private val onClickEmoji: (String) -> Unit,\n    private val onCountStart: () -> Unit,\n    private val onCountStop: () -> Unit\n) : BaseAdapter() {\n\n    override fun getCount() = 28\n\n    override fun getItem(position: Int): Any {\n        return 0\n    }\n\n    override fun getItemId(position: Int): Long {\n        return 0\n    }\n\n    @SuppressLint(\"ViewHolder\", \"ClickableViewAccessibility\")\n    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {\n        val emoji = emojiList.getOrNull(position)\n        return if (emoji != null || position == 27) {\n            val view =\n                LayoutInflater.from(parent.context).inflate(R.layout.item_emoji, parent, false)\n            val imageView: ImageView = view.findViewById(R.id.imageView)\n\n            imageView.setImageResource(emoji?.second ?: R.drawable.outline_backspace_24)\n            emoji?.first?.let {\n                view.background =\n                    AppCompatResources.getDrawable(parent.context, R.drawable.selector_emoji)\n                if (SDK_INT >= 26)\n                    view.tooltipText = it.substring(1, it.lastIndex)\n            }\n            view.setOnClickListener {\n                onClickEmoji(emoji?.first ?: \"[c001apk]\")\n            }\n            if (position == 27) {\n                if (SDK_INT >= 26)\n                    view.tooltipText = null\n                view.setOnLongClickListener {\n                    onCountStart()\n                    false\n                }\n                view.setOnTouchListener { _, event ->\n                    if (event.action == MotionEvent.ACTION_UP || event.action == MotionEvent.ACTION_CANCEL) {\n                        onCountStop()\n                    }\n                    false\n                }\n            }\n            view\n        } else View(parent.context).apply {\n            layoutParams = ViewGroup.LayoutParams(48.dp, 48.dp)\n            isEnabled = false\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/feed/reply/emoji/EmojiPagerAdapter.kt",
    "content": "package com.example.c001apk.compose.ui.feed.reply.emoji\n\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.widget.ViewPager2\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.view.circleindicator.CircleIndicator3\n\nclass EmojiPagerAdapter(\n    private val emojiList: List<List<List<Pair<String, Int>>>>,\n    private val onClickEmoji: (String) -> Unit,\n    private val onCountStart: () -> Unit,\n    private val onCountStop: () -> Unit\n) : RecyclerView.Adapter<EmojiPagerAdapter.ViewHolder>() {\n\n    class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {\n        val viewPager: ViewPager2 = view.findViewById(R.id.viewPager)\n        val indicator: CircleIndicator3 = view.findViewById(R.id.indicator)\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {\n        val view = LayoutInflater.from(parent.context).inflate(\n            R.layout.item_emoji_child_viewpager, parent, false\n        )\n        return ViewHolder(view)\n    }\n\n    override fun getItemCount() = emojiList.size\n\n    override fun onBindViewHolder(holder: ViewHolder, position: Int) {\n        holder.viewPager.adapter = EmojiChildPagerAdapter(\n            emojiList[position], onClickEmoji, onCountStart, onCountStop\n        )\n        holder.indicator.setViewPager(holder.viewPager)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/ffflist/FFFContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.ffflist\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.Dialog\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@Composable\nfun FFFContentScreen(\n    uid: String?,\n    id: String?,\n    type: String,\n    paddingValues: PaddingValues,\n    refreshState: Boolean?,\n    resetRefreshState: () -> Unit,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onViewFFFList: (String?, String, String?, String?) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<FFFContentViewModel, FFFContentViewModel.ViewModelFactory>(key = id + uid + type) { factory ->\n            factory.create(\n                url = when (type) {\n                    FFFListType.FEED.name -> \"/v6/user/feedList?showAnonymous=0&isIncludeTop=1\"\n                    FFFListType.FOLLOW.name, FFFListType.USER_FOLLOW.name -> \"/v6/user/followList\"\n                    FFFListType.APK.name -> \"/v6/user/apkFollowList\"\n                    FFFListType.FAN.name -> \"/v6/user/fansList\"\n                    FFFListType.RECENT.name -> \"/v6/user/recentHistoryList\"\n                    FFFListType.LIKE.name -> \"/v6/user/likeList\"\n                    FFFListType.REPLY.name -> \"/v6/user/replyList\"\n                    FFFListType.REPLYME.name -> \"/v6/user/replyToMeList\"\n                    FFFListType.COLLECTION.name -> \"/v6/collection/list\"\n                    FFFListType.COLLECTION_ITEM.name -> \"/v6/collection/itemList\"\n                    else -> EMPTY_STRING\n                },\n                uid = uid, id = id, showDefault = if (type.contains(\"COLLECTION\")) 0 else null\n            )\n        }\n\n    var showRecentDialog by remember { mutableStateOf(false) }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = paddingValues,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n        onViewFFFList = onViewFFFList,\n        onHandleRecent = { actionId, targetId, targetType, isTop ->\n            viewModel.actionId = actionId\n            viewModel.targetId = targetId\n            viewModel.targetType = targetType\n            viewModel.isTop = isTop\n            showRecentDialog = true\n        },\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n    when {\n        showRecentDialog -> {\n            Dialog(onDismissRequest = { showRecentDialog = false }) {\n                ElevatedCard(\n                    modifier = Modifier.fillMaxWidth(),\n                    shape = RoundedCornerShape(28.dp),\n                    colors = CardDefaults.elevatedCardColors()\n                        .copy(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh)\n                ) {\n                    Column(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(vertical = 8.dp)\n                    ) {\n                        Text(\n                            text = if (viewModel.isTop == 1) \"移除置顶\" else \"置顶\",\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clickable {\n                                    showRecentDialog = false\n                                    viewModel.onHandleRecentHistory(FFFContentViewModel.ActionType.TOP)\n                                }\n                                .padding(horizontal = 24.dp, vertical = 14.dp),\n                            style = MaterialTheme.typography.titleSmall\n                        )\n                        Text(\n                            text = \"删除\",\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clickable {\n                                    showRecentDialog = false\n                                    viewModel.onHandleRecentHistory(FFFContentViewModel.ActionType.DELETE)\n                                }\n                                .padding(horizontal = 24.dp, vertical = 14.dp),\n                            style = MaterialTheme.typography.titleSmall\n                        )\n                        Text(\n                            text = \"清空全部\",\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clickable {\n                                    showRecentDialog = false\n                                    viewModel.onHandleRecentHistory(FFFContentViewModel.ActionType.DELETE_ALL)\n                                }\n                                .padding(horizontal = 24.dp, vertical = 14.dp),\n                            style = MaterialTheme.typography.titleSmall\n                        )\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/ffflist/FFFContentViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.ffflist\n\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n@HiltViewModel(assistedFactory = FFFContentViewModel.ViewModelFactory::class)\nclass FFFContentViewModel @AssistedInject constructor(\n    @Assisted(\"url\") val url: String,\n    @Assisted(\"uid\") val uid: String?,\n    @Assisted(\"id\") val id: String?,\n    @Assisted val showDefault: Int?,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"url\") url: String,\n            @Assisted(\"uid\") uid: String?,\n            @Assisted(\"id\") id: String?,\n            @Assisted showDefault: Int?,\n        ): FFFContentViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getFollowList(url, uid, id, showDefault, page, lastItem)\n\n    enum class ActionType {\n        TOP, DELETE, DELETE_ALL\n    }\n\n    var isTop = 0\n    lateinit var actionId: String\n    lateinit var targetId: String\n    lateinit var targetType: String\n\n    fun onHandleRecentHistory(actionType: ActionType) {\n        val url = \"/v6/user/\" + when (actionType) {\n            ActionType.TOP -> if (isTop == 0) \"addToTop\" else \"removeFromTop\"\n            ActionType.DELETE -> \"delete\"\n            ActionType.DELETE_ALL -> \"clear\"\n        } + \"RecentHistory\"\n        val postData = when (actionType) {\n            ActionType.TOP -> if (isTop == 1) mapOf(\"id\" to actionId)\n            else mapOf(\n                \"targetId\" to targetId,\n                \"targetType\" to targetType,\n            )\n\n            ActionType.DELETE -> mapOf(\"id\" to actionId)\n            ActionType.DELETE_ALL -> mapOf()\n        }\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postDelete(url, postData)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                        } else if (data.data?.count?.contains(\"成功\") == true) {\n                            var response = (loadingState as LoadingState.Success).response\n                            response = when (actionType) {\n                                ActionType.TOP -> {\n                                    if (isTop == 0) {\n                                        val actionItem = response.find { it.targetId == targetId }\n                                        response.toMutableList().also {\n                                            it.remove(actionItem)\n                                            actionItem?.let { item ->\n                                                item.isTop = 1\n                                                it.add(0, item)\n                                            }\n                                        }\n                                    } else {\n                                        response.map {\n                                            if (it.targetId == targetId) it.copy(isTop = 0)\n                                            else it\n                                        }\n                                    }\n                                }\n\n                                ActionType.DELETE -> response.filterNot { item -> item.targetId == targetId }\n                                ActionType.DELETE_ALL -> emptyList()\n                            }\n                            loadingState = LoadingState.Success(response)\n                            toastText = data.data.count\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/ffflist/FFFListScreen.kt",
    "content": "package com.example.c001apk.compose.ui.ffflist\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SecondaryScrollableTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.ui.carousel.CarouselContentScreen\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.ReportType\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/12\n */\n\nenum class FFFListType {\n    FEED, FOLLOW, APK, USER_FOLLOW, FAN, RECENT, LIKE, REPLY, REPLYME, FAV, HISTORY, COLLECTION, COLLECTION_ITEM\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun FFFListScreen(\n    onBackClick: () -> Unit,\n    uid: String?,\n    type: String,\n    id: String?,\n    title: String?,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onViewFFFList: (String?, String, String?, String?) -> Unit,\n) {\n\n    val layoutDirection = LocalLayoutDirection.current\n    val isMe by lazy { uid == CookieUtil.uid }\n    val followList by lazy { listOf(\"用户\", \"话题\", \"数码\", \"应用\") }\n    val replyList by lazy { listOf(\"我的回复\", \"我收到的回复\") }\n    val pagerState = rememberPagerState {\n        if (type == FFFListType.FOLLOW.name) followList.size\n        else replyList.size\n    }\n    val tabList by lazy {\n        if (type == FFFListType.FOLLOW.name) followList\n        else replyList\n    }\n    var refreshState by remember { mutableStateOf(false) }\n    val scope = rememberCoroutineScope()\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        text = title ?: when (type) {\n                            FFFListType.FEED.name -> \"我的动态\"\n\n                            FFFListType.FOLLOW.name -> \"我的关注\"\n\n                            FFFListType.USER_FOLLOW.name -> {\n                                if (isMe)\n                                    \"我关注的人\"\n                                else\n                                    \"TA关注的人\"\n                            }\n\n                            FFFListType.FAN.name -> {\n                                if (isMe)\n                                    \"关注我的人\"\n                                else\n                                    \"TA的粉丝\"\n                            }\n\n                            FFFListType.RECENT.name -> \"我的常去\"\n\n                            FFFListType.LIKE.name -> \"我的赞\"\n\n                            FFFListType.REPLY.name -> \"我的回复\"\n\n                            FFFListType.COLLECTION.name -> \"我的收藏\"\n\n                            else -> EMPTY_STRING\n                        },\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                }\n            )\n        }\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier.padding(top = paddingValues.calculateTopPadding())\n        ) {\n            when (type) {\n                FFFListType.FOLLOW.name, FFFListType.REPLY.name -> {\n                    SecondaryScrollableTabRow(\n                        modifier = Modifier.padding(\n                            start = paddingValues.calculateLeftPadding(layoutDirection),\n                            end = paddingValues.calculateRightPadding(layoutDirection),\n                        ),\n                        selectedTabIndex = pagerState.currentPage,\n                        indicator = {\n                            TabRowDefaults.SecondaryIndicator(\n                                Modifier\n                                    .tabIndicatorOffset(\n                                        pagerState.currentPage,\n                                        matchContentSize = true\n                                    )\n                                    .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                            )\n                        },\n                        divider = {}\n                    ) {\n                        tabList.forEachIndexed { index, tab ->\n                            Tab(\n                                selected = pagerState.currentPage == index,\n                                onClick = {\n                                    if (pagerState.currentPage == index) {\n                                        refreshState = true\n                                    }\n                                    scope.launch { pagerState.animateScrollToPage(index) }\n                                },\n                                text = { Text(text = tab) }\n                            )\n                        }\n                    }\n\n                    HorizontalDivider()\n\n                    HorizontalPager(\n                        state = pagerState\n                    ) { index ->\n                        if (type == FFFListType.FOLLOW.name) {\n                            when (index) {\n                                0, 3 -> FFFContentScreen(\n                                    id = id,\n                                    uid = uid,\n                                    type = if (index == 0) FFFListType.FOLLOW.name\n                                    else FFFListType.APK.name,\n                                    paddingValues = PaddingValues(\n                                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                                        bottom = paddingValues.calculateBottomPadding(),\n                                    ),\n                                    refreshState = refreshState,\n                                    resetRefreshState = { refreshState = false },\n                                    onViewUser = onViewUser,\n                                    onViewFeed = onViewFeed,\n                                    onOpenLink = onOpenLink,\n                                    onCopyText = onCopyText,\n                                    onReport = onReport,\n                                    onViewFFFList = onViewFFFList,\n                                )\n\n                                1, 2 -> CarouselContentScreen(\n                                    url = if (index == 1) \"#/topic/userFollowTagList\"\n                                    else \"#/product/followProductList\",\n                                    title = if (index == 1) \"我关注的话题\"\n                                    else \"我关注的数码吧\",\n                                    paddingValues = paddingValues,\n                                    refreshState = refreshState,\n                                    resetRefreshState = { refreshState = false },\n                                    onViewUser = onViewUser,\n                                    onViewFeed = onViewFeed,\n                                    onOpenLink = onOpenLink,\n                                    onCopyText = onCopyText,\n                                    onReport = onReport,\n                                )\n                            }\n                        } else if (type == FFFListType.REPLY.name) {\n                            FFFContentScreen(\n                                id = id,\n                                uid = uid,\n                                type = if (index == 0) FFFListType.REPLY.name else FFFListType.REPLYME.name,\n                                paddingValues = PaddingValues(\n                                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                                    bottom = paddingValues.calculateBottomPadding(),\n                                ),\n                                refreshState = refreshState,\n                                resetRefreshState = { refreshState = false },\n                                onViewUser = onViewUser,\n                                onViewFeed = onViewFeed,\n                                onOpenLink = onOpenLink,\n                                onCopyText = onCopyText,\n                                onReport = onReport,\n                                onViewFFFList = onViewFFFList,\n                            )\n\n                        }\n                    }\n\n                }\n\n                else -> {\n                    HorizontalDivider()\n                    FFFContentScreen(\n                        id = id,\n                        uid = uid,\n                        type = type,\n                        paddingValues = PaddingValues(\n                            start = paddingValues.calculateLeftPadding(layoutDirection),\n                            bottom = paddingValues.calculateBottomPadding(),\n                        ),\n                        refreshState = null,\n                        resetRefreshState = {},\n                        onViewUser = onViewUser,\n                        onViewFeed = onViewFeed,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText,\n                        onReport = onReport,\n                        onViewFFFList = onViewFFFList,\n                    )\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/history/HistoryScreen.kt",
    "content": "package com.example.c001apk.compose.ui.history\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.ClearAll\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.cards.HistoryCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.util.ReportType\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/17\n */\n\nenum class HistoryType {\n    FAV, HISTORY\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun HistoryScreen(\n    onBackClick: () -> Unit,\n    type: String,\n    onViewUser: (String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onCopyText: (String?) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<HistoryViewModel, HistoryViewModel.ViewModelFactory>(key = type) { factory ->\n            factory.create(HistoryType.valueOf(type))\n        }\n    val dataList by viewModel.dataList.collectAsStateWithLifecycle(initialValue = emptyList())\n\n    val layoutDirection = LocalLayoutDirection.current\n    var showClearDialog by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(text = type)\n                },\n                actions = {\n                    AnimatedVisibility(\n                        visible = dataList.isNotEmpty(),\n                        enter = fadeIn(),\n                        exit = fadeOut()\n                    ) {\n                        IconButton(onClick = {\n                            showClearDialog = true\n                        }) {\n                            Icon(imageVector = Icons.Default.ClearAll, contentDescription = null)\n                        }\n                    }\n\n                }\n            )\n        },\n    ) { paddingValues ->\n\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(\n                    top = paddingValues.calculateTopPadding(),\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                ),\n            contentPadding = PaddingValues(\n                start = 10.dp,\n                top = 10.dp,\n                end = 10.dp,\n                bottom = 10.dp + paddingValues.calculateBottomPadding()\n            ),\n            verticalArrangement = Arrangement.spacedBy(10.dp)\n        ) {\n\n            items(dataList, key = { it.id }) {\n                HistoryCard(\n                    data = it,\n                    onViewUser = onViewUser,\n                    onReport = onReport,\n                    onDelete = { id ->\n                        viewModel.delete(id)\n                    },\n                    onBlockUser = { uid ->\n                        viewModel.blockUser(uid)\n                    },\n                    onOpenLink = onOpenLink,\n                    onViewFeed = onViewFeed,\n                    onCopyText = onCopyText,\n                )\n            }\n\n        }\n\n        if (dataList.isEmpty()) {\n            Box(\n                modifier = Modifier.fillMaxSize(),\n                contentAlignment = Alignment.Center\n            ) {\n                LoadingCard(state = LoadingState.Empty)\n            }\n        }\n\n    }\n\n    when {\n        showClearDialog -> {\n            AlertDialog(\n                onDismissRequest = { showClearDialog = false },\n                confirmButton = {\n                    TextButton(\n                        onClick = {\n                            showClearDialog = false\n                            viewModel.clearAll()\n                        }) {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                },\n                dismissButton = {\n                    TextButton(\n                        onClick = {\n                            showClearDialog = false\n                        }) {\n                        Text(text = stringResource(id = android.R.string.cancel))\n                    }\n                },\n                title = {\n                    Text(text = \"确定清除全部？\", modifier = Modifier.fillMaxWidth())\n                }\n            )\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/history/HistoryViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.history\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.HistoryFavoriteRepo\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/17\n */\n@HiltViewModel(assistedFactory = HistoryViewModel.ViewModelFactory::class)\nclass HistoryViewModel @AssistedInject constructor(\n    @Assisted val type: HistoryType = HistoryType.HISTORY,\n    private val blackListRepo: BlackListRepo,\n    private val historyFavoriteRepo: HistoryFavoriteRepo\n) : ViewModel() {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(type: HistoryType): HistoryViewModel\n    }\n\n    val dataList = when (type) {\n        HistoryType.FAV -> historyFavoriteRepo.loadAllFavoriteListFlow()\n        HistoryType.HISTORY -> historyFavoriteRepo.loadAllHistoryListFlow()\n    }\n\n    fun blockUser(uid: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (!blackListRepo.checkUid(uid)) {\n                blackListRepo.saveUid(uid)\n            }\n            when (type) {\n                HistoryType.FAV -> historyFavoriteRepo.deleteFavByUid(uid)\n                HistoryType.HISTORY -> historyFavoriteRepo.deleteHistoryByUid(uid)\n            }\n        }\n    }\n\n    fun delete(id: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (type) {\n                HistoryType.FAV -> historyFavoriteRepo.deleteFavorite(id)\n                HistoryType.HISTORY -> historyFavoriteRepo.deleteHistory(id)\n            }\n        }\n    }\n\n    fun clearAll() {\n        viewModelScope.launch(Dispatchers.IO) {\n            when (type) {\n                HistoryType.FAV -> historyFavoriteRepo.deleteAllFavorite()\n                HistoryType.HISTORY -> historyFavoriteRepo.deleteAllHistory()\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/HomeScreen.kt",
    "content": "package com.example.c001apk.compose.ui.home\n\nimport android.content.Intent\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.exclude\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.navigationBars\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Add\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.ScaffoldDefaults\nimport androidx.compose.material3.SecondaryScrollableTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.core.content.ContextCompat\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.logic.model.UpdateCheckItem\nimport com.example.c001apk.compose.ui.feed.reply.ReplyActivity\nimport com.example.c001apk.compose.ui.home.app.AppListScreen\nimport com.example.c001apk.compose.ui.home.feed.HomeFeedScreen\nimport com.example.c001apk.compose.ui.home.topic.HomeTopicScreen\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.ReportType\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/5\n */\n\nenum class TabType {\n    FOLLOW, APP, FEED, HOT, TOPIC, PRODUCT, COOLPIC\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun HomeScreen(\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    onRefresh: () -> Unit,\n    onSearch: () -> Unit,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onViewApp: (String) -> Unit,\n    onCheckUpdate: (List<UpdateCheckItem>) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val scope = rememberCoroutineScope()\n\n    val tabList = TabType.entries\n    val initialPage = tabList.indexOf(TabType.FEED)\n    val pagerState = rememberPagerState(\n        initialPage = initialPage,\n        pageCount = {\n            tabList.size\n        }\n    )\n    val context = LocalContext.current\n    var isScrollingUp by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        floatingActionButton = {\n            if (isLogin && pagerState.currentPage == initialPage) {\n                AnimatedVisibility(\n                    visible = isScrollingUp,\n                    enter = slideInVertically { it * 2 },\n                    exit = slideOutVertically { it * 2 }\n                ) {\n                    FloatingActionButton(\n                        onClick = {\n                            val intent = Intent(context, ReplyActivity::class.java)\n                            intent.putExtra(\"type\", \"createFeed\")\n                            val animationBundle = ActivityOptionsCompat.makeCustomAnimation(\n                                context,\n                                R.anim.anim_bottom_sheet_slide_up,\n                                R.anim.anim_bottom_sheet_slide_down\n                            ).toBundle()\n                            ContextCompat.startActivity(context, intent, animationBundle)\n                        }\n                    ) {\n                        Icon(\n                            imageVector = Icons.Default.Add,\n                            contentDescription = null\n                        )\n                    }\n                }\n            }\n        },\n        contentWindowInsets = ScaffoldDefaults\n            .contentWindowInsets\n            .exclude(WindowInsets.navigationBars)\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier.padding(top = paddingValues.calculateTopPadding()),\n        ) {\n            Row(\n                modifier = Modifier.fillMaxWidth()\n            ) {\n                SecondaryScrollableTabRow(\n                    modifier = Modifier.weight(1f),\n                    selectedTabIndex = pagerState.currentPage,\n                    indicator = {\n                        TabRowDefaults.SecondaryIndicator(\n                            Modifier\n                                .tabIndicatorOffset(pagerState.currentPage, matchContentSize = true)\n                                .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                        )\n                    },\n                    divider = {}\n                ) {\n                    tabList.forEachIndexed { index, tab ->\n                        Tab(\n                            selected = pagerState.currentPage == index,\n                            onClick = {\n                                if (pagerState.currentPage == index) {\n                                    onRefresh()\n                                }\n                                scope.launch { pagerState.animateScrollToPage(index) }\n                            },\n                            text = { Text(text = tab.name) }\n                        )\n                    }\n                }\n                IconButton(onClick = { onSearch() }) {\n                    Icon(imageVector = Icons.Default.Search, contentDescription = null)\n                }\n            }\n\n            HorizontalDivider()\n\n            HorizontalPager(\n                state = pagerState,\n            ) { index ->\n\n                when (val type = TabType.valueOf(TabType.entries[index].name)) {\n                    TabType.FOLLOW, TabType.FEED, TabType.HOT, TabType.COOLPIC ->\n                        HomeFeedScreen(\n                            refreshState = refreshState,\n                            resetRefreshState = resetRefreshState,\n                            type = type,\n                            onViewUser = onViewUser,\n                            onViewFeed = onViewFeed,\n                            onOpenLink = onOpenLink,\n                            onCopyText = onCopyText,\n                            onReport = onReport,\n                            isScrollingUp = {\n                                isScrollingUp = it\n                            }\n                        )\n\n                    TabType.APP -> AppListScreen(\n                        refreshState = refreshState,\n                        resetRefreshState = resetRefreshState,\n                        onViewApp = onViewApp,\n                        onCheckUpdate = onCheckUpdate,\n                    )\n\n                    TabType.TOPIC, TabType.PRODUCT -> HomeTopicScreen(\n                        type = type,\n                        onViewUser = onViewUser,\n                        onViewFeed = onViewFeed,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText\n                    )\n                }\n\n            }\n\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/app/AppListScreen.kt",
    "content": "package com.example.c001apk.compose.ui.home.app\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Update\nimport androidx.compose.material3.CircularProgressIndicator\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.ListItem\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.StrokeCap\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.unit.dp\nimport androidx.core.view.isVisible\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.example.c001apk.compose.logic.model.UpdateCheckItem\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.util.isScrollingUp\nimport com.example.c001apk.compose.util.longVersionCodeCompat\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AppListScreen(\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    onViewApp: (String) -> Unit,\n    onCheckUpdate: (List<UpdateCheckItem>) -> Unit\n) {\n\n    val viewModel = hiltViewModel<AppListViewModel>()\n    val prefs = LocalUserPreferences.current\n    val context = LocalContext.current\n    val view = LocalView.current\n    val state = rememberPullToRefreshState()\n    val lazyListState = rememberLazyListState()\n\n    LaunchedEffect(refreshState) {\n        if (refreshState) {\n            resetRefreshState()\n            if (view.isVisible) {\n                viewModel.refresh()\n                lazyListState.scrollToItem(0)\n            }\n        }\n    }\n\n    PullToRefreshBox(\n        state = state,\n        isRefreshing = viewModel.isRefreshing,\n        onRefresh = viewModel::refresh,\n        indicator = {\n            PullToRefreshDefaults.Indicator(\n                modifier = Modifier.align(Alignment.TopCenter),\n                isRefreshing = viewModel.isRefreshing,\n                state = state,\n                color = MaterialTheme.colorScheme.primary,\n            )\n        }\n    ) {\n        LazyColumn(\n            modifier = Modifier.fillMaxSize(),\n            state = lazyListState,\n        ) {\n\n            items(\n                items = viewModel.appList,\n                key = { it.packageInfo.packageName }\n            ) {\n                ListItem(\n                    modifier = Modifier.clickable {\n                        onViewApp(it.packageInfo.packageName)\n                    },\n                    leadingContent = {\n                        AsyncImage(\n                            model = ImageRequest.Builder(context)\n                                .data(it.packageInfo)\n                                .crossfade(true)\n                                .build(),\n                            contentDescription = it.label,\n                            modifier = Modifier\n                                .padding(4.dp)\n                                .width(48.dp)\n                                .height(48.dp)\n                        )\n                    },\n                    headlineContent = {\n                        Text(text = it.label)\n                    },\n                    supportingContent = {\n                        Text(\n                            text =\n                            \"${it.packageInfo.packageName}\\n${it.packageInfo.versionName}(${it.packageInfo.longVersionCodeCompat})\"\n                        )\n                    }\n                )\n            }\n\n        }\n\n        if (viewModel.isLoading) {\n            CircularProgressIndicator(\n                modifier = Modifier.align(Alignment.Center),\n                strokeCap = StrokeCap.Round\n            )\n        }\n\n        if (prefs.checkUpdate && viewModel.appList.isNotEmpty()) {\n            AnimatedVisibility(\n                visible = lazyListState.isScrollingUp(),\n                enter = slideInVertically { it * 2 },\n                exit = slideOutVertically { it * 2 },\n                modifier = Modifier\n                    .align(Alignment.BottomEnd)\n                    .padding(20.dp)\n            ) {\n                FloatingActionButton(\n                    onClick = { onCheckUpdate(viewModel.dataList) },\n                ) {\n                    Icon(imageVector = Icons.Default.Update, contentDescription = null)\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/app/AppListViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.home.app\n\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.c001Application\nimport com.example.c001apk.compose.logic.model.AppItem\nimport com.example.c001apk.compose.logic.model.UpdateCheckItem\nimport com.example.c001apk.compose.util.Utils\nimport com.example.c001apk.compose.util.longVersionCodeCompat\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\nclass AppListViewModel : ViewModel() {\n\n    var appList by mutableStateOf<List<AppItem>>(emptyList())\n        private set\n    var isRefreshing by mutableStateOf(false)\n        private set\n    var isLoading by mutableStateOf(true)\n        private set\n\n    init {\n        fetchAppList()\n    }\n\n    val dataList = ArrayList<UpdateCheckItem>()\n\n    private fun fetchAppList() {\n        viewModelScope.launch(Dispatchers.IO) {\n            val pm = c001Application.packageManager\n            val infoList = pm.getInstalledApplications(PackageManager.GET_SHARED_LIBRARY_FILES)\n            val itemList: MutableList<AppItem> = ArrayList()\n\n            infoList.forEach { info ->\n                if (((info.flags and ApplicationInfo.FLAG_SYSTEM) != ApplicationInfo.FLAG_SYSTEM)) {\n                    val packageInfo = pm.getPackageInfo(info.packageName, 0)\n\n                    if (info.packageName != \"com.example.c001apk.compose\") {\n                        val appItem = AppItem(\n                            label = info.loadLabel(pm).toString(),\n                            packageInfo = packageInfo\n                        )\n                        itemList.add(appItem)\n\n                        dataList.add(\n                            UpdateCheckItem(\n                                info.packageName,\n                                \"0,${packageInfo.longVersionCodeCompat},${\n                                    Utils.getInstalledAppMd5(\n                                        info\n                                    )\n                                }\"\n                            )\n                        )\n                    }\n\n                }\n            }\n\n            appList = itemList.sortedByDescending { it.packageInfo.lastUpdateTime }\n\n            isRefreshing = false\n            isLoading = false\n        }\n    }\n\n    fun refresh() {\n        if (!isRefreshing) {\n            isRefreshing = true\n            fetchAppList()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/feed/HomeFeedScreen.kt",
    "content": "package com.example.c001apk.compose.ui.home.feed\n\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.FollowType\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.ui.home.TabType\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/2\n */\n@Composable\nfun HomeFeedScreen(\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    type: TabType,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    isScrollingUp: ((Boolean) -> Unit)? = null,\n) {\n\n    val context = LocalContext.current\n    val prefs = LocalUserPreferences.current\n\n    val dataListUrl: String? = if (type == TabType.FOLLOW)\n        when (prefs.followType) {\n            FollowType.ALL -> \"/page?url=V9_HOME_TAB_FOLLOW\"\n            FollowType.USER -> \"/page?url=V9_HOME_TAB_FOLLOW&type=circle\"\n            FollowType.TOPIC -> \"/page?url=V9_HOME_TAB_FOLLOW&type=topic\"\n            FollowType.PRODUCT -> \"/page?url=V9_HOME_TAB_FOLLOW&type=product\"\n            FollowType.APP -> \"/page?url=V9_HOME_TAB_FOLLOW&type=apk\"\n            FollowType.UNRECOGNIZED -> EMPTY_STRING\n        }\n    else null\n\n    val dataListTitle: String? = if (type == TabType.FOLLOW)\n        when (prefs.followType) {\n            FollowType.ALL -> \"全部关注\"\n            FollowType.USER -> \"好友关注\"\n            FollowType.TOPIC -> \"话题关注\"\n            FollowType.PRODUCT -> \"数码关注\"\n            FollowType.APP -> \"应用关注\"\n            FollowType.UNRECOGNIZED -> EMPTY_STRING\n        }\n    else null\n\n    val viewModel =\n        hiltViewModel<HomeFeedViewModel, HomeFeedViewModel.ViewModelFactory>(key = type.name) { factory ->\n            factory.create(\n                type = type,\n                dataListUrl = when (type) {\n                    TabType.FOLLOW -> dataListUrl ?: EMPTY_STRING\n                    TabType.HOT -> \"/page?url=V9_HOME_TAB_RANKING\"\n                    TabType.COOLPIC -> \"/page?url=V11_FIND_COOLPIC\"\n                    else -> EMPTY_STRING\n                },\n                dataListTitle = when (type) {\n                    TabType.FOLLOW -> dataListTitle ?: EMPTY_STRING\n                    TabType.HOT -> \"热榜\"\n                    TabType.COOLPIC -> \"酷图\"\n                    else -> EMPTY_STRING\n                },\n                installTime = prefs.installTime\n            )\n        }\n\n    LaunchedEffect(prefs.followType) {\n        if (type == TabType.FOLLOW) {\n            viewModel.dataListUrl = dataListUrl ?: EMPTY_STRING\n            viewModel.dataListTitle = dataListTitle ?: EMPTY_STRING\n        }\n    }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n        isScrollingUp = isScrollingUp,\n    )\n\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/feed/HomeFeedViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.home.feed\n\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport com.example.c001apk.compose.ui.home.TabType\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\n@HiltViewModel(assistedFactory = HomeFeedViewModel.ViewModelFactory::class)\nclass HomeFeedViewModel @AssistedInject constructor(\n    @Assisted val type: TabType = TabType.FEED,\n    @Assisted(\"dataListUrl\") var dataListUrl: String,\n    @Assisted(\"dataListTitle\") var dataListTitle: String,\n    @Assisted(\"installTime\") var installTime: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n    private val userPreferencesRepository: UserPreferencesRepository,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            type: TabType,\n            @Assisted(\"dataListUrl\") dataListUrl: String,\n            @Assisted(\"dataListTitle\") dataListTitle: String,\n            @Assisted(\"installTime\") installTime: String,\n        ): HomeFeedViewModel\n    }\n\n    init {\n        if (installTime.isEmpty()) {\n            installTime = System.currentTimeMillis().toString()\n            setInstallTime()\n        }\n        fetchData()\n    }\n\n    override suspend fun customFetchData() = when (type) {\n        TabType.FOLLOW, TabType.HOT, TabType.COOLPIC ->\n            networkRepo.getDataList(dataListUrl, dataListTitle, null, lastItem, page)\n\n        TabType.FEED -> networkRepo.getHomeFeed(page, firstLaunch, installTime, null, null)\n\n        else -> throw IllegalArgumentException(\"invalid type: ${type.name}\")\n    }\n\n    private fun setInstallTime() {\n        viewModelScope.launch {\n            userPreferencesRepository.setInstallTime(installTime)\n        }\n    }\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/topic/HomeTopicScreen.kt",
    "content": "package com.example.c001apk.compose.ui.home.topic\n\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxHeight\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.foundation.pager.PagerState\nimport androidx.compose.foundation.pager.VerticalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.model.TopicBean\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.carousel.CarouselContentScreen\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.home.TabType\nimport com.example.c001apk.compose.ui.theme.cardBg\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@Composable\nfun HomeTopicScreen(\n    type: TabType,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<HomeTopicViewModel, HomeTopicViewModel.ViewModelFactory>(key = type.name) { factory ->\n            factory.create(\n                url = when (type) {\n                    TabType.TOPIC -> \"/v6/page/dataList?url=V11_VERTICAL_TOPIC&title=话题&page=1\"\n                    TabType.PRODUCT -> \"/v6/product/categoryList\"\n                    else -> throw IllegalArgumentException(\"invalid type: $type\")\n                }\n            )\n        }\n    val scope = rememberCoroutineScope()\n    val currentIndex = when (type) {\n        TabType.TOPIC -> 1\n        TabType.PRODUCT -> 0\n        else -> throw IllegalArgumentException(\"invalid type: $type\")\n    }\n    val listState = rememberLazyListState()\n    var pageState: PagerState\n    var tabList: List<TopicBean>?\n\n    Box(modifier = Modifier.fillMaxSize()) {\n        when (viewModel.loadingState) {\n            LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                Box(modifier = Modifier.fillMaxSize()) {\n                    LoadingCard(\n                        modifier = Modifier\n                            .align(Alignment.Center)\n                            .padding(horizontal = 10.dp),\n                        state = viewModel.loadingState,\n                        onClick = if (viewModel.loadingState is LoadingState.Loading) null\n                        else viewModel::loadMore\n                    )\n                }\n            }\n\n            is LoadingState.Success -> {\n\n                tabList = when (type) {\n                    TabType.TOPIC -> (viewModel.loadingState as LoadingState.Success<List<HomeFeedResponse.Data>>)\n                        .response.getOrNull(0)?.entities?.map {\n                            TopicBean(it.url.orEmpty(), it.title.orEmpty())\n                        }\n\n                    TabType.PRODUCT -> (viewModel.loadingState as LoadingState.Success<List<HomeFeedResponse.Data>>)\n                        .response.map {\n                            TopicBean(it.url.orEmpty(), it.title.orEmpty())\n                        }\n\n                    else -> throw IllegalArgumentException(\"invalid type: $type\")\n                }\n\n                tabList?.let {\n                    pageState = rememberPagerState(\n                        initialPage = currentIndex,\n                        pageCount = {\n                            it.size\n                        }\n                    )\n\n                    Row(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .background(cardBg())\n                    ) {\n\n                        LazyColumn(\n                            state = listState,\n                            modifier = Modifier\n                                .fillMaxHeight()\n                                .weight(0.22f)\n                        ) {\n                            itemsIndexed(it, key = { _, item -> item.title }) { index, item ->\n                                Row(\n                                    modifier = Modifier\n                                        .height(IntrinsicSize.Min)\n                                        .fillMaxWidth()\n                                        .clickable {\n                                            scope.launch {\n                                                pageState.scrollToPage(index)\n                                            }\n                                        }\n                                ) {\n                                    Box(\n                                        modifier = Modifier\n                                            .fillMaxHeight()\n                                            .width(3.dp)\n                                            .background(\n                                                if (index == pageState.currentPage) MaterialTheme.colorScheme.primary\n                                                else MaterialTheme.colorScheme.surface\n                                            )\n                                    )\n                                    Text(\n                                        text = item.title,\n                                        modifier = Modifier\n                                            .weight(1f)\n                                            .background(\n                                                if (index == pageState.currentPage)\n                                                    MaterialTheme.colorScheme.surfaceColorAtElevation(\n                                                        3.dp\n                                                    )\n                                                else MaterialTheme.colorScheme.surface\n                                            )\n                                            .padding(8.dp)\n                                            .align(Alignment.CenterVertically),\n                                        textAlign = TextAlign.Center,\n                                        fontSize = 14.sp,\n                                        color = if (index == pageState.currentPage) MaterialTheme.colorScheme.primary\n                                        else MaterialTheme.colorScheme.onSurface\n                                    )\n                                }\n                            }\n                        }\n\n                        VerticalPager(\n                            state = pageState,\n                            modifier = Modifier\n                                .fillMaxHeight()\n                                .weight(0.78f),\n                            userScrollEnabled = false,\n                        ) { index ->\n                            CarouselContentScreen(\n                                url = it[index].url,\n                                title = it[index].title,\n                                paddingValues = PaddingValues(),\n                                refreshState = null,\n                                resetRefreshState = {},\n                                onViewUser = onViewUser,\n                                onViewFeed = onViewFeed,\n                                onOpenLink = onOpenLink,\n                                onCopyText = onCopyText,\n                                isHomeFeed = true,\n                            )\n                        }\n\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/home/topic/HomeTopicViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.home.topic\n\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@HiltViewModel(assistedFactory = HomeTopicViewModel.ViewModelFactory::class)\nclass HomeTopicViewModel @AssistedInject constructor(\n    @Assisted val url: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(url: String): HomeTopicViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() = networkRepo.getProductList(url)\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/login/LoginScreen.kt",
    "content": "package com.example.c001apk.compose.ui.login\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.IntrinsicSize\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.text.KeyboardActions\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Visibility\nimport androidx.compose.material.icons.filled.VisibilityOff\nimport androidx.compose.material.icons.rounded.Cancel\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FilledTonalButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TextField\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.PasswordVisualTransformation\nimport androidx.compose.ui.text.input.VisualTransformation\nimport androidx.compose.ui.unit.dp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport coil.compose.AsyncImage\nimport coil.request.ImageRequest\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.util.createRandomNumber\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun LoginScreen(\n    onBackClick: () -> Unit,\n    onWebLogin: () -> Unit,\n) {\n\n    val viewModel = hiltViewModel<LoginViewModel>()\n\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    val prefs = LocalUserPreferences.current\n    var account by remember { mutableStateOf(EMPTY_STRING) }\n    var password by remember { mutableStateOf(EMPTY_STRING) }\n    var captcha by remember { mutableStateOf(EMPTY_STRING) }\n    var passwordHidden by remember { mutableStateOf(true) }\n\n    fun onLogin() {\n        if (account.isNotEmpty() && password.isNotEmpty()) {\n            context.makeToast(\"正在登录...\")\n            viewModel.loginData[\"submit\"] = \"1\"\n            viewModel.loginData[\"randomNumber\"] = createRandomNumber()\n            viewModel.loginData[\"requestHash\"] = viewModel.requestHash\n            viewModel.loginData[\"login\"] = account\n            viewModel.loginData[\"password\"] = password\n            viewModel.loginData[\"captcha\"] = captcha\n            viewModel.loginData[\"code\"] = EMPTY_STRING\n            viewModel.onLogin()\n        } else {\n            context.makeToast(\"用户名或密码为空\")\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(text = \"登录\")\n                },\n                actions = {\n                    TextButton(\n                        onClick = onWebLogin,\n                        modifier = Modifier.padding(end = 20.dp)\n                    ) {\n                        Text(text = \"网页登录\")\n                    }\n                }\n            )\n        },\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier.padding(\n                top = paddingValues.calculateTopPadding(),\n                start = paddingValues.calculateLeftPadding(layoutDirection),\n            )\n        ) {\n\n            HorizontalDivider()\n\n            TextField(\n                value = account,\n                onValueChange = { account = it },\n                singleLine = true,\n                label = { Text(\"账号\") },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(top = 20.dp)\n                    .padding(horizontal = 20.dp),\n                keyboardOptions = KeyboardOptions(\n                    keyboardType = KeyboardType.Text,\n                    imeAction = ImeAction.Next\n                ),\n                trailingIcon = {\n                    AnimatedVisibility(\n                        visible = account.isNotEmpty(),\n                        enter = fadeIn(),\n                        exit = fadeOut()\n                    ) {\n                        IconButton(onClick = {\n                            account = EMPTY_STRING\n                        }) {\n                            Icon(\n                                imageVector = Icons.Rounded.Cancel,\n                                contentDescription = null\n                            )\n                        }\n                    }\n                },\n            )\n            TextField(\n                value = password,\n                onValueChange = { password = it },\n                singleLine = true,\n                label = { Text(\"密码\") },\n                visualTransformation =\n                if (passwordHidden) PasswordVisualTransformation() else VisualTransformation.None,\n                keyboardOptions = KeyboardOptions(\n                    keyboardType = KeyboardType.Password,\n                    imeAction = ImeAction.Done\n                ),\n                keyboardActions = KeyboardActions(\n                    onDone = {\n                        onLogin()\n                    }\n                ),\n                trailingIcon = {\n                    IconButton(onClick = { passwordHidden = !passwordHidden }) {\n                        val visibilityIcon =\n                            if (passwordHidden) Icons.Filled.Visibility else Icons.Filled.VisibilityOff\n                        Icon(imageVector = visibilityIcon, contentDescription = null)\n                    }\n                },\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(horizontal = 20.dp)\n                    .padding(top = 5.dp)\n            )\n            AnimatedVisibility(\n                visible = viewModel.captchaImg != null,\n                enter = fadeIn(),\n                exit = fadeOut()\n            ) {\n                Row(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(horizontal = 20.dp)\n                        .height(IntrinsicSize.Min)\n                        .padding(top = 5.dp),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    AsyncImage(\n                        model = ImageRequest.Builder(context)\n                            .data(viewModel.captchaImg)\n                            .crossfade(true)\n                            .build(),\n                        contentDescription = null,\n                        modifier = Modifier\n                            .weight(1f)\n                            .clickable {\n                                viewModel.onGetCaptcha()\n                            }\n                    )\n                    TextField(\n                        value = captcha,\n                        onValueChange = { captcha = it },\n                        modifier = Modifier.weight(1f),\n                        singleLine = true,\n                        trailingIcon = {\n                            AnimatedVisibility(\n                                visible = captcha.isNotEmpty(),\n                                enter = fadeIn(),\n                                exit = fadeOut()\n                            ) {\n                                IconButton(onClick = {\n                                    captcha = EMPTY_STRING\n                                }) {\n                                    Icon(\n                                        imageVector = Icons.Rounded.Cancel,\n                                        contentDescription = null\n                                    )\n                                }\n                            }\n                        },\n                    )\n                }\n            }\n\n            FilledTonalButton(\n                enabled = viewModel.requestHash.isNotEmpty(),\n                onClick = {\n                    onLogin()\n                },\n                modifier = Modifier\n                    .align(Alignment.CenterHorizontally)\n                    .padding(top = 5.dp)\n            ) {\n                Text(text = \"登录\")\n            }\n\n        }\n    }\n\n    if (prefs.isLogin) {\n        onBackClick()\n    }\n\n    viewModel.toastText?.let {\n        context.makeToast(it)\n        viewModel.reset()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/login/LoginViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.login\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.LoginResponse\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.util.CookieUtil.SESSID\nimport com.example.c001apk.compose.util.CookieUtil.isGetCaptcha\nimport com.example.c001apk.compose.util.CookieUtil.isGetLoginParam\nimport com.example.c001apk.compose.util.CookieUtil.isPreGetLoginParam\nimport com.example.c001apk.compose.util.CookieUtil.isTryLogin\nimport com.example.c001apk.compose.util.createRequestHash\nimport com.google.gson.Gson\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport org.jsoup.Jsoup\nimport javax.inject.Inject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/10\n */\n@HiltViewModel\nclass LoginViewModel @Inject constructor(\n    private val networkRepo: NetworkRepo,\n    private val userPreferencesRepository: UserPreferencesRepository\n) : ViewModel() {\n\n    var requestHash by mutableStateOf(EMPTY_STRING)\n    var captchaImg by mutableStateOf<Bitmap?>(null)\n        private set\n    var toastText by mutableStateOf<String?>(null)\n        private set\n\n    private val urlPreGetParam = \"/auth/login?type=mobile\"\n    private val urlGetParam = \"/auth/loginByCoolApk\"\n\n    init {\n        isPreGetLoginParam = true\n        viewModelScope.launch(Dispatchers.IO) {\n            onGetLoginParam(urlPreGetParam)\n        }\n    }\n\n    private suspend fun onGetLoginParam(url: String) {\n        networkRepo.getLoginParam(url)\n            .collect { result ->\n                val response = result.getOrNull()\n                if (response != null) {\n                    if (url == urlGetParam) {\n                        response.body()?.string()?.let {\n                            requestHash = Jsoup.parse(it).createRequestHash()\n                        }\n                    }\n                    try {\n                        val session = response.headers().values(\"Set-Cookie\")[0]\n                        SESSID = session.substring(0, session.indexOf(\";\"))\n                    } catch (e: Exception) {\n                        e.printStackTrace()\n                        toastText = \"无法获取Cookie\"\n                        return@collect\n                    }\n                    if (url == urlPreGetParam) {\n                        isGetLoginParam = true\n                        onGetLoginParam(urlGetParam)\n                    }\n                } else {\n                    toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                    result.exceptionOrNull()?.printStackTrace()\n                }\n            }\n    }\n\n    fun onGetCaptcha() {\n        isGetCaptcha = true\n        val timeStamp = System.currentTimeMillis().toString()\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getCaptcha(\"/auth/showCaptchaImage?$timeStamp\")\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        captchaImg = BitmapFactory.decodeStream(response.body()?.byteStream())\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    var loginData = HashMap<String, String?>()\n    fun onLogin() {\n        isTryLogin = true\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.tryLogin(loginData)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    response?.body()?.let {\n                        val login: LoginResponse = Gson().fromJson(\n                            response.body()?.string(),\n                            LoginResponse::class.java\n                        )\n                        if (login.status == 1) {\n                            val cookies = response.headers().values(\"Set-Cookie\")\n                            val uid =\n                                cookies.find { it.startsWith(\"uid=\") }?.split(\";\")\n                                    ?.getOrNull(0)\n                                    ?.replace(\"uid=\", EMPTY_STRING)?.trim()\n                            val username =\n                                cookies.find { it.startsWith(\"username=\") }?.split(\";\")\n                                    ?.getOrNull(0)?.replace(\"username=\", EMPTY_STRING)?.trim()\n                            val token =\n                                cookies.findLast { it.startsWith(\"token=\") }?.split(\";\")\n                                    ?.getOrNull(0)\n                                    ?.replace(\"token=\", EMPTY_STRING)?.trim()\n                            if (!uid.isNullOrEmpty() && !username.isNullOrEmpty() && !token.isNullOrEmpty()) {\n                                userPreferencesRepository.apply {\n                                    setIsLogin(true)\n                                    setUid(uid)\n                                    setUsername(username)\n                                    setToken(token)\n                                }\n                            }\n                        } else {\n                            login.message?.let {\n                                toastText = login.message\n                                when (login.message) {\n                                    \"图形验证码不能为空\", \"图形验证码错误\" -> onGetCaptcha()\n\n                                    \"密码错误\" -> if (captchaImg != null) onGetCaptcha()\n                                }\n                            }\n                        }\n                    }\n                }\n        }\n    }\n\n    fun reset() {\n        toastText = null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/main/MainActivity.kt",
    "content": "package com.example.c001apk.compose.ui.main\n\nimport android.content.Intent\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.activity.viewModels\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Surface\nimport androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi\nimport androidx.compose.material3.windowsizeclass.calculateWindowSizeClass\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.ui.Modifier\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport androidx.navigation.NavHostController\nimport androidx.navigation.compose.rememberNavController\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.ui.theme.C001apkComposeTheme\nimport com.example.c001apk.compose.util.CookieUtil.apiVersion\nimport com.example.c001apk.compose.util.CookieUtil.imageFilter\nimport com.example.c001apk.compose.util.CookieUtil.imageQuality\nimport com.example.c001apk.compose.util.CookieUtil.isDarkMode\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.CookieUtil.materialYou\nimport com.example.c001apk.compose.util.CookieUtil.openInBrowser\nimport com.example.c001apk.compose.util.CookieUtil.recordHistory\nimport com.example.c001apk.compose.util.CookieUtil.sdkInt\nimport com.example.c001apk.compose.util.CookieUtil.showEmoji\nimport com.example.c001apk.compose.util.CookieUtil.showSquare\nimport com.example.c001apk.compose.util.CookieUtil.szlmId\nimport com.example.c001apk.compose.util.CookieUtil.token\nimport com.example.c001apk.compose.util.CookieUtil.uid\nimport com.example.c001apk.compose.util.CookieUtil.userAgent\nimport com.example.c001apk.compose.util.CookieUtil.username\nimport com.example.c001apk.compose.util.CookieUtil.versionCode\nimport com.example.c001apk.compose.util.CookieUtil.versionName\nimport com.example.c001apk.compose.util.CookieUtil.xAppDevice\nimport dagger.hilt.android.AndroidEntryPoint\nimport javax.inject.Inject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/29\n */\n@AndroidEntryPoint\nclass MainActivity : ComponentActivity() {\n\n    @Inject\n    lateinit var userPreferencesRepository: UserPreferencesRepository\n    private lateinit var navController: NavHostController\n    private val viewModel by viewModels<MainViewModel>()\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        handleIntent(intent)\n    }\n\n    private fun handleIntent(intent: Intent) {\n        intent.data?.let {\n            navController.onOpenLink(\n                context = this,\n                url = it.toString(),\n                title = null,\n                needConvert = true\n            )\n        }\n    }\n\n    @OptIn(ExperimentalMaterial3WindowSizeClassApi::class)\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge()\n\n        setContent {\n            navController = rememberNavController()\n\n            val widthSizeClass = calculateWindowSizeClass(this).widthSizeClass\n\n            val userPreferences by userPreferencesRepository.data\n                .collectAsStateWithLifecycle(\n                    initialValue = null,\n                    lifecycleOwner = androidx.lifecycle.compose.LocalLifecycleOwner.current\n                )\n\n            val preferences = if (userPreferences == null) {\n                return@setContent\n            } else {\n                checkNotNull(userPreferences)\n            }\n\n            CompositionLocalProvider(\n                LocalUserPreferences provides preferences\n            ) {\n                if (preferences.xAppDevice.isEmpty())\n                    viewModel.regenerateParams()\n\n                isLogin = preferences.isLogin\n                szlmId = preferences.szlmId\n                xAppDevice = preferences.xAppDevice\n                uid = preferences.uid\n                username = preferences.username\n                token = preferences.token\n                userAgent = preferences.userAgent\n                sdkInt = preferences.sdkInt\n                versionName = preferences.versionName\n                versionCode = preferences.versionCode\n                apiVersion = preferences.apiVersion\n                imageQuality = preferences.imageQuality\n                showEmoji = preferences.showEmoji\n                showSquare = preferences.showSquare\n                openInBrowser = preferences.openInBrowser\n                imageFilter = preferences.imageFilter\n                recordHistory = preferences.recordHistory\n                materialYou = preferences.materialYou\n                isDarkMode = preferences.isDarkMode()\n\n                C001apkComposeTheme(\n                    darkTheme = preferences.isDarkMode(),\n                    themeType = preferences.themeType,\n                    seedColor = preferences.seedColor,\n                    materialYou = preferences.materialYou,\n                    pureBlack = preferences.pureBlack,\n                    paletteStyle = preferences.paletteStyle,\n                    fontScale = preferences.fontScale,\n                    contentScale = preferences.contentScale,\n                ) {\n                    Surface(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .background(MaterialTheme.colorScheme.background)\n                    ) {\n                        MainNavigation(\n                            navController = navController,\n                            badge = viewModel.badge,\n                            resetBadge = viewModel::resetBadge,\n                            widthSizeClass = widthSizeClass,\n                        )\n                    }\n                }\n            }\n\n            LaunchedEffect(key1 = navController) {\n                if (savedInstanceState == null) {\n                    handleIntent(intent)\n                }\n            }\n\n        }\n\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/main/MainNavigation.kt",
    "content": "package com.example.c001apk.compose.ui.main\n\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Bundle\nimport androidx.compose.foundation.background\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.AllInclusive\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.surfaceColorAtElevation\nimport androidx.compose.material3.windowsizeclass.WindowWidthSizeClass\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.unit.dp\nimport androidx.navigation.NavHostController\nimport androidx.navigation.NavOptions\nimport androidx.navigation.NavType\nimport androidx.navigation.Navigator\nimport androidx.navigation.compose.NavHost\nimport androidx.navigation.compose.composable\nimport androidx.navigation.navArgument\nimport com.example.c001apk.compose.constant.Constants.CHANNEL\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.PREFIX_APP\nimport com.example.c001apk.compose.constant.Constants.PREFIX_CAROUSEL\nimport com.example.c001apk.compose.constant.Constants.PREFIX_CAROUSEL1\nimport com.example.c001apk.compose.constant.Constants.PREFIX_COLLECTION\nimport com.example.c001apk.compose.constant.Constants.PREFIX_COOLMARKET\nimport com.example.c001apk.compose.constant.Constants.PREFIX_DYH\nimport com.example.c001apk.compose.constant.Constants.PREFIX_FEED\nimport com.example.c001apk.compose.constant.Constants.PREFIX_GAME\nimport com.example.c001apk.compose.constant.Constants.PREFIX_HTTP\nimport com.example.c001apk.compose.constant.Constants.PREFIX_PRODUCT\nimport com.example.c001apk.compose.constant.Constants.PREFIX_TOPIC\nimport com.example.c001apk.compose.constant.Constants.PREFIX_USER\nimport com.example.c001apk.compose.constant.Constants.PREFIX_USER_LIST\nimport com.example.c001apk.compose.constant.Constants.URL_LOGIN\nimport com.example.c001apk.compose.logic.model.UpdateCheckItem\nimport com.example.c001apk.compose.ui.app.AppScreen\nimport com.example.c001apk.compose.ui.appupdate.AppUpdateScreen\nimport com.example.c001apk.compose.ui.blacklist.BlackListScreen\nimport com.example.c001apk.compose.ui.carousel.CarouselScreen\nimport com.example.c001apk.compose.ui.chat.ChatScreen\nimport com.example.c001apk.compose.ui.collection.CollectionScreen\nimport com.example.c001apk.compose.ui.component.SlideTransition\nimport com.example.c001apk.compose.ui.coolpic.CoolPicScreen\nimport com.example.c001apk.compose.ui.dyh.DyhScreen\nimport com.example.c001apk.compose.ui.feed.FeedScreen\nimport com.example.c001apk.compose.ui.ffflist.FFFListScreen\nimport com.example.c001apk.compose.ui.ffflist.FFFListType\nimport com.example.c001apk.compose.ui.history.HistoryScreen\nimport com.example.c001apk.compose.ui.login.LoginScreen\nimport com.example.c001apk.compose.ui.notification.NoticeScreen\nimport com.example.c001apk.compose.ui.others.CopyTextScreen\nimport com.example.c001apk.compose.ui.search.SearchResultScreen\nimport com.example.c001apk.compose.ui.search.SearchScreen\nimport com.example.c001apk.compose.ui.settings.AboutScreen\nimport com.example.c001apk.compose.ui.settings.LicenseScreen\nimport com.example.c001apk.compose.ui.settings.ParamsScreen\nimport com.example.c001apk.compose.ui.topic.TopicScreen\nimport com.example.c001apk.compose.ui.user.UserScreen\nimport com.example.c001apk.compose.ui.webview.WebViewScreen\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.CookieUtil.openInBrowser\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.decode\nimport com.example.c001apk.compose.util.encode\nimport com.example.c001apk.compose.util.getReportUrl\nimport com.example.c001apk.compose.util.makeToast\nimport com.example.c001apk.compose.util.openInBrowser\nimport kotlin.math.max\nimport kotlin.math.min\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/30\n */\n@Composable\nfun MainNavigation(\n    navController: NavHostController,\n    badge: Int,\n    resetBadge: () -> Unit,\n    widthSizeClass: WindowWidthSizeClass,\n) {\n\n    val context = LocalContext.current\n    var initialPage = 0\n    var selectIndex by rememberSaveable { mutableIntStateOf(0) }\n    val isCompat by lazy { widthSizeClass == WindowWidthSizeClass.Compact }\n    var compatId by remember { mutableStateOf<String?>(null) }\n    var compatReply by remember { mutableStateOf<Boolean?>(null) }\n\n    fun onReport(id: String, type: ReportType) {\n        navController.navigateToWebView(getReportUrl(id, type))\n    }\n\n    fun onViewFeed(viewId: String, isViewReply: Boolean) {\n        if (selectIndex != 2 && !isCompat) {\n            compatId = viewId\n            compatReply = isViewReply\n        } else {\n            navController.navigateToFeed(viewId, isViewReply)\n        }\n    }\n\n    fun onOpenLink(url:String, title: String?){\n        navController.onOpenLink(\n            context,\n            url,\n            title\n        ) { viewId, isViewReply ->\n            onViewFeed(viewId, isViewReply)\n        }\n    }\n\n    Row(modifier = Modifier.fillMaxSize()) {\n        NavHost(\n            modifier = Modifier.weight(1f),\n            navController = navController,\n            startDestination = Router.MAIN.name,\n            enterTransition = {\n                SlideTransition.slideLeft.enterTransition()\n            },\n            exitTransition = {\n                SlideTransition.slideLeft.exitTransition()\n            },\n            popEnterTransition = {\n                SlideTransition.slideRight.enterTransition()\n            },\n            popExitTransition = {\n                SlideTransition.slideRight.exitTransition()\n            }\n        ) {\n\n            composable(route = Router.MAIN.name) {\n                MainScreen(\n                    selectIndex = selectIndex,\n                    setSelectIndex = {\n                        selectIndex = it\n                    },\n                    badge = badge,\n                    resetBadge = resetBadge,\n                    widthSizeClass = widthSizeClass,\n                    onParamsClick = {\n                        navController.navigate(Router.PARAMS.name)\n                    },\n                    onAboutClick = {\n                        navController.navigate(Router.ABOUT.name)\n                    },\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onSearch = {\n                        initialPage = 0\n                        navController.navigateToSearch(null, null, null)\n                    },\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onViewApp = navController::navigateToApp,\n                    onLogin = {\n                        navController.navigate(Router.LOGIN.name)\n                    },\n                    onCheckUpdate = { data ->\n                        val bundle = Bundle()\n                        bundle.putParcelableArrayList(\"list\", ArrayList(data))\n                        navController.navigate(route = Router.UPDATE.name, args = bundle)\n                    },\n                    onViewFFFList = { viewUid, viewType ->\n                        navController.navigateToFFFList(viewUid, viewType, null, null)\n                    },\n                    onReport = ::onReport,\n                    onViewNotice = navController::navigateToNotice,\n                    onViewBlackList = navController::navigateToBlackList,\n                    onViewHistory = navController::navigateToHistory,\n                )\n            }\n\n            composable(route = Router.PARAMS.name) {\n                ParamsScreen(\n                    onBackClick = navController::popBackStack\n                )\n            }\n\n            composable(route = Router.ABOUT.name) {\n                AboutScreen(\n                    onBackClick = navController::popBackStack,\n                    onLicenseClick = {\n                        navController.navigate(Router.LICENSE.name)\n                    }\n                )\n            }\n\n            composable(route = Router.LICENSE.name) {\n                LicenseScreen(\n                    onBackClick = navController::popBackStack\n                )\n            }\n\n            composable(\n                route = \"${Router.FEED.name}/{id}/{isViewReply}\",\n                arguments = listOf(\n                    navArgument(\"id\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"isViewReply\") {\n                        type = NavType.BoolType\n                    },\n                )\n            ) {\n                val id = it.arguments?.getString(\"id\").orEmpty()\n                val isViewReply = it.arguments?.getBoolean(\"isViewReply\") ?: false\n                FeedScreen(\n                    onBackClick = navController::popBackStack,\n                    id = id,\n                    isViewReply = isViewReply,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport,\n                )\n            }\n\n            composable(\n                route = \"${Router.USER.name}/{uid}\",\n                arguments = listOf(\n                    navArgument(\"uid\") {\n                        type = NavType.StringType\n                    }\n                )\n            ) {\n                val uid = it.arguments?.getString(\"uid\").orEmpty()\n                UserScreen(\n                    uid = uid,\n                    onBackClick = navController::popBackStack,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onSearch = { title, pageType, pageParam ->\n                        initialPage = 0\n                        navController.navigateToSearch(title, pageType, pageParam)\n                    },\n                    onViewFFFList = { viewUid, viewType ->\n                        navController.navigateToFFFList(viewUid, viewType, null, null)\n                    },\n                    onReport = ::onReport,\n                    onPMUser = { viewUid, viewUsername ->\n                        navController.navigateToChat(\n                            \"${\n                                min(\n                                    viewUid.toLongOrNull() ?: 0,\n                                    CookieUtil.uid.toLongOrNull() ?: 0\n                                )\n                            }_${\n                                max(\n                                    viewUid.toLongOrNull() ?: 0,\n                                    CookieUtil.uid.toLongOrNull() ?: 0\n                                )\n                            }\",\n                            viewUid,\n                            viewUsername\n                        )\n                    }\n                )\n            }\n\n            composable(\n                route = \"${Router.SEARCH.name}/{title}/{pageType}/{pageParam}\",\n                arguments = listOf(\n                    navArgument(\"title\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"pageType\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"pageParam\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                )\n            ) {\n                val title = it.arguments?.getString(\"title\")\n                val pageType = it.arguments?.getString(\"pageType\")\n                val pageParam = it.arguments?.getString(\"pageParam\")\n                SearchScreen(\n                    onBackClick = navController::popBackStack,\n                    title = title,\n                    onSearch = { keyword ->\n                        navController.navigateToSearchResult(keyword, title, pageType, pageParam)\n                    }\n                )\n            }\n\n            composable(\n                route = \"${Router.SEARCHRESULT.name}/{keyword}/{title}/{pageType}/{pageParam}\",\n                arguments = listOf(\n                    navArgument(\"keyword\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"title\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"pageType\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"pageParam\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                )\n            ) {\n                val keyword = it.arguments?.getString(\"keyword\").orEmpty()\n                val title = it.arguments?.getString(\"title\")\n                val pageType = it.arguments?.getString(\"pageType\")\n                val pageParam = it.arguments?.getString(\"pageParam\")\n                SearchResultScreen(\n                    onBackClick = navController::popBackStack,\n                    keyword = keyword,\n                    title = title,\n                    pageType = pageType,\n                    pageParam = pageParam,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    initialPage = initialPage,\n                    updateInitPage = { index ->\n                        initialPage = index\n                    },\n                    onReport = ::onReport\n                )\n            }\n\n            composable(\n                route = \"${Router.COPY.name}/{text}\",\n                arguments = listOf(\n                    navArgument(\"text\") {\n                        type = NavType.StringType\n                        nullable = true\n                    }\n                )\n            ) {\n                val text = it.arguments?.getString(\"text\").orEmpty()\n                CopyTextScreen(text = text)\n            }\n\n            composable(\n                route = \"${Router.TOPIC}/{tag}/{id}\",\n                arguments = listOf(\n                    navArgument(\"tag\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"id\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                )\n            ) {\n                val tag = it.arguments?.getString(\"tag\")\n                val id = it.arguments?.getString(\"id\")\n                TopicScreen(\n                    onBackClick = navController::popBackStack,\n                    tag = tag,\n                    id = id,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onSearch = { title, pageType, pageParam ->\n                        initialPage = 0\n                        navController.navigateToSearch(title, pageType, pageParam)\n                    },\n                    onReport = ::onReport\n                )\n            }\n\n            composable(\n                route = \"${Router.WEBVIEW.name}/{url}/{isLogin}\",\n                arguments = listOf(\n                    navArgument(\"url\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"isLogin\") {\n                        type = NavType.BoolType\n                    }\n                )\n            ) {\n                val url = it.arguments?.getString(\"url\").orEmpty()\n                val isLogin = it.arguments?.getBoolean(\"isLogin\") ?: false\n                WebViewScreen(\n                    onBackClick = {\n                        if (navController.currentDestination?.route?.contains(Router.WEBVIEW.name) == true) {\n                            navController.popBackStack()\n                        }\n                    },\n                    url = url,\n                    isLogin = isLogin,\n                )\n            }\n\n            composable(\n                route = \"${Router.APP.name}/{packageName}\",\n                arguments = listOf(\n                    navArgument(\"packageName\") {\n                    },\n                )\n            ) {\n                val packageName = it.arguments?.getString(\"packageName\").orEmpty()\n                AppScreen(\n                    onBackClick = navController::popBackStack,\n                    packageName = packageName,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onSearch = { title, pageType, pageParam ->\n                        initialPage = 0\n                        navController.navigateToSearch(title, pageType, pageParam)\n                    },\n                    onReport = ::onReport\n                )\n            }\n\n            composable(route = Router.LOGIN.name) {\n                LoginScreen(\n                    onBackClick = {\n                        if (navController.currentDestination?.route == Router.LOGIN.name) {\n                            navController.popBackStack()\n                        }\n                    },\n                    onWebLogin = {\n                        navController.navigateToWebView(url = URL_LOGIN, isLogin = true)\n                    }\n                )\n            }\n\n            composable(\n                route = \"${Router.CAROUSEL.name}/{url}/{title}\",\n                arguments = listOf(\n                    navArgument(\"url\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"title\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val url = it.arguments?.getString(\"url\").orEmpty()\n                val title = it.arguments?.getString(\"title\").orEmpty()\n                CarouselScreen(\n                    onBackClick = navController::popBackStack,\n                    url = url,\n                    title = title,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport\n                )\n            }\n\n            composable(\n                route = Router.UPDATE.name,\n            ) {\n                val bundle = it.arguments\n                val data = if (SDK_INT >= 33)\n                    bundle?.getParcelableArrayList(\"list\", UpdateCheckItem::class.java)\n                else\n                    bundle?.getParcelableArrayList(\"list\")\n                AppUpdateScreen(\n                    onBackClick = navController::popBackStack,\n                    data = data,\n                    onViewApp = navController::navigateToApp,\n                )\n            }\n\n            composable(\n                route = \"${Router.FFFLIST.name}/{uid}/{type}/{id}/{title}\",\n                arguments = listOf(\n                    navArgument(\"uid\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"type\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"id\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                    navArgument(\"title\") {\n                        type = NavType.StringType\n                        nullable = true\n                    },\n                )\n            ) {\n                val uid = it.arguments?.getString(\"uid\")\n                val type = it.arguments?.getString(\"type\").orEmpty()\n                val id = it.arguments?.getString(\"id\")\n                val title = it.arguments?.getString(\"title\")\n                FFFListScreen(\n                    onBackClick = navController::popBackStack,\n                    id = id,\n                    title = title,\n                    uid = uid,\n                    type = type,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport,\n                    onViewFFFList = navController::navigateToFFFList,\n                )\n            }\n\n            composable(\n                route = \"${Router.DYH.name}/{id}/{title}\",\n                arguments = listOf(\n                    navArgument(\"id\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"title\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val id = it.arguments?.getString(\"id\").orEmpty()\n                val title = it.arguments?.getString(\"title\").orEmpty()\n                DyhScreen(\n                    onBackClick = navController::popBackStack,\n                    id = id,\n                    title = title,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport\n                )\n            }\n\n            composable(\n                route = \"${Router.COOLPIC.name}/{title}\",\n                arguments = listOf(\n                    navArgument(\"title\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val title = it.arguments?.getString(\"title\").orEmpty()\n                CoolPicScreen(\n                    onBackClick = navController::popBackStack,\n                    title = title,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport\n                )\n            }\n\n            composable(\n                route = \"${Router.NOTICE.name}/{type}\",\n                arguments = listOf(\n                    navArgument(\"type\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val type = it.arguments?.getString(\"type\").orEmpty()\n                NoticeScreen(\n                    onBackClick = navController::popBackStack,\n                    type = type,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport,\n                    onViewChat = navController::navigateToChat,\n                )\n            }\n\n            composable(\n                route = \"${Router.BLACKLIST.name}/{type}\",\n                arguments = listOf(\n                    navArgument(\"type\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val type = it.arguments?.getString(\"type\").orEmpty()\n                BlackListScreen(\n                    onBackClick = navController::popBackStack,\n                    type = type,\n                    onViewUser = navController::navigateToUser,\n                    onViewTopic = { tag ->\n                        navController.navigateToTopic(tag, null)\n                    }\n                )\n            }\n\n            composable(\n                route = \"${Router.HISTORY.name}/{type}\",\n                arguments = listOf(\n                    navArgument(\"type\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val type = it.arguments?.getString(\"type\").orEmpty()\n                HistoryScreen(\n                    onBackClick = navController::popBackStack,\n                    type = type,\n                    onViewUser = navController::navigateToUser,\n                    onReport = ::onReport,\n                    onOpenLink = ::onOpenLink,\n                    onViewFeed = ::onViewFeed,\n                    onCopyText = navController::navigateToCopyText,\n                )\n            }\n\n            composable(\n                route = \"${Router.CHAT.name}/{ukey}/{uid}/{username}\",\n                arguments = listOf(\n                    navArgument(\"ukey\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"uid\") {\n                        type = NavType.StringType\n                    },\n                    navArgument(\"username\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val ukey = it.arguments?.getString(\"ukey\").orEmpty()\n                val uid = it.arguments?.getString(\"uid\").orEmpty()\n                val username = it.arguments?.getString(\"username\").orEmpty()\n                ChatScreen(\n                    onBackClick = navController::popBackStack,\n                    ukey = ukey,\n                    uid = uid,\n                    username = username,\n                    onViewUser = navController::navigateToUser,\n                    onReport = ::onReport,\n                )\n            }\n\n            composable(\n                route = \"${Router.COLLECTION.name}/{id}\",\n                arguments = listOf(\n                    navArgument(\"id\") {\n                        type = NavType.StringType\n                    },\n                )\n            ) {\n                val id = it.arguments?.getString(\"id\").orEmpty()\n                CollectionScreen(\n                    onBackClick = navController::popBackStack,\n                    id = id,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = ::onViewFeed,\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport,\n                )\n            }\n\n        }\n        if (selectIndex != 2 && !isCompat) {\n            if (compatId.isNullOrEmpty()) {\n                Box(\n                    modifier = Modifier\n                        .weight(1f)\n                        .fillMaxSize()\n                        .background(\n                            MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)\n                        ),\n                    contentAlignment = Alignment.Center\n                ) {\n                    Icon(\n                        imageVector = Icons.Default.AllInclusive,\n                        contentDescription = null,\n                        tint = MaterialTheme.colorScheme.outline,\n                        modifier = Modifier.size(55.dp)\n                    )\n                }\n            } else {\n                FeedScreen(\n                    modifier = Modifier.weight(1f),\n                    isCompat = false,\n                    onBackClick = { compatId = null },\n                    id = compatId.orEmpty(),\n                    isViewReply = compatReply ?: false,\n                    onViewUser = navController::navigateToUser,\n                    onViewFeed = { viewId, viewReply ->\n                        compatId = viewId\n                        compatReply = viewReply\n                    },\n                    onOpenLink = ::onOpenLink,\n                    onCopyText = navController::navigateToCopyText,\n                    onReport = ::onReport,\n                )\n            }\n        }\n    }\n\n\n}\n\nfun NavHostController.onOpenLink(\n    context: Context,\n    url: String,\n    title: String? = null,\n    needConvert: Boolean = false,\n    onViewFeed: ((String, Boolean) -> Unit)? = null,\n) {\n    if (url.isEmpty())\n        return\n    val path = with(url.decode) {\n        if (needConvert) {\n            if (this.startsWith(PREFIX_COOLMARKET))\n                this.replaceFirst(PREFIX_COOLMARKET, \"/\")\n            else {\n                val uri = Uri.parse(this)\n                if (uri.host?.contains(CHANNEL) == true)\n                    uri.path ?: url\n                else url\n            }\n        } else this\n    }\n    when {\n        path.startsWith(PREFIX_USER) -> {\n            navigateToUser(path.replaceFirst(PREFIX_USER, EMPTY_STRING))\n        }\n\n        path.startsWith(PREFIX_FEED) -> {\n            val id = path.replaceFirst(PREFIX_FEED, EMPTY_STRING).replace(\"?\", \"&\")\n            if (onViewFeed == null) {\n                navigateToFeed(id, id.contains(\"rid\"))\n            } else {\n                onViewFeed(id, id.contains(\"rid\"))\n            }\n        }\n\n        path.startsWith(PREFIX_TOPIC) -> {\n            val tag = path.replaceFirst(PREFIX_TOPIC, EMPTY_STRING)\n                .replace(\"\\\\?type=[A-Za-z0-9]+\".toRegex(), EMPTY_STRING)\n            if (path.contains(\"type=8\"))\n                navigateToCoolPic(tag)\n            else\n                navigateToTopic(id = null, tag = tag)\n        }\n\n        path.startsWith(PREFIX_PRODUCT) -> {\n            navigateToTopic(\n                id = path.replaceFirst(PREFIX_PRODUCT, EMPTY_STRING),\n                tag = null,\n            )\n        }\n\n        path.startsWith(PREFIX_APP) -> {\n            navigateToApp(packageName = path.replaceFirst(PREFIX_APP, EMPTY_STRING))\n        }\n\n        path.startsWith(PREFIX_GAME) -> {\n            navigateToApp(packageName = path.replaceFirst(PREFIX_GAME, EMPTY_STRING))\n        }\n\n        path.startsWith(PREFIX_CAROUSEL) -> {\n            navigateToCarousel(\n                path.replaceFirst(PREFIX_CAROUSEL, EMPTY_STRING),\n                title.orEmpty()\n            )\n        }\n\n        path.startsWith(PREFIX_CAROUSEL1) -> {\n            navigateToCarousel(path.replaceFirst(\"#\", EMPTY_STRING), title.orEmpty())\n        }\n\n        path.startsWith(PREFIX_USER_LIST) -> {\n            val type = when {\n                path.contains(\"myFollowList\") -> FFFListType.USER_FOLLOW.name\n                else -> EMPTY_STRING\n            }\n            navigateToFFFList(CookieUtil.uid, type, null, null)\n        }\n\n        path.startsWith(PREFIX_DYH) -> {\n            navigateToDyh(path.replaceFirst(PREFIX_DYH, EMPTY_STRING), title.orEmpty())\n        }\n\n        path.startsWith(PREFIX_COLLECTION) -> {\n            navigateToCollection(path.replaceFirst(PREFIX_COLLECTION, EMPTY_STRING))\n        }\n\n        else -> {\n            if (!needConvert)\n                onOpenLink(context, url, title, true)\n            else {\n                if (url.startsWith(PREFIX_HTTP)) {\n                    if (openInBrowser)\n                        context.openInBrowser(url)\n                    else\n                        navigateToWebView(url)\n                } else {\n                    context.makeToast(\"unsupported url: $url\")\n                    context.copyText(url, false)\n                }\n            }\n        }\n    }\n}\n\nfun NavHostController.navigateToCopyText(text: String?) {\n    navigate(\"${Router.COPY.name}/${text.encode}\")\n}\n\nfun NavHostController.navigateToFeed(id: String, isViewReply: Boolean = false) {\n    navigate(\"${Router.FEED.name}/$id/$isViewReply\")\n}\n\nfun NavHostController.navigateToUser(uid: String) {\n    navigate(\"${Router.USER.name}/$uid\")\n}\n\nfun NavHostController.navigateToTopic(tag: String?, id: String?) {\n    navigate(\"${Router.TOPIC.name}/$tag/$id\")\n}\n\nfun NavHostController.navigateToWebView(url: String, isLogin: Boolean = false) {\n    navigate(\"${Router.WEBVIEW.name}/${url.encode}/$isLogin\")\n}\n\nfun NavHostController.navigateToSearch(title: String?, pageType: String?, pageParam: String?) {\n    navigate(\"${Router.SEARCH.name}/$title/$pageType/$pageParam\")\n}\n\nfun NavHostController.navigateToSearchResult(\n    keyword: String,\n    title: String?,\n    pageType: String?,\n    pageParam: String?\n) {\n    navigate(\"${Router.SEARCHRESULT.name}/${keyword.encode}/$title/$pageType/$pageParam\")\n}\n\nfun NavHostController.navigateToApp(packageName: String) {\n    navigate(\"${Router.APP.name}/$packageName\")\n}\n\nfun NavHostController.navigateToCarousel(url: String, title: String) {\n    navigate(\"${Router.CAROUSEL.name}/${url.encode}/$title\")\n}\n\nfun NavHostController.navigate(\n    route: String,\n    args: Bundle,\n    navOptions: NavOptions? = null,\n    navigatorExtras: Navigator.Extras? = null\n) {\n    val nodeId = graph.findNode(route = route)?.id\n    if (nodeId != null) {\n        navigate(nodeId, args, navOptions, navigatorExtras)\n    }\n}\n\nfun NavHostController.navigateToFFFList(uid: String?, type: String, id: String?, title: String?) {\n    navigate(\"${Router.FFFLIST.name}/$uid/$type/$id/$title\")\n}\n\nfun NavHostController.navigateToDyh(id: String, title: String) {\n    navigate(\"${Router.DYH.name}/$id/$title\")\n}\n\nfun NavHostController.navigateToCoolPic(title: String) {\n    navigate(\"${Router.COOLPIC.name}/$title\")\n}\n\nfun NavHostController.navigateToNotice(type: String) {\n    navigate(\"${Router.NOTICE.name}/$type\")\n}\n\nfun NavHostController.navigateToBlackList(type: String) {\n    navigate(\"${Router.BLACKLIST.name}/$type\")\n}\n\nfun NavHostController.navigateToHistory(type: String) {\n    navigate(\"${Router.HISTORY.name}/$type\")\n}\n\nfun NavHostController.navigateToChat(ukey: String, uid: String, username: String) {\n    navigate(\"${Router.CHAT.name}/$ukey/$uid/$username\")\n}\n\nfun NavHostController.navigateToCollection(id: String) {\n    navigate(\"${Router.COLLECTION.name}/$id\")\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/main/MainScreen.kt",
    "content": "package com.example.c001apk.compose.ui.main\n\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.animation.AnimatedContent\nimport androidx.compose.animation.core.tween\nimport androidx.compose.animation.scaleIn\nimport androidx.compose.animation.scaleOut\nimport androidx.compose.animation.togetherWith\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Badge\nimport androidx.compose.material3.BadgedBox\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteScaffold\nimport androidx.compose.material3.adaptive.navigationsuite.NavigationSuiteType\nimport androidx.compose.material3.windowsizeclass.WindowWidthSizeClass\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveableStateHolder\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport com.example.c001apk.compose.logic.model.UpdateCheckItem\nimport com.example.c001apk.compose.ui.component.SlideTransition\nimport com.example.c001apk.compose.ui.home.HomeScreen\nimport com.example.c001apk.compose.ui.message.MessageScreen\nimport com.example.c001apk.compose.ui.settings.SettingsScreen\nimport com.example.c001apk.compose.util.ReportType\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/30\n */\n@Composable\nfun MainScreen(\n    selectIndex: Int,\n    setSelectIndex: (Int) -> Unit,\n    badge: Int,\n    resetBadge: () -> Unit,\n    onParamsClick: () -> Unit,\n    onAboutClick: () -> Unit,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onSearch: () -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onViewApp: (String) -> Unit,\n    onLogin: () -> Unit,\n    onCheckUpdate: (List<UpdateCheckItem>) -> Unit,\n    onViewFFFList: (String?, String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onViewNotice: (String) -> Unit,\n    onViewBlackList: (String) -> Unit,\n    onViewHistory: (String) -> Unit,\n    widthSizeClass: WindowWidthSizeClass,\n) {\n\n    val screens = listOf(\n        Router.HOME,\n        Router.MESSAGE,\n        Router.SETTINGS\n    )\n\n    val savableStateHolder = rememberSaveableStateHolder()\n    var refreshState by remember { mutableStateOf(false) }\n\n    val customNavSuiteType = when (widthSizeClass) {\n        WindowWidthSizeClass.Compact -> NavigationSuiteType.NavigationBar\n        else -> NavigationSuiteType.NavigationRail\n    }\n\n    NavigationSuiteScaffold(\n        navigationSuiteItems = {\n            screens.forEachIndexed { index, screen ->\n                item(\n                    icon = {\n                        BadgedBox(\n                            badge = {\n                                androidx.compose.animation.AnimatedVisibility(\n                                    visible = if (index == 1) badge > 0\n                                    else false,\n                                    enter = scaleIn(animationSpec = tween(250)),\n                                    exit = scaleOut(animationSpec = tween(250))\n                                ) {\n                                    Badge(\n                                        modifier = Modifier\n                                            .padding(start = 15.dp, bottom = 10.dp)\n                                    ) {\n                                        Text(text = badge.toString())\n                                    }\n                                }\n                            }\n                        ) {\n                            Icon(\n                                imageVector =\n                                if (selectIndex == screens.indexOf(screen)) {\n                                    screen.selectedIcon!!\n                                } else {\n                                    screen.unselectedIcon!!\n                                },\n                                contentDescription = null\n                            )\n                        }\n                    },\n                    label = { Text(text = stringResource(id = screen.stringId!!)) },\n                    selected = selectIndex == screens.indexOf(screen),\n                    onClick = {\n                        with(screens.indexOf(screen)) {\n                            if (selectIndex == 0 && this == 0) {\n                                refreshState = true\n                            } else if (this == 1 && badge != 0) {\n                                resetBadge()\n                            }\n                            setSelectIndex(this)\n                        }\n                    },\n                    alwaysShowLabel = false\n                )\n            }\n        },\n        layoutType = customNavSuiteType\n    ) {\n        AnimatedContent(\n            modifier = Modifier.fillMaxSize(),\n            label = \"home-content\",\n            targetState = selectIndex,\n            transitionSpec = {\n                SlideTransition.slideLeft.enterTransition()\n                    .togetherWith(SlideTransition.slideLeft.exitTransition())\n            },\n        ) { page ->\n            savableStateHolder.SaveableStateProvider(\n                key = page,\n                content = {\n                    when (page) {\n                        0 -> HomeScreen(\n                            refreshState = refreshState,\n                            onRefresh = {\n                                refreshState = true\n                            },\n                            resetRefreshState = {\n                                refreshState = false\n                            },\n                            onViewUser = onViewUser,\n                            onViewFeed = onViewFeed,\n                            onSearch = onSearch,\n                            onOpenLink = onOpenLink,\n                            onCopyText = onCopyText,\n                            onViewApp = onViewApp,\n                            onCheckUpdate = onCheckUpdate,\n                            onReport = onReport,\n                        )\n\n                        1 -> MessageScreen(\n                            onLogin = onLogin,\n                            onViewUser = onViewUser,\n                            onViewFeed = onViewFeed,\n                            onOpenLink = onOpenLink,\n                            onCopyText = onCopyText,\n                            onViewFFFList = onViewFFFList,\n                            onReport = onReport,\n                            onViewNotice = onViewNotice,\n                            onViewHistory = onViewHistory,\n                        )\n\n                        2 -> SettingsScreen(\n                            onParamsClick = onParamsClick,\n                            onAboutClick = onAboutClick,\n                            onViewBlackList = onViewBlackList,\n                        )\n                    }\n                }\n            )\n        }\n    }\n\n    BackHandler(enabled = selectIndex != 0) {\n        setSelectIndex(0)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/main/MainViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.main\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.ui.base.PrefsViewModel\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.CookieUtil.SESSID\nimport com.example.c001apk.compose.util.encode\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\n@HiltViewModel\nclass MainViewModel @Inject constructor(\n    private val networkRepo: NetworkRepo,\n    private val userPreferencesRepository: UserPreferencesRepository,\n) : PrefsViewModel(userPreferencesRepository) {\n\n    init {\n        getCheckLoginInfo()\n    }\n\n    var badge by mutableIntStateOf(0)\n        private set\n\n    private fun getCheckLoginInfo() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.checkLoginInfo()\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        response.body()?.data?.let { login ->\n                            badge = login.notifyCount.badge\n                            CookieUtil.atme = login.notifyCount.atme\n                            CookieUtil.atcommentme = login.notifyCount.atcommentme\n                            CookieUtil.feedlike = login.notifyCount.feedlike\n                            CookieUtil.contacts_follow = login.notifyCount.contactsFollow\n                            CookieUtil.message = login.notifyCount.message\n\n                            userPreferencesRepository.apply {\n                                setUid(login.uid)\n                                setUserAvatar(login.userAvatar)\n                                setUsername(login.username.encode)\n                                setToken(login.token)\n                                setIsLogin(true)\n                            }\n                        }\n\n                        if (response.body()?.message == \"登录信息有误\") {\n                            userPreferencesRepository.apply {\n                                setUid(EMPTY_STRING)\n                                setUserAvatar(EMPTY_STRING)\n                                setUsername(EMPTY_STRING)\n                                setToken(EMPTY_STRING)\n                                setIsLogin(false)\n                            }\n                        }\n\n                        try {\n                            val session = response.headers().values(\"Set-Cookie\")[0]\n                            SESSID = session.substring(0, session.indexOf(\";\"))\n                        } catch (e: Exception) {\n                            e.printStackTrace()\n                        }\n                    }\n                }\n        }\n    }\n\n    fun resetBadge() {\n        badge = 0\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/main/Router.kt",
    "content": "package com.example.c001apk.compose.ui.main\n\nimport androidx.annotation.StringRes\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.Message\nimport androidx.compose.material.icons.automirrored.outlined.Message\nimport androidx.compose.material.icons.filled.Home\nimport androidx.compose.material.icons.filled.Settings\nimport androidx.compose.material.icons.outlined.Home\nimport androidx.compose.material.icons.outlined.Settings\nimport androidx.compose.ui.graphics.vector.ImageVector\nimport com.example.c001apk.compose.R\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/30\n */\nsealed class Router(\n    val name: String,\n    @StringRes val stringId: Int? = null,\n    val unselectedIcon: ImageVector? = null,\n    val selectedIcon: ImageVector? = null,\n) {\n\n    data object MAIN : Router(\n        name = \"MAIN\"\n    )\n\n    data object HOME : Router(\n        name = \"HOME\",\n        stringId = R.string.home,\n        unselectedIcon = Icons.Outlined.Home,\n        selectedIcon = Icons.Default.Home\n    )\n\n    data object MESSAGE : Router(\n        name = \"MESSAGE\",\n        stringId = R.string.message,\n        unselectedIcon = Icons.AutoMirrored.Outlined.Message,\n        selectedIcon = Icons.AutoMirrored.Filled.Message\n    )\n\n    data object SETTINGS : Router(\n        name = \"SETTINGS\",\n        stringId = R.string.settings,\n        unselectedIcon = Icons.Outlined.Settings,\n        selectedIcon = Icons.Default.Settings\n    )\n\n    data object PARAMS : Router(name = \"PARAMS\")\n\n    data object ABOUT : Router(name = \"ABOUT\")\n\n    data object LICENSE : Router(name = \"LICENSE\")\n\n    data object BLACKLIST : Router(name = \"BLACKLIST\")\n\n    data object SEARCH : Router(name = \"SEARCH\")\n\n    data object SEARCHRESULT : Router(name = \"SEARCHRESULT\")\n\n    data object TAB : Router(name = \"TAB\")\n\n    data object FEED : Router(name = \"FEED\")\n\n    data object USER : Router(name = \"USER\")\n\n    data object TOPIC : Router(name = \"TOPIC\")\n\n    data object COPY : Router(name = \"COPY\")\n\n    data object WEBVIEW : Router(name = \"WEBVIEW\")\n\n    data object APP : Router(name = \"APP\")\n\n    data object LOGIN : Router(name = \"LOGIN\")\n\n    data object CAROUSEL : Router(name = \"CAROUSEL\")\n\n    data object UPDATE : Router(name = \"UPDATE\")\n\n    data object FFFLIST : Router(name = \"FFFLIST\")\n\n    data object DYH : Router(name = \"DYH\")\n\n    data object COOLPIC : Router(name = \"COOLPIC\")\n\n    data object NOTICE : Router(name = \"NOTICE\")\n\n    data object HISTORY : Router(name = \"HISTORY\")\n\n    data object CHAT : Router(name = \"CHAT\")\n\n    data object COLLECTION : Router(name = \"COLLECTION\")\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/message/MessageScreen.kt",
    "content": "package com.example.c001apk.compose.ui.message\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.component.FooterCard\nimport com.example.c001apk.compose.ui.component.ItemCard\nimport com.example.c001apk.compose.ui.component.cards.MessageFFFCard\nimport com.example.c001apk.compose.ui.component.cards.MessageHeaderCard\nimport com.example.c001apk.compose.ui.component.cards.MessageListCard\nimport com.example.c001apk.compose.ui.component.cards.MessageWidgetCard\nimport com.example.c001apk.compose.ui.component.cards.backgroundList\nimport com.example.c001apk.compose.ui.component.cards.iconList\nimport com.example.c001apk.compose.ui.component.cards.titleList\nimport com.example.c001apk.compose.ui.notification.NoticeType\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/2\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun MessageScreen(\n    onLogin: () -> Unit,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onViewFFFList: (String?, String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onViewNotice: (String) -> Unit,\n    onViewHistory: (String) -> Unit,\n) {\n\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    val prefs = LocalUserPreferences.current\n    val viewModel =\n        hiltViewModel<MessageViewModel, MessageViewModel.ViewModelFactory> { factory ->\n            factory.create(url = \"/v6/notification/list\")\n        }\n    var showLogoutDialog by remember { mutableStateOf(false) }\n    var showDeleteDialog by remember { mutableStateOf(false) }\n    val state = rememberPullToRefreshState()\n\n    LaunchedEffect(prefs.isLogin) {\n        if (prefs.isLogin && viewModel.fffList.isEmpty()) {\n            viewModel.refresh()\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n    ) { paddingValues ->\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(top = paddingValues.calculateTopPadding())\n        ) {\n            MessageHeaderCard(\n                modifier = Modifier.padding(\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                ),\n                isLogin = prefs.isLogin,\n                userAvatar = prefs.userAvatar,\n                userName = prefs.username,\n                level = prefs.level,\n                experience = prefs.experience,\n                nextLevelExperience = prefs.nextLevelExperience,\n                onLogin = onLogin,\n                onLogout = {\n                    showLogoutDialog = true\n                },\n                onViewUser = onViewUser\n            )\n\n            HorizontalDivider()\n\n            PullToRefreshBox(\n                modifier = Modifier.padding(\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                ),\n                state = state,\n                isRefreshing = viewModel.isRefreshing,\n                onRefresh = viewModel::refresh,\n                indicator = {\n                    PullToRefreshDefaults.Indicator(\n                        modifier = Modifier.align(Alignment.TopCenter),\n                        isRefreshing = viewModel.isRefreshing,\n                        state = state,\n                        color = MaterialTheme.colorScheme.primary,\n                    )\n                }\n            ) {\n                LazyColumn(\n                    Modifier.fillMaxSize(),\n                    contentPadding = PaddingValues(vertical = 10.dp),\n                    verticalArrangement = Arrangement.spacedBy(10.dp)\n                ) {\n                    item(key = \"fff\") {\n                        MessageFFFCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            fffList = viewModel.fffList,\n                            onViewFFFList = onViewFFFList,\n                        )\n                    }\n\n                    item(key = \"widget\") {\n                        MessageWidgetCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            onViewFFFList = onViewFFFList,\n                            onViewHistory = onViewHistory,\n                        )\n                    }\n\n                    items(5) { index ->\n                        MessageListCard(\n                            modifier = Modifier.padding(horizontal = 10.dp),\n                            background = backgroundList[index],\n                            imageVector = iconList[index],\n                            title = titleList[index],\n                            count = viewModel.badgeList.getOrNull(index),\n                            onViewNotice = {\n                                viewModel.clearBadge(index)\n                                onViewNotice(NoticeType.entries[index].name)\n                            }\n                        )\n                    }\n\n                    ItemCard(\n                        loadingState = viewModel.loadingState,\n                        loadMore = viewModel::loadMore,\n                        isEnd = viewModel.isEnd,\n                        onViewUser = onViewUser,\n                        onViewFeed = onViewFeed,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText,\n                        onReport = onReport,\n                        onBlockUser = { uid, _ ->\n                            viewModel.onBlockUser(uid)\n                        },\n                        onDeleteNotice = { id ->\n                            viewModel.deleteId = id\n                            showDeleteDialog = true\n                        }\n                    )\n\n                    FooterCard(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        footerState = viewModel.footerState,\n                        loadMore = viewModel::loadMore,\n                        isFeed = false\n                    )\n\n                }\n            }\n\n        }\n\n    }\n\n    when {\n        showLogoutDialog -> {\n            AlertDialog(\n                onDismissRequest = { showLogoutDialog = false },\n                confirmButton = {\n                    TextButton(\n                        onClick = {\n                            viewModel.onLogout()\n                            showLogoutDialog = false\n                        })\n                    {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                },\n                dismissButton = {\n                    TextButton(\n                        onClick = { showLogoutDialog = false })\n                    {\n                        Text(text = stringResource(id = android.R.string.cancel))\n                    }\n                },\n                title = {\n                    Text(text = \"确定退出登录？\", modifier = Modifier.fillMaxWidth())\n                }\n            )\n        }\n\n        showDeleteDialog -> {\n            AlertDialog(\n                onDismissRequest = { showDeleteDialog = false },\n                confirmButton = {\n                    TextButton(\n                        onClick = {\n                            viewModel.onPostDelete()\n                            showDeleteDialog = false\n                        })\n                    {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                },\n                dismissButton = {\n                    TextButton(\n                        onClick = { showDeleteDialog = false })\n                    {\n                        Text(text = stringResource(id = android.R.string.cancel))\n                    }\n                },\n                title = {\n                    Text(text = \"确定删除此条消息？\", modifier = Modifier.fillMaxWidth())\n                }\n            )\n        }\n    }\n\n    viewModel.toastText?.let {\n        context.makeToast(it)\n        viewModel.resetToastText()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/message/MessageViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.message\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.logic.state.FooterState\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport com.example.c001apk.compose.util.CookieUtil.atcommentme\nimport com.example.c001apk.compose.util.CookieUtil.atme\nimport com.example.c001apk.compose.util.CookieUtil.contacts_follow\nimport com.example.c001apk.compose.util.CookieUtil.feedlike\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.CookieUtil.message\nimport com.example.c001apk.compose.util.CookieUtil.uid\nimport com.example.c001apk.compose.util.encode\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@HiltViewModel(assistedFactory = MessageViewModel.ViewModelFactory::class)\nclass MessageViewModel @AssistedInject constructor(\n    @Assisted val url: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n    private val userPreferencesRepository: UserPreferencesRepository,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    var fffList by mutableStateOf<List<String>>(emptyList())\n        private set\n    var badgeList by mutableStateOf(listOf(atme, atcommentme, feedlike, contacts_follow, message))\n        private set\n\n    init {\n        loadingState = LoadingState.Success(emptyList())\n    }\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(url: String): MessageViewModel\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getMessage(url, page, lastItem)\n\n    private fun fetchProfile() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getProfile(uid)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data?.data != null) {\n                        fffList = listOf(\n                            data.data.feed?.id ?: \"0\",\n                            data.data.follow ?: \"0\",\n                            data.data.fans ?: \"0\"\n                        )\n                        userPreferencesRepository.apply {\n                            setUserAvatar(data.data.userAvatar.orEmpty())\n                            setUsername(data.data.username.encode)\n                            setLevel(data.data.level ?: \"0\")\n                            setExperience(data.data.experience ?: \"0\")\n                            setNextLevelExperience(data.data.nextLevelExperience ?: \"0\")\n                        }\n                    } else {\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    fun onLogout() {\n        viewModelScope.launch {\n            userPreferencesRepository.apply {\n                setUid(Constants.EMPTY_STRING)\n                setUserAvatar(Constants.EMPTY_STRING)\n                setUsername(Constants.EMPTY_STRING)\n                setToken(Constants.EMPTY_STRING)\n                setIsLogin(false)\n            }\n            fffList = emptyList()\n            badgeList = listOf(null)\n            loadingState = LoadingState.Success(emptyList())\n            footerState = FooterState.Success\n        }\n    }\n\n    private fun onCheckCount() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.checkCount()\n                .collect { result ->\n                    result.getOrNull()?.data?.let {\n                        badgeList = listOf(\n                            it.atme, it.atcommentme, it.feedlike, it.contactsFollow, it.message\n                        )\n                    }\n                }\n        }\n    }\n\n    override fun refresh() {\n        if (isLogin && !isRefreshing && !isLoadMore) {\n            page = 1\n            isEnd = false\n            isLoadMore = false\n            isRefreshing = true\n            firstItem = null\n            lastItem = null\n            fetchProfile()\n            onCheckCount()\n            fetchData()\n        } else {\n            viewModelScope.launch {\n                isRefreshing = true\n                delay(50)\n                isRefreshing = false\n            }\n        }\n    }\n\n    fun clearBadge(index: Int) {\n        badgeList = badgeList.mapIndexed { i, count ->\n            if (index == i) null\n            else count\n        }\n    }\n\n    lateinit var deleteId: String\n    fun onPostDelete() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.postDelete(\"/v6/notification/delete\", deleteId)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                        } else if (data.data?.count?.contains(\"成功\") == true) {\n                            var response = (loadingState as LoadingState.Success).response\n                            response = response.filterNot { it.id == deleteId }\n                            loadingState = LoadingState.Success(response)\n                            toastText = data.data.count\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/notification/NoticeScreen.kt",
    "content": "package com.example.c001apk.compose.ui.notification\n\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.window.Dialog\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.ui.ffflist.FFFContentViewModel\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/13\n */\n\nenum class NoticeType {\n    AT, COMMENT, LIKE, FOLLOW, MESSAGE\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun NoticeScreen(\n    onBackClick: () -> Unit,\n    type: String,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onViewChat: (String, String, String) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<NoticeViewModel, NoticeViewModel.ViewModelFactory>(key = type) { factory ->\n            factory.create(\n                url = when (type) {\n                    NoticeType.AT.name -> \"/v6/notification/atMeList\"\n                    NoticeType.COMMENT.name -> \"/v6/notification/atCommentMeList\"\n                    NoticeType.LIKE.name -> \"/v6/notification/feedLikeList\"\n                    NoticeType.FOLLOW.name -> \"/v6/notification/contactsFollowList\"\n                    NoticeType.MESSAGE.name -> \"/v6/message/list\"\n                    else -> throw IllegalArgumentException(\"invalid type: $type\")\n                }\n            )\n        }\n\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n    var showMessageDialog by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        text = when (type) {\n                            NoticeType.AT.name -> \"@我的动态\"\n                            NoticeType.COMMENT.name -> \"@我的评论\"\n                            NoticeType.LIKE.name -> \"我收到的赞\"\n                            NoticeType.FOLLOW.name -> \"好友关注\"\n                            NoticeType.MESSAGE.name -> \"私信\"\n                            else -> throw IllegalArgumentException(\"invalid type: $type\")\n                        },\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                }\n            )\n        }\n    ) { paddingValues ->\n\n        CommonScreen(\n            viewModel = viewModel,\n            refreshState = null,\n            resetRefreshState = {},\n            paddingValues = PaddingValues(\n                top = paddingValues.calculateTopPadding(),\n                start = paddingValues.calculateLeftPadding(layoutDirection),\n                bottom = paddingValues.calculateBottomPadding(),\n            ),\n            needTopPadding = true,\n            onViewUser = onViewUser,\n            onViewFeed = onViewFeed,\n            onOpenLink = onOpenLink,\n            onCopyText = onCopyText,\n            onReport = onReport,\n            onHandleMessage = { ukey, isTop ->\n                viewModel.ukey = ukey\n                viewModel.isTop = isTop\n                showMessageDialog = true\n            },\n            onViewChat = { ukey, uid, username ->\n                viewModel.resetUnRead(ukey)\n                onViewChat(ukey, uid, username)\n            }\n        )\n    }\n\n    when {\n        showMessageDialog -> {\n            Dialog(onDismissRequest = { showMessageDialog = false }) {\n                ElevatedCard(\n                    modifier = Modifier.fillMaxWidth(),\n                    shape = RoundedCornerShape(28.dp),\n                    colors = CardDefaults.elevatedCardColors()\n                        .copy(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh)\n                ) {\n                    Column(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .padding(vertical = 8.dp)\n                    ) {\n                        Text(\n                            text = if (viewModel.isTop == 1) \"移除置顶\" else \"置顶\",\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clickable {\n                                    showMessageDialog = false\n                                    viewModel.onHandleMessage(FFFContentViewModel.ActionType.TOP)\n                                }\n                                .padding(horizontal = 24.dp, vertical = 14.dp),\n                            style = MaterialTheme.typography.titleSmall\n                        )\n                        Text(\n                            text = \"删除此对话\",\n                            modifier = Modifier\n                                .fillMaxWidth()\n                                .clickable {\n                                    showMessageDialog = false\n                                    viewModel.onHandleMessage(FFFContentViewModel.ActionType.DELETE)\n                                }\n                                .padding(horizontal = 24.dp, vertical = 14.dp),\n                            style = MaterialTheme.typography.titleSmall\n                        )\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/notification/NoticeViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.notification\n\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport com.example.c001apk.compose.ui.ffflist.FFFContentViewModel.ActionType\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/13\n */\n@HiltViewModel(assistedFactory = NoticeViewModel.ViewModelFactory::class)\nclass NoticeViewModel @AssistedInject constructor(\n    @Assisted val url: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(url: String): NoticeViewModel\n    }\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() = networkRepo.getMessage(url, page, lastItem)\n\n    var isTop = 0\n    lateinit var ukey: String\n\n    fun onHandleMessage(actionType: ActionType) {\n        val url = when (actionType) {\n            ActionType.TOP -> \"/v6/message/${if (isTop == 1) \"removeTop\" else \"addTop\"}\"\n            ActionType.DELETE -> \"/v6/message/deleteChat\"\n            ActionType.DELETE_ALL -> EMPTY_STRING\n        }\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.deleteMessage(url, ukey, null)\n                .collect { result ->\n                    val data = result.getOrNull()\n                    if (data != null) {\n                        if (!data.message.isNullOrEmpty()) {\n                            toastText = data.message\n                        } else if (data.data?.count?.contains(\"成功\") == true) {\n                            var response = (loadingState as LoadingState.Success).response\n                            response = when (actionType) {\n                                ActionType.TOP -> {\n                                    if (isTop == 0) {\n                                        val actionItem = response.find { it.ukey == ukey }\n                                        response.toMutableList().also {\n                                            it.remove(actionItem)\n                                            actionItem?.let { item ->\n                                                item.isTop = 1\n                                                it.add(0, item)\n                                            }\n                                        }\n                                    } else {\n                                        response.map {\n                                            if (it.ukey == ukey) it.copy(isTop = 0)\n                                            else it\n                                        }\n                                    }\n                                }\n\n                                ActionType.DELETE -> response.filterNot { it.ukey == ukey }\n                                ActionType.DELETE_ALL -> response\n                            }\n                            loadingState = LoadingState.Success(response)\n                            toastText = data.data.count\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    fun resetUnRead(ukey: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            var response = (loadingState as LoadingState.Success).response\n            response = response.map { item ->\n                if (item.ukey == ukey)\n                    item.copy(unreadNum = 0)\n                else\n                    item\n            }\n            loadingState = LoadingState.Success(response)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/others/CopyTextScreen.kt",
    "content": "package com.example.c001apk.compose.ui.others\n\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.text.selection.SelectionContainer\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.util.Utils.richToString\nimport com.example.c001apk.compose.util.decode\nimport com.example.c001apk.compose.util.getAllLinkAndText\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/7\n */\n@Composable\nfun CopyTextScreen(text: String) {\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n    ) { paddingValues ->\n        SelectionContainer {\n            Box(modifier = Modifier.fillMaxSize()) {\n                Text(\n                    text = text\n                        .decode\n                        .getAllLinkAndText\n                        .replace(\"\\n\", \"<br/>\")\n                        .richToString(),\n                    style = MaterialTheme.typography.titleLarge.copy(\n                        fontSize = 20.sp,\n                        lineHeight = 35.sp\n                    ),\n                    modifier = Modifier\n                        .padding(paddingValues)\n                        .align(Alignment.Center)\n                        .verticalScroll(rememberScrollState())\n                        .padding(horizontal = 20.dp, vertical = 25.dp)\n                )\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/search/SearchContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.search\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalView\nimport androidx.core.view.isVisible\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@Composable\nfun SearchContentScreen(\n    searchType: SearchType,\n    keyword: String,\n    pageType: String?,\n    pageParam: String?,\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    feedType: SearchFeedType,\n    orderType: SearchOrderType,\n    paddingValues: PaddingValues,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    updateInitPage: () -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val view = LocalView.current\n    if (view.isVisible) {\n        updateInitPage()\n    }\n\n    val viewModel =\n        hiltViewModel<SearchContentViewModel, SearchContentViewModel.ViewModelFactory>(key = keyword + searchType.name) { factory ->\n            factory.create(\n                type = when (searchType) {\n                    SearchType.FEED -> \"feed\"\n                    SearchType.APP -> \"apk\"\n                    SearchType.GAME -> \"game\"\n                    SearchType.PRODUCT -> \"product\"\n                    SearchType.USER -> \"user\"\n                    SearchType.TOPIC -> \"feedTopic\"\n                },\n                keyword = keyword, pageType = pageType, pageParam = pageParam\n            )\n        }\n\n    if (searchType == SearchType.FEED) {\n        LaunchedEffect(feedType) {\n            if (feedType != viewModel.searchFeedType) {\n                viewModel.searchFeedType = feedType\n                viewModel.feedType = when (feedType) {\n                    SearchFeedType.ALL -> \"all\"\n                    SearchFeedType.FEED -> \"feed\"\n                    SearchFeedType.ARTICLE -> \"feedArticle\"\n                    SearchFeedType.COOLPIC -> \"picture\"\n                    SearchFeedType.COMMENT -> \"comment\"\n                    SearchFeedType.RATING -> \"rating\"\n                    SearchFeedType.ANSWER -> \"answer\"\n                    SearchFeedType.QUESTION -> \"question\"\n                    SearchFeedType.VOTE -> \"vote\"\n                }\n                viewModel.refresh()\n            }\n        }\n\n        LaunchedEffect(orderType) {\n            if (orderType != viewModel.sortType) {\n                viewModel.sortType = orderType\n                viewModel.sort = when (orderType) {\n                    SearchOrderType.DATELINE -> \"default\"\n                    SearchOrderType.HOT -> \"hot\"\n                    SearchOrderType.REPLY -> \"reply\"\n                }\n                viewModel.refresh()\n            }\n        }\n    }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = paddingValues,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/search/SearchContentViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.search\n\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@HiltViewModel(assistedFactory = SearchContentViewModel.ViewModelFactory::class)\nclass SearchContentViewModel @AssistedInject constructor(\n    @Assisted(\"type\") val type: String,\n    @Assisted(\"keyword\") val keyword: String,\n    @Assisted(\"pageType\") val pageType: String?,\n    @Assisted(\"pageParam\") var pageParam: String?,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"type\") type: String,\n            @Assisted(\"keyword\") keyword: String,\n            @Assisted(\"pageType\") pageType: String?,\n            @Assisted(\"pageParam\") pageParam: String?,\n        ): SearchContentViewModel\n    }\n\n    var feedType: String = \"all\"\n    var sort: String = \"default\" //hot // reply\n\n    var searchFeedType = SearchFeedType.ALL\n    var sortType = SearchOrderType.DATELINE\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getSearch(\n            type, feedType, sort, keyword,\n            pageType, pageParam, page, lastItem\n        )\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/search/SearchResultScreen.kt",
    "content": "package com.example.c001apk.compose.ui.search\n\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowRight\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SecondaryScrollableTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.decode\nimport com.example.c001apk.compose.util.noRippleClickable\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n\nenum class SearchType {\n    FEED, APP, GAME, PRODUCT, USER, TOPIC\n}\n\nenum class SearchFeedType {\n    ALL, FEED, ARTICLE, COOLPIC, COMMENT, RATING, ANSWER, QUESTION, VOTE\n}\n\nenum class SearchOrderType {\n    DATELINE, HOT, REPLY\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SearchResultScreen(\n    onBackClick: () -> Unit,\n    keyword: String,\n    title: String?,\n    pageType: String?,\n    pageParam: String?,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    initialPage: Int = 0,\n    updateInitPage: (Int) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val layoutDirection = LocalLayoutDirection.current\n    val tabList = SearchType.entries\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    val pagerState = rememberPagerState(\n        initialPage = initialPage,\n        pageCount = { if (pageType.isNullOrEmpty()) tabList.size else 1 }\n    )\n    var refreshState by remember { mutableStateOf(false) }\n    val scope = rememberCoroutineScope()\n    var typeMenuExpanded by remember { mutableStateOf(false) }\n    var orderMenuExpanded by remember { mutableStateOf(false) }\n    var feedType by rememberSaveable { mutableStateOf(SearchFeedType.ALL) }\n    var orderType by rememberSaveable { mutableStateOf(SearchOrderType.DATELINE) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Column(\n                        modifier = Modifier\n                            .fillMaxSize()\n                            .noRippleClickable { onBackClick() },\n                        verticalArrangement = Arrangement.Center,\n                    ) {\n                        Text(\n                            text = keyword.decode,\n                            style = MaterialTheme.typography.titleMedium.copy(fontSize = 18.sp),\n                            maxLines = 1,\n                            overflow = TextOverflow.Ellipsis\n                        )\n                        if (!title.isNullOrEmpty()) {\n                            Text(\n                                text = \"$pageType: $title\",\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis,\n                                style = MaterialTheme.typography.bodySmall.copy(fontSize = 14.sp),\n                            )\n                        }\n                    }\n                },\n                actions = {\n                    androidx.compose.animation.AnimatedVisibility(\n                        visible = pagerState.currentPage == 0,\n                        enter = fadeIn(),\n                        exit = fadeOut()\n                    ) {\n                        Box {\n                            IconButton(onClick = { dropdownMenuExpanded = true }) {\n                                Icon(\n                                    Icons.Default.MoreVert,\n                                    contentDescription = null\n                                )\n                            }\n                            DropdownMenu(\n                                expanded = dropdownMenuExpanded,\n                                onDismissRequest = { dropdownMenuExpanded = false }\n                            ) {\n                                listOf(\"Type\", \"Order\")\n                                    .forEachIndexed { index, menu ->\n                                        DropdownMenuItem(\n                                            text = { Text(menu) },\n                                            onClick = {\n                                                dropdownMenuExpanded = false\n                                                when (index) {\n                                                    0 -> typeMenuExpanded = true\n                                                    1 -> orderMenuExpanded = true\n                                                }\n                                            },\n                                            trailingIcon = {\n                                                Icon(\n                                                    imageVector = Icons.AutoMirrored.Filled.ArrowRight,\n                                                    contentDescription = null\n                                                )\n                                            }\n                                        )\n                                    }\n\n                            }\n                            DropdownMenu(\n                                expanded = typeMenuExpanded,\n                                onDismissRequest = { typeMenuExpanded = false }\n                            ) {\n                                SearchFeedType.entries.forEach { feed ->\n                                    Row(\n                                        modifier = Modifier\n                                            .clickable {\n                                                typeMenuExpanded = false\n                                                feedType = feed\n                                            },\n                                        verticalAlignment = Alignment.CenterVertically\n                                    ) {\n                                        RadioButton(\n                                            selected = feedType == feed,\n                                            onClick = {\n                                                typeMenuExpanded = false\n                                                feedType = feed\n                                            }\n                                        )\n                                        Text(\n                                            text = feed.name,\n                                            style = MaterialTheme.typography.bodyMedium,\n                                            modifier = Modifier\n                                                .fillMaxWidth()\n                                                .padding(end = 16.dp)\n                                        )\n                                    }\n                                }\n                            }\n                            DropdownMenu(\n                                expanded = orderMenuExpanded,\n                                onDismissRequest = { orderMenuExpanded = false }\n                            ) {\n                                SearchOrderType.entries.forEach { order ->\n                                    Row(\n                                        modifier = Modifier\n                                            .clickable {\n                                                orderMenuExpanded = false\n                                                orderType = order\n                                            },\n                                        verticalAlignment = Alignment.CenterVertically\n                                    ) {\n                                        RadioButton(\n                                            selected = orderType == order,\n                                            onClick = {\n                                                orderMenuExpanded = false\n                                                orderType = order\n                                            }\n                                        )\n                                        Text(\n                                            text = order.name,\n                                            style = MaterialTheme.typography.bodyMedium,\n                                            modifier = Modifier\n                                                .fillMaxWidth()\n                                                .padding(end = 16.dp)\n                                        )\n                                    }\n                                }\n                            }\n                        }\n                    }\n                },\n            )\n        },\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(top = paddingValues.calculateTopPadding())\n        ) {\n\n            if (pageType.isNullOrEmpty()) {\n                SecondaryScrollableTabRow(\n                    modifier = Modifier\n                        .fillMaxWidth()\n                        .padding(\n                            start = paddingValues.calculateLeftPadding(layoutDirection),\n                        ),\n                    selectedTabIndex = pagerState.currentPage,\n                    indicator = {\n                        TabRowDefaults.SecondaryIndicator(\n                            Modifier\n                                .tabIndicatorOffset(\n                                    pagerState.currentPage,\n                                    matchContentSize = true\n                                )\n                                .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                        )\n                    },\n                    divider = {}\n                ) {\n                    tabList.forEachIndexed { index, tab ->\n                        Tab(\n                            selected = pagerState.currentPage == index,\n                            onClick = {\n                                if (pagerState.currentPage == index) {\n                                    refreshState = true\n                                }\n                                scope.launch { pagerState.animateScrollToPage(index) }\n                            },\n                            text = { Text(text = tab.name) }\n                        )\n                    }\n                }\n            }\n\n            HorizontalDivider()\n\n            HorizontalPager(\n                state = pagerState\n            ) { index ->\n                SearchContentScreen(\n                    searchType = SearchType.valueOf(SearchType.entries[index].name),\n                    keyword = keyword.decode,\n                    pageType = pageType,\n                    pageParam = pageParam,\n                    refreshState = refreshState,\n                    resetRefreshState = {\n                        refreshState = false\n                    },\n                    feedType = feedType,\n                    orderType = orderType,\n                    paddingValues = PaddingValues(\n                        start = paddingValues.calculateLeftPadding(layoutDirection),\n                        bottom = paddingValues.calculateBottomPadding(),\n                    ),\n                    onViewUser = onViewUser,\n                    onViewFeed = onViewFeed,\n                    onOpenLink = onOpenLink,\n                    onCopyText = onCopyText,\n                    updateInitPage = {\n                        updateInitPage(pagerState.currentPage)\n                    },\n                    onReport = onReport,\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/search/SearchScreen.kt",
    "content": "package com.example.c001apk.compose.ui.search\n\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.fadeIn\nimport androidx.compose.animation.fadeOut\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.ExperimentalLayoutApi\nimport androidx.compose.foundation.layout.FlowRow\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.text.KeyboardActions\nimport androidx.compose.foundation.text.KeyboardOptions\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.Clear\nimport androidx.compose.material.icons.filled.ClearAll\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.LocalTextStyle\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TextField\nimport androidx.compose.material3.TextFieldDefaults\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.text.input.ImeAction\nimport androidx.compose.ui.text.input.KeyboardType\nimport androidx.compose.ui.text.input.TextFieldValue\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport androidx.lifecycle.compose.collectAsStateWithLifecycle\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.cards.SearchHistoryCard\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/6\n */\n@OptIn(ExperimentalMaterial3Api::class, ExperimentalLayoutApi::class)\n@Composable\nfun SearchScreen(\n    onBackClick: () -> Unit,\n    title: String?,\n    onSearch: (String) -> Unit\n) {\n\n    val viewModel = hiltViewModel<SearchViewModel>(key = title)\n    val searchHistory by viewModel.searchHistory.collectAsStateWithLifecycle(initialValue = emptyList())\n\n    var textInput by rememberSaveable(stateSaver = TextFieldValue.Saver) {\n        mutableStateOf(TextFieldValue())\n    }\n\n    val focusRequest = remember { FocusRequester() }\n    LaunchedEffect(Unit) {\n        try {\n            focusRequest.requestFocus()\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n    val textStyle = LocalTextStyle.current\n    var showClearDialog by remember { mutableStateOf(false) }\n    val layoutDirection = LocalLayoutDirection.current\n\n    fun onCheckSearch(keyword: String = textInput.text) {\n        with(keyword) {\n            if (this.isNotEmpty()) {\n                viewModel.saveHistory(this)\n                onSearch(this)\n            }\n        }\n    }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    TextField(\n                        modifier = Modifier\n                            .fillMaxWidth()\n                            .focusRequester(focusRequest),\n                        singleLine = true,\n                        value = textInput,\n                        onValueChange = { textInput = it },\n                        textStyle = textStyle.copy(fontSize = 18.sp),\n                        placeholder = {\n                            Text(\n                                text = \"Search${if (!title.isNullOrEmpty()) \" in $title\" else EMPTY_STRING}\",\n                                maxLines = 1,\n                                overflow = TextOverflow.Ellipsis\n                            )\n                        },\n                        trailingIcon = {\n                            AnimatedVisibility(\n                                visible = textInput.text.isNotEmpty(),\n                                enter = fadeIn(),\n                                exit = fadeOut()\n                            ) {\n                                IconButton(onClick = {\n                                    textInput = TextFieldValue()\n                                }) {\n                                    Icon(\n                                        imageVector = Icons.Default.Clear,\n                                        contentDescription = null\n                                    )\n                                }\n                            }\n                        },\n                        colors = TextFieldDefaults.colors(\n                            focusedContainerColor = Color.Transparent,\n                            unfocusedContainerColor = Color.Transparent,\n                            focusedIndicatorColor = Color.Transparent,\n                            unfocusedIndicatorColor = Color.Transparent\n                        ),\n                        keyboardOptions = KeyboardOptions(\n                            keyboardType = KeyboardType.Text,\n                            imeAction = ImeAction.Search\n                        ),\n                        keyboardActions = KeyboardActions(\n                            onSearch = {\n                                onCheckSearch()\n                            }\n                        )\n                    )\n                },\n                actions = {\n                    IconButton(onClick = {\n                        onCheckSearch()\n                    }) {\n                        Icon(imageVector = Icons.Default.Search, contentDescription = null)\n                    }\n                }\n            )\n        }\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier.padding(\n                top = paddingValues.calculateTopPadding()\n            )\n        ) {\n            HorizontalDivider()\n\n            androidx.compose.animation.AnimatedVisibility(\n                modifier = Modifier.padding(\n                    start = paddingValues.calculateLeftPadding(\n                        layoutDirection\n                    )\n                ),\n                visible = searchHistory.isNotEmpty(),\n                enter = fadeIn(),\n                exit = fadeOut()\n            ) {\n                Row(\n                    modifier = Modifier.fillMaxWidth(),\n                    verticalAlignment = Alignment.CenterVertically\n                ) {\n                    Text(\n                        text = \"搜索历史\", modifier = Modifier\n                            .weight(1f)\n                            .padding(start = 10.dp),\n                        style = MaterialTheme.typography.titleSmall.copy(\n                            fontSize = 15.sp,\n                            fontWeight = FontWeight.Bold,\n                        )\n                    )\n\n                    IconButton(\n                        onClick = {\n                            showClearDialog = true\n                        }\n                    ) {\n                        Icon(imageVector = Icons.Default.ClearAll, contentDescription = null)\n                    }\n                }\n            }\n\n            FlowRow(\n                horizontalArrangement = Arrangement.spacedBy(10.dp),\n                verticalArrangement = Arrangement.spacedBy(10.dp),\n                modifier = Modifier\n                    .padding(start = paddingValues.calculateLeftPadding(layoutDirection))\n                    .padding(horizontal = 10.dp),\n            ) {\n                searchHistory.forEach {\n                    SearchHistoryCard(\n                        data = it.data,\n                        onSearch = {\n                            onCheckSearch(it.data)\n                        },\n                        onDelete = {\n                            viewModel.delete(it.data)\n                        }\n                    )\n                }\n            }\n        }\n\n    }\n\n    when {\n        showClearDialog -> {\n            AlertDialog(\n                onDismissRequest = { showClearDialog = false },\n                confirmButton = {\n                    TextButton(\n                        onClick = {\n                            showClearDialog = false\n                            viewModel.clearAll()\n                        }) {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                },\n                dismissButton = {\n                    TextButton(\n                        onClick = {\n                            showClearDialog = false\n                        }) {\n                        Text(text = stringResource(id = android.R.string.cancel))\n                    }\n                },\n                title = {\n                    Text(text = \"确定清除全部搜索历史？\", modifier = Modifier.fillMaxWidth())\n                }\n            )\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/search/SearchViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.search\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.StringEntity\nimport com.example.c001apk.compose.logic.repository.SearchHistoryRepo\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/16\n */\n@HiltViewModel\nclass SearchViewModel @Inject constructor(\n    private val searchHistoryRepo: SearchHistoryRepo\n) : ViewModel() {\n\n    val searchHistory: Flow<List<StringEntity>> = searchHistoryRepo.loadAllListFlow()\n\n    fun saveHistory(keyword: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (searchHistoryRepo.isExist(keyword)) {\n                searchHistoryRepo.updateHistory(keyword)\n            } else {\n                searchHistoryRepo.saveHistory(keyword)\n            }\n        }\n    }\n\n    fun clearAll() {\n        viewModelScope.launch(Dispatchers.IO) {\n            searchHistoryRepo.deleteAllHistory()\n        }\n    }\n\n    fun delete(keyword: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            searchHistoryRepo.deleteHistory(keyword)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/settings/AboutScreen.kt",
    "content": "package com.example.c001apk.compose.ui.settings\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.ErrorOutline\nimport androidx.compose.material.icons.outlined.AllInclusive\nimport androidx.compose.material.icons.outlined.Code\nimport androidx.compose.material.icons.outlined.Source\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.unit.dp\nimport androidx.core.content.res.ResourcesCompat\nimport com.example.c001apk.compose.BuildConfig\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.URL_SOURCE_CODE\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.settings.BasicListItem\nimport com.example.c001apk.compose.util.openInBrowser\nimport com.google.accompanist.drawablepainter.rememberDrawablePainter\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/2\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun AboutScreen(\n    onBackClick: () -> Unit,\n    onLicenseClick: () -> Unit,\n) {\n\n    val context = LocalContext.current\n    val rememberScrollState = rememberScrollState()\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = { Text(text = \"About\") },\n            )\n        }\n    ) { paddingValues ->\n        Column(\n            Modifier\n                .fillMaxSize()\n                .padding(paddingValues)\n                .verticalScroll(rememberScrollState)\n        ) {\n            Box(\n                Modifier\n                    .fillMaxWidth()\n                    .height(125.dp)\n                    .align(Alignment.CenterHorizontally)\n            ) {\n                val drawable = ResourcesCompat.getDrawable(\n                    context.resources,\n                    R.mipmap.ic_launcher,\n                    context.theme\n                )\n                Image(\n                    painter = rememberDrawablePainter(drawable),\n                    contentDescription = null,\n                    modifier = Modifier.align(Alignment.Center)\n                )\n            }\n            HorizontalDivider()\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.AllInclusive,\n                headlineText = stringResource(id = R.string.app_name),\n                supportingText = \"test only\"\n            ) { }\n            BasicListItem(\n                leadingImageVector = Icons.Default.ErrorOutline,\n                headlineText = \"Version\",\n                supportingText = \"${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})\"\n            ) { }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.Code,\n                headlineText = \"Source Code\",\n                supportingText = URL_SOURCE_CODE\n            ) {\n                context.openInBrowser(URL_SOURCE_CODE)\n            }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.Source,\n                headlineText = \"Open Source License\"\n            ) {\n                onLicenseClick()\n            }\n\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/settings/LicenseScreen.kt",
    "content": "package com.example.c001apk.compose.ui.settings\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.items\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.settings.BasicListItem\nimport com.example.c001apk.compose.util.openInBrowser\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/2\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun LicenseScreen(\n    onBackClick: () -> Unit\n) {\n\n    val layoutDirection = LocalLayoutDirection.current\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                navigationIcon = {\n                    BackButton(onBackClick)\n                },\n                title = {\n                    Text(text = \"Open Source License\")\n                },\n            )\n        }\n    ) { paddingValues ->\n        LazyColumn(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(\n                    top = paddingValues.calculateTopPadding(),\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                    end = paddingValues.calculateRightPadding(layoutDirection),\n                ),\n            contentPadding = PaddingValues(bottom = paddingValues.calculateBottomPadding())\n        ) {\n            items(\n                items = licenseList,\n                key = { it.url }\n            ) { item ->\n                LicenseRow(item = item)\n            }\n        }\n    }\n\n}\n\nprivate val licenseList = listOf(\n    LicenseItem(\n        \"Google\",\n        \"Jetpack Compose\",\n        \"https://github.com/androidx/androidx\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"JetBrains\",\n        \"Kotlin\",\n        \"https://github.com/JetBrains/kotlin\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"Google\",\n        \"Accompanist\",\n        \"https://github.com/google/accompanist\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"Google\",\n        \"Material Design 3\",\n        \"https://m3.material.io/\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"Google\",\n        \"Material Icons\",\n        \"https://github.com/google/material-design-icons\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"square\",\n        \"okhttp\",\n        \"https://github.com/square/okhttp\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"square\",\n        \"retrofit\",\n        \"https://github.com/square/retrofit\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"mikaelzero\",\n        \"mojito\",\n        \"https://github.com/mikaelzero/mojito\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"plain-dev\",\n        \"NineGridImageView\",\n        \"https://github.com/plain-dev/NineGridImageView\",\n        LicenseType.MIT\n    ),\n    LicenseItem(\n        \"coil-kt\",\n        \"coil\",\n        \"https://github.com/coil-kt/coil\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"wasabeef\",\n        \"transformers\",\n        \"https://github.com/wasabeef/transformers\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"jeremyh\",\n        \"jBCrypt\",\n        \"https://github.com/jeremyh/jBCrypt\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"jhy\",\n        \"jsoup\",\n        \"https://github.com/jhy/jsoup\",\n        LicenseType.MIT\n    ),\n    LicenseItem(\n        \"zhanghai\",\n        \"AppIconLoader\",\n        \"https://github.com/zhanghai/AppIconLoader\",\n        LicenseType.MIT\n    ),\n    LicenseItem(\n        \"onebone\",\n        \"compose-collapsing-toolbar\",\n        \"https://github.com/onebone/compose-collapsing-toolbar\",\n        LicenseType.MIT\n    ),\n    LicenseItem(\n        \"AlexMofer\",\n        \"SmoothInputLayout\",\n        \"https://github.com/AlexMofer/SmoothInputLayout\",\n        LicenseType.Apache2\n    ),\n    LicenseItem(\n        \"jordond\",\n        \"MaterialKolor\",\n        \"https://github.com/jordond/MaterialKolor\",\n        LicenseType.MIT\n    ),\n)\n\ndata class LicenseItem(\n    val author: String,\n    val name: String,\n    val url: String,\n    val type: LicenseType\n)\n\nenum class LicenseType {\n    Apache2,\n    MIT,\n    GPL3\n}\n\nprivate fun getLicense(type: LicenseType): String =\n    when (type) {\n        LicenseType.Apache2 -> \"Apache Software License 2.0\"\n        LicenseType.MIT -> \"MIT License\"\n        LicenseType.GPL3 -> \"GNU general public license Version 3\"\n    }\n\n@Composable\nfun LicenseRow(item: LicenseItem) {\n    val context = LocalContext.current\n    BasicListItem(\n        headlineText = \"${item.name} - ${item.author}\",\n        supportingText = \"${item.url}\\n${getLicense(item.type)}\"\n    ) {\n        context.openInBrowser(item.url)\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/settings/ParamsScreen.kt",
    "content": "package com.example.c001apk.compose.ui.settings\n\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.constant.Constants\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.settings.BasicListItem\nimport com.example.c001apk.compose.util.TokenDeviceUtils.encode\nimport com.example.c001apk.compose.util.Utils.randomMacAddress\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/1\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun ParamsScreen(\n    viewModel: SettingsViewModel = hiltViewModel(),\n    onBackClick: () -> Unit\n) {\n\n    val rememberScrollState = rememberScrollState()\n    val prefs = LocalUserPreferences.current\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = { Text(text = \"Params\") },\n            )\n        }\n    ) { paddingValues ->\n        Column(\n            Modifier\n                .fillMaxSize()\n                .padding(paddingValues)\n                .verticalScroll(rememberScrollState)\n        ) {\n\n            ParamsItem(\n                title = \"Version Name\",\n                data = prefs.versionName.ifEmpty { null }\n            ) {\n                viewModel.setVersionName(it)\n                viewModel.setUserAgent(\n                    \"Dalvik/2.1.0 (Linux; U; Android ${prefs.androidVersion}; ${prefs.model} ${prefs.buildNumber}) (#Build; ${prefs.brand}; ${prefs.model}; ${prefs.buildNumber}; ${prefs.androidVersion}) +CoolMarket/$it-${prefs.versionCode}-${Constants.MODE}\"\n                )\n            }\n\n            ParamsItem(\n                title = \"Api Version\",\n                data = prefs.apiVersion.ifEmpty { null }\n            ) {\n                viewModel.setApiVersion(it)\n            }\n\n            ParamsItem(\n                title = \"Version Code\",\n                data = prefs.versionCode.ifEmpty { null }\n            ) {\n                viewModel.setVersionCode(it)\n                viewModel.setUserAgent(\n                    \"Dalvik/2.1.0 (Linux; U; Android ${prefs.androidVersion}; ${prefs.model} ${prefs.buildNumber}) (#Build; ${prefs.brand}; ${prefs.model}; ${prefs.buildNumber}; ${prefs.androidVersion}) +CoolMarket/${prefs.versionName}-$it-${Constants.MODE}\"\n                )\n            }\n\n            ParamsItem(\n                title = \"Manufacturer\",\n                data = prefs.manufacturer.ifEmpty { null }\n            ) {\n                viewModel.setManufacturer(it)\n                viewModel.setXAppDevice(\n                    encode(\"${prefs.szlmId}; ; ; ${randomMacAddress()}; $it; ${prefs.brand}; ${prefs.model}; ${prefs.buildNumber}; null\")\n                )\n            }\n\n            ParamsItem(\n                title = \"Brand\",\n                data = prefs.brand.ifEmpty { null }\n            ) {\n                viewModel.setBrand(it)\n                viewModel.setXAppDevice(\n                    encode(\"${prefs.szlmId}; ; ; ${randomMacAddress()}; ${prefs.manufacturer}; $it; ${prefs.model}; ${prefs.buildNumber}; null\")\n                )\n                viewModel.setUserAgent(\n                    \"Dalvik/2.1.0 (Linux; U; Android ${prefs.androidVersion}; ${prefs.model} ${prefs.buildNumber}) (#Build; $it; ${prefs.model}; ${prefs.buildNumber}; ${prefs.androidVersion}) +CoolMarket/${prefs.versionName}-${prefs.versionCode}-${Constants.MODE}\"\n                )\n            }\n\n            ParamsItem(\n                title = \"Model\",\n                data = prefs.model.ifEmpty { null }\n            ) {\n                viewModel.setModel(it)\n                viewModel.setXAppDevice(\n                    encode(\"${prefs.szlmId}; ; ; ${randomMacAddress()}; ${prefs.manufacturer}; ${prefs.brand}; $it; ${prefs.buildNumber}; null\")\n                )\n                viewModel.setUserAgent(\n                    \"Dalvik/2.1.0 (Linux; U; Android ${prefs.androidVersion}; $it ${prefs.buildNumber}) (#Build; ${prefs.brand}; $it; ${prefs.buildNumber}; ${prefs.androidVersion}) +CoolMarket/${prefs.versionName}-${prefs.versionCode}-${Constants.MODE}\"\n                )\n            }\n\n            ParamsItem(\n                title = \"BuildNumber\",\n                data = prefs.buildNumber.ifEmpty { null }\n            ) {\n                viewModel.setBuildNumber(it)\n                viewModel.setXAppDevice(\n                    encode(\"${prefs.szlmId}; ; ; ${randomMacAddress()}; ${prefs.manufacturer}; ${prefs.brand}; ${prefs.model}; $it; null\")\n                )\n                viewModel.setUserAgent(\n                    \"Dalvik/2.1.0 (Linux; U; Android ${prefs.androidVersion}; ${prefs.model} ${prefs.buildNumber}) (#Build; ${prefs.brand}; ${prefs.model}; $it; ${prefs.androidVersion}) +CoolMarket/${prefs.versionName}-${prefs.versionCode}-${Constants.MODE}\"\n                )\n            }\n\n            ParamsItem(\n                title = \"SDK INT\",\n                data = prefs.sdkInt.ifEmpty { null }\n            ) {\n                viewModel.setSdkInt(it)\n            }\n\n            ParamsItem(\n                title = \"Android Version\",\n                data = prefs.androidVersion.ifEmpty { null }\n            ) {\n                viewModel.setAndroidVersion(it)\n                viewModel.setUserAgent(\n                    \"Dalvik/2.1.0 (Linux; U; Android $it; ${prefs.model} ${prefs.buildNumber}) (#Build; ${prefs.brand}; ${prefs.model}; ${prefs.buildNumber}; $it) +CoolMarket/${prefs.versionName}-${prefs.versionCode}-${Constants.MODE}\"\n                )\n            }\n\n            ParamsItem(\n                title = \"User Agent\",\n                data = prefs.userAgent.ifEmpty { null }\n            )\n\n            ParamsItem(\n                title = \"X-App-Device\",\n                data = prefs.xAppDevice.ifEmpty { null }\n            )\n\n            BasicListItem(\n                headlineText = \"Regenerate Params\"\n            ) {\n                viewModel.regenerateParams()\n            }\n\n        }\n    }\n\n}\n\n@Composable\nfun ParamsItem(\n    title: String,\n    data: String?,\n    setData: ((String) -> Unit)? = null\n) {\n\n    var showDialog by remember {\n        mutableStateOf(false)\n    }\n\n    BasicListItem(\n        headlineText = title,\n        supportingText = data\n    ) {\n        setData?.let {\n            showDialog = true\n        }\n    }\n\n    when {\n        showDialog -> {\n            EditTextDialog(\n                data = data.orEmpty(),\n                title = title,\n                onDismiss = {\n                    showDialog = false\n                },\n                setData = {\n                    if (setData != null) {\n                        setData(it)\n                    }\n                }\n            )\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/settings/SettingsScreen.kt",
    "content": "package com.example.c001apk.compose.ui.settings\n\nimport androidx.compose.foundation.Image\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.Spacer\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.height\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.size\nimport androidx.compose.foundation.layout.width\nimport androidx.compose.foundation.layout.wrapContentSize\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.itemsIndexed\nimport androidx.compose.foundation.rememberScrollState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.foundation.verticalScroll\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.outlined.Feed\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.outlined.AddCircleOutline\nimport androidx.compose.material.icons.outlined.AllInclusive\nimport androidx.compose.material.icons.outlined.Block\nimport androidx.compose.material.icons.outlined.CleaningServices\nimport androidx.compose.material.icons.outlined.DarkMode\nimport androidx.compose.material.icons.outlined.DeveloperMode\nimport androidx.compose.material.icons.outlined.EmojiEmotions\nimport androidx.compose.material.icons.outlined.FormatColorFill\nimport androidx.compose.material.icons.outlined.History\nimport androidx.compose.material.icons.outlined.Image\nimport androidx.compose.material.icons.outlined.ImageAspectRatio\nimport androidx.compose.material.icons.outlined.InvertColors\nimport androidx.compose.material.icons.outlined.Palette\nimport androidx.compose.material.icons.outlined.PhotoLibrary\nimport androidx.compose.material.icons.outlined.Smartphone\nimport androidx.compose.material.icons.outlined.SystemUpdate\nimport androidx.compose.material.icons.outlined.TextFields\nimport androidx.compose.material.icons.outlined.TravelExplore\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.CardDefaults\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ElevatedCard\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.OutlinedTextField\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Slider\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.focus.FocusRequester\nimport androidx.compose.ui.focus.focusRequester\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.TextRange\nimport androidx.compose.ui.text.input.TextFieldValue\nimport androidx.compose.ui.text.style.TextAlign\nimport androidx.compose.ui.unit.dp\nimport androidx.compose.ui.unit.sp\nimport androidx.compose.ui.window.Dialog\nimport androidx.core.content.res.ResourcesCompat\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.BuildConfig\nimport com.example.c001apk.compose.FollowType\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.ThemeMode\nimport com.example.c001apk.compose.ThemeType\nimport com.example.c001apk.compose.constant.Constants.URL_SOURCE_CODE\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.blacklist.BlackListType\nimport com.example.c001apk.compose.ui.component.HtmlText\nimport com.example.c001apk.compose.ui.component.icons.Swatch\nimport com.example.c001apk.compose.ui.component.settings.BasicListItem\nimport com.example.c001apk.compose.ui.component.settings.DropdownListItem\nimport com.example.c001apk.compose.ui.component.settings.SelectionItem\nimport com.example.c001apk.compose.ui.component.settings.StateDropdownListItem\nimport com.example.c001apk.compose.ui.component.settings.SwitchListItem\nimport com.example.c001apk.compose.util.CacheDataManager.clearAllCache\nimport com.example.c001apk.compose.util.CacheDataManager.getTotalCacheSize\nimport com.example.c001apk.compose.util.TokenDeviceUtils.encode\nimport com.example.c001apk.compose.util.Utils.randomMacAddress\nimport com.example.c001apk.compose.util.openInBrowser\nimport com.google.accompanist.drawablepainter.rememberDrawablePainter\nimport com.materialkolor.PaletteStyle\nimport java.util.Formatter\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/30\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun SettingsScreen(\n    viewModel: SettingsViewModel = hiltViewModel(),\n    onParamsClick: () -> Unit,\n    onAboutClick: () -> Unit,\n    onViewBlackList: (String) -> Unit,\n) {\n\n    val prefs = LocalUserPreferences.current\n    val rememberScrollState = rememberScrollState()\n    val layoutDirection = LocalLayoutDirection.current\n    val context = LocalContext.current\n\n    val imageQualityList by lazy { listOf(\"网络自适应\", \"原图\", \"普清\") }\n    val themeList by lazy { listOf(\"跟随系统\", \"总是开启\", \"总是关闭\") }\n    val followList by lazy { listOf(\"全部\", \"好友\", \"话题\", \"数码\", \"应用\") }\n\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    var showAboutDialog by remember { mutableStateOf(false) }\n    var showSZLMIDDialog by remember { mutableStateOf(false) }\n    var showFontScaleDialog by remember { mutableStateOf(false) }\n    var showContentScaleDialog by remember { mutableStateOf(false) }\n    var showImageQualityDialog by remember { mutableStateOf(false) }\n    var showCleanCacheDialog by remember { mutableStateOf(false) }\n    var showThemeTypeDialog by remember { mutableStateOf(false) }\n    var cacheSize by remember { mutableStateOf(getTotalCacheSize(context)) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                title = { Text(text = \"Settings\") },\n                actions = {\n                    Box(Modifier.wrapContentSize(Alignment.TopEnd)) {\n                        IconButton(onClick = { dropdownMenuExpanded = true }) {\n                            Icon(\n                                Icons.Default.MoreVert,\n                                contentDescription = Icons.Default.MoreVert.name\n                            )\n                        }\n\n                        DropdownMenu(\n                            expanded = dropdownMenuExpanded,\n                            onDismissRequest = { dropdownMenuExpanded = false }\n                        ) {\n                            listOf(\"Feedback\", \"About\").forEachIndexed { index, menu ->\n                                DropdownMenuItem(\n                                    text = { Text(menu) },\n                                    onClick = {\n                                        dropdownMenuExpanded = false\n                                        when (index) {\n                                            0 -> context.openInBrowser(\"$URL_SOURCE_CODE/issues\")\n\n                                            1 -> showAboutDialog = true\n                                        }\n                                    }\n                                )\n                            }\n                        }\n                    }\n                },\n            )\n        }\n    ) { paddingValues ->\n        Column(\n            Modifier\n                .fillMaxSize()\n                .padding(\n                    top = paddingValues.calculateTopPadding(),\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                    end = paddingValues.calculateRightPadding(layoutDirection)\n                )\n                .verticalScroll(rememberScrollState)\n        ) {\n\n            BasicListItem(leadingText = stringResource(id = R.string.app_name))\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.Smartphone,\n                headlineText = \"数字联盟ID\",\n                supportingText = prefs.szlmId.ifEmpty { null }\n            ) {\n                showSZLMIDDialog = true\n            }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.DeveloperMode,\n                headlineText = \"机型参数\",\n            ) {\n                onParamsClick()\n            }\n\n            BasicListItem(leadingText = \"主题\")\n            SwitchListItem(\n                value = prefs.materialYou,\n                leadingImageVector = Icons.Outlined.Palette,\n                headlineText = \"系统主题色\",\n            ) {\n                viewModel.setMaterialYou(it)\n            }\n            StateDropdownListItem(\n                isEnable = !prefs.materialYou,\n                leadingImageVector = Icons.Outlined.FormatColorFill,\n                headlineText = \"主题颜色\",\n                value = prefs.themeType.name,\n                selections = ThemeType.entries.toMutableList()\n                    .also { it.remove(ThemeType.UNRECOGNIZED) }.map {\n                        SelectionItem(it.name, it.name)\n                    },\n                onValueChanged = { index, _ ->\n                    if (index == 19)\n                        showThemeTypeDialog = true\n                    viewModel.setThemeType(ThemeType.forNumber(index))\n                }\n            )\n            StateDropdownListItem(\n                isEnable = !prefs.materialYou,\n                leadingImageVector = Icons.Rounded.Swatch,\n                headlineText = \"调色版风格\",\n                value = prefs.paletteStyle,\n                selections = PaletteStyle.entries.mapIndexed { index, label ->\n                    SelectionItem(label.name, index)\n                },\n                onValueChanged = { index, _ ->\n                    viewModel.setPaletteStyle(index)\n                }\n            )\n            DropdownListItem(\n                leadingImageVector = Icons.Outlined.DarkMode,\n                headlineText = \"深色主题\",\n                value = prefs.themeMode.name,\n                selections = (0..2).map {\n                    SelectionItem(\n                        themeList[it],\n                        ThemeMode.entries[it].name\n                    )\n                }/*ThemeMode.entries.map {\n                    SelectionItem(it.name, it.name)\n                }*/,\n                onValueChanged = { index, _ ->\n                    viewModel.setDarkTheme(ThemeMode.forNumber(index))\n                }\n            )\n            SwitchListItem(\n                value = prefs.pureBlack,\n                leadingImageVector = Icons.Outlined.InvertColors,\n                headlineText = \"纯黑主题\",\n            ) {\n                viewModel.setPureBlack(it)\n            }\n\n            BasicListItem(leadingText = \"显示\")\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.Block,\n                headlineText = \"用户黑名单\",\n            ) {\n                onViewBlackList(BlackListType.USER.name)\n            }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.Block,\n                headlineText = \"话题黑名单\",\n            ) {\n                onViewBlackList(BlackListType.TOPIC.name)\n            }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.TextFields,\n                headlineText = \"字体大小\",\n                supportingText = \"${Formatter().format(\"%.2f\", prefs.fontScale)}x\"\n            ) {\n                showFontScaleDialog = true\n            }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.ImageAspectRatio,\n                headlineText = \"内容大小\",\n                supportingText = \"${Formatter().format(\"%.2f\", prefs.contentScale)}x\"\n            ) {\n                showContentScaleDialog = true\n            }\n            DropdownListItem(\n                leadingImageVector = Icons.Outlined.AddCircleOutline,\n                headlineText = \"关注分组\",\n                value = prefs.followType.name,\n                selections = followList.mapIndexed { index, label ->\n                    SelectionItem(label, FollowType.entries[index].name)\n                },\n                onValueChanged = { index, _ ->\n                    viewModel.setFollowType(FollowType.forNumber(index))\n                }\n            )\n            DropdownListItem(\n                leadingImageVector = Icons.Outlined.Image,\n                headlineText = \"图片画质\",\n                value = prefs.imageQuality,\n                selections = imageQualityList.mapIndexed { index, label ->\n                    SelectionItem(label, index)\n                },\n                onValueChanged = { index, _ ->\n                    viewModel.setImageQuality(index)\n                }\n            )\n            /*BasicListItem(\n                leadingImageVector = Icons.Outlined.Image,\n                headlineText = \"Image Quality\",\n                supportingText = imageQualityList[userPreferences.imageQuality]\n            ) {\n                showImageQualityDialog = true\n            }*/\n            SwitchListItem(\n                value = prefs.imageFilter,\n                leadingImageVector = Icons.Outlined.PhotoLibrary,\n                headlineText = \"压暗图片\",\n            ) {\n                viewModel.setImageFilter(it)\n            }\n            SwitchListItem(\n                value = prefs.openInBrowser,\n                leadingImageVector = Icons.Outlined.TravelExplore,\n                headlineText = \"使用外部浏览器打开链接\",\n            ) {\n                viewModel.setOpenInBrowser(it)\n            }\n            SwitchListItem(\n                value = prefs.showSquare,\n                leadingImageVector = Icons.AutoMirrored.Outlined.Feed,\n                headlineText = \"头条显示广场\",\n            ) {\n                viewModel.setShowSquare(it)\n            }\n            SwitchListItem(\n                value = prefs.recordHistory,\n                leadingImageVector = Icons.Outlined.History,\n                headlineText = \"记录浏览历史\",\n            ) {\n                viewModel.setRecordHistory(it)\n            }\n            SwitchListItem(\n                value = prefs.showEmoji,\n                leadingImageVector = Icons.Outlined.EmojiEmotions,\n                headlineText = \"显示表情\",\n            ) {\n                viewModel.setShowEmoji(it)\n            }\n            SwitchListItem(\n                value = prefs.checkUpdate,\n                leadingImageVector = Icons.Outlined.SystemUpdate,\n                headlineText = \"检查更新\",\n            ) {\n                viewModel.setCheckUpdate(it)\n            }\n            /*SwitchListItem(\n                value = prefs.checkCount,\n                leadingImageVector = Icons.Outlined.Notifications,\n                headlineText = \"检查通知\",\n            ) {\n                viewModel.setCheckCount(it)\n            }*/\n\n            BasicListItem(leadingText = \"其他\")\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.AllInclusive,\n                headlineText = \"关于\",\n                supportingText = \"${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})\"\n            ) {\n                onAboutClick()\n            }\n            BasicListItem(\n                leadingImageVector = Icons.Outlined.CleaningServices,\n                headlineText = \"清理缓存\",\n                supportingText = cacheSize\n            ) {\n                cacheSize = getTotalCacheSize(context)\n                showCleanCacheDialog = true\n            }\n\n        }\n\n        when {\n            showAboutDialog -> {\n                AboutDialog {\n                    showAboutDialog = false\n                }\n            }\n\n            showImageQualityDialog -> {\n                ListItemDialog(\n                    title = \"Image Quality\",\n                    list = imageQualityList,\n                    selected = prefs.imageQuality,\n                    onDismiss = {\n                        showImageQualityDialog = false\n                    },\n                    setData = {\n                        showImageQualityDialog = false\n                        viewModel.setImageQuality(it)\n                    }\n                )\n            }\n\n            showThemeTypeDialog -> {\n                EditTextDialog(\n                    hint = \"6650A4\",\n                    maxLength = 6,\n                    data = prefs.seedColor,\n                    title = \"Custom Theme Color\",\n                    onDismiss = {\n                        showThemeTypeDialog = false\n                    },\n                    setData = {\n                        viewModel.setSeedColor(it)\n                    }\n                )\n            }\n\n            showSZLMIDDialog -> {\n                EditTextDialog(\n                    data = prefs.szlmId,\n                    title = \"SZLM ID\",\n                    onDismiss = {\n                        showSZLMIDDialog = false\n                    },\n                    setData = {\n                        viewModel.setSZLMId(it)\n                        viewModel.setXAppDevice(\n                            encode(\"$it; ; ; ${randomMacAddress()}; ${prefs.manufacturer}; ${prefs.brand}; ${prefs.model}; ${prefs.buildNumber}; null\")\n                        )\n                    }\n                )\n            }\n\n            showFontScaleDialog -> {\n                SliderDialog(\n                    data = prefs.fontScale,\n                    title = \"Font Scale\",\n                    hint = \"Font Size\",\n                    onDismiss = {\n                        showFontScaleDialog = false\n                    },\n                    setData = {\n                        viewModel.setFontScale(it)\n                    }\n                )\n            }\n\n            showContentScaleDialog -> {\n                SliderDialog(\n                    data = prefs.contentScale,\n                    title = \"Content Scale\",\n                    hint = \"Content Size\",\n                    onDismiss = {\n                        showContentScaleDialog = false\n                    },\n                    setData = {\n                        viewModel.setContentScale(it)\n                    }\n                )\n            }\n\n            showCleanCacheDialog -> {\n                CleanCacheDialog(\n                    cacheSize = cacheSize,\n                    onDismiss = {\n                        showCleanCacheDialog = false\n                    },\n                    onClean = {\n                        clearAllCache(context)\n                        cacheSize = \"0 B\"\n                    }\n                )\n            }\n\n        }\n    }\n\n}\n\n@Composable\nfun CleanCacheDialog(\n    cacheSize: String,\n    onDismiss: () -> Unit,\n    onClean: () -> Unit\n) {\n    AlertDialog(\n        onDismissRequest = { onDismiss() },\n        dismissButton = {\n            TextButton(\n                onClick = {\n                    onDismiss()\n                }) {\n                Text(text = stringResource(id = android.R.string.cancel))\n            }\n        },\n        confirmButton = {\n            TextButton(\n                onClick = {\n                    onDismiss()\n                    onClean()\n                }\n            ) {\n                Text(text = stringResource(id = android.R.string.ok))\n            }\n        },\n        text = {\n            Text(text = \"当前缓存大小: $cacheSize\")\n        },\n        title = {\n            Text(\n                modifier = Modifier.fillMaxWidth(),\n                text = \"清理缓存\",\n                textAlign = TextAlign.Center\n            )\n        }\n    )\n}\n\n@Composable\nfun ListItemDialog(\n    title: String,\n    list: List<String>,\n    selected: Int,\n    onDismiss: () -> Unit,\n    setData: (Int) -> Unit\n) {\n    AlertDialog(\n        onDismissRequest = { onDismiss() },\n        confirmButton = {},\n        title = {\n            Text(\n                modifier = Modifier.fillMaxWidth(),\n                text = title,\n                textAlign = TextAlign.Center\n            )\n        },\n        text = {\n            LazyColumn {\n                itemsIndexed(list) { index, title ->\n                    ListItemDialogItem(title, selected == index) {\n                        setData(index)\n                    }\n                }\n            }\n        },\n    )\n}\n\n@Composable\nfun ListItemDialogItem(title: String, selected: Boolean, onClick: () -> Unit) {\n    Row(\n        modifier = Modifier.clickable { onClick() }\n    ) {\n        RadioButton(selected = selected, onClick = { onClick() })\n        Text(\n            modifier = Modifier\n                .fillMaxWidth()\n                .align(Alignment.CenterVertically),\n            text = title,\n        )\n    }\n}\n\n@Composable\nfun EditTextDialog(\n    data: String,\n    title: String,\n    onDismiss: () -> Unit,\n    setData: (String) -> Unit,\n    hint: String? = null,\n    maxLength: Int? = null,\n) {\n    var text by remember {\n        mutableStateOf(\n            TextFieldValue(\n                text = data,\n                selection = TextRange(data.length)\n            )\n        )\n    }\n    val focusRequest = remember { FocusRequester() }\n    LaunchedEffect(Unit) {\n        try {\n            focusRequest.requestFocus()\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n    AlertDialog(\n        onDismissRequest = { onDismiss() },\n        dismissButton = {\n            TextButton(\n                onClick = {\n                    onDismiss()\n                }) {\n                Text(text = stringResource(id = android.R.string.cancel))\n            }\n        },\n        confirmButton = {\n            TextButton(\n                onClick = {\n                    onDismiss()\n                    setData(text.text)\n                }) {\n                Text(text = stringResource(id = android.R.string.ok))\n            }\n        },\n        title = {\n            Text(\n                modifier = Modifier.fillMaxWidth(),\n                text = title,\n                textAlign = TextAlign.Center\n            )\n        },\n        text = {\n            OutlinedTextField(\n                modifier = Modifier.focusRequester(focusRequest),\n                value = text,\n                onValueChange = {\n                    if (maxLength != null) {\n                        if (it.text.matches(Regex(\"^[0-9a-fA-F]{0,6}$\")))\n                            text = it\n                    } else {\n                        text = it\n                    }\n                },\n                placeholder = {\n                    hint?.let {\n                        Text(text = it)\n                    }\n                }\n            )\n        },\n    )\n}\n\n@Composable\nfun SliderDialog(\n    data: Float,\n    title: String,\n    hint: String,\n    onDismiss: () -> Unit,\n    setData: (Float) -> Unit\n) {\n    var progress by remember { mutableFloatStateOf(data) }\n    AlertDialog(\n        onDismissRequest = {\n            onDismiss()\n        },\n        dismissButton = {\n            TextButton(\n                onClick = {\n                    onDismiss()\n                    setData(1.0f)\n                }) {\n                Text(text = \"Reset\")\n            }\n        },\n        confirmButton = {\n            TextButton(\n                onClick = {\n                    onDismiss()\n                    setData(progress)\n                }) {\n                Text(text = stringResource(id = android.R.string.ok))\n            }\n        },\n        title = {\n            Text(\n                modifier = Modifier.fillMaxWidth(),\n                text = title,\n                textAlign = TextAlign.Center\n            )\n        },\n        text = {\n            Column(\n                Modifier.fillMaxWidth()\n            ) {\n                Slider(\n                    value = progress,\n                    onValueChange = {\n                        progress = it\n                    },\n                    valueRange = 0.8f..1.3f\n                )\n                Text(\n                    text = \"$hint: ${Formatter().format(\"%.2f\", progress)}\",\n                    modifier = Modifier.fillMaxWidth(),\n                    fontSize = 15.sp * progress,\n                    textAlign = TextAlign.Center\n                )\n            }\n        }\n    )\n}\n\n@Composable\nfun AboutDialog(onDismiss: () -> Unit) {\n    val context = LocalContext.current\n    Dialog(onDismissRequest = { onDismiss() }) {\n        ElevatedCard(\n            modifier = Modifier.fillMaxWidth(),\n            shape = RoundedCornerShape(28.dp),\n            colors = CardDefaults.elevatedCardColors()\n                .copy(containerColor = MaterialTheme.colorScheme.surfaceContainerHigh)\n        ) {\n            Row(\n                modifier = Modifier\n                    .fillMaxWidth()\n                    .padding(24.dp)\n            ) {\n                val drawable = ResourcesCompat.getDrawable(\n                    context.resources,\n                    R.mipmap.ic_launcher,\n                    context.theme\n                )\n                Image(\n                    painter = rememberDrawablePainter(drawable),\n                    contentDescription = \"icon\",\n                    modifier = Modifier.size(40.dp)\n                )\n\n                Spacer(modifier = Modifier.width(12.dp))\n\n                Column {\n\n                    Text(\n                        stringResource(id = R.string.app_name),\n                        style = MaterialTheme.typography.titleSmall,\n                        fontSize = 18.sp\n                    )\n                    Text(\n                        text = \"${BuildConfig.VERSION_NAME}(${BuildConfig.VERSION_CODE})\",\n                        style = MaterialTheme.typography.bodySmall,\n                        fontSize = 14.sp\n                    )\n                    Spacer(modifier = Modifier.height(8.dp))\n\n                    HtmlText(\n                        html = stringResource(\n                            id = R.string.about_source_code,\n                            \"<b><a href=\\\"${URL_SOURCE_CODE}\\\">GitHub</a></b>\"\n                        )\n                    )\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/settings/SettingsViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.settings\n\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.FollowType\nimport com.example.c001apk.compose.ThemeMode\nimport com.example.c001apk.compose.ThemeType\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport com.example.c001apk.compose.ui.base.PrefsViewModel\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/31\n */\n@HiltViewModel\nclass SettingsViewModel @Inject constructor(\n    private val userPreferencesRepository: UserPreferencesRepository\n) : PrefsViewModel(userPreferencesRepository) {\n\n    fun setDarkTheme(value: ThemeMode) {\n        viewModelScope.launch {\n            userPreferencesRepository.setThemeMode(value)\n        }\n    }\n\n    fun setMaterialYou(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setMaterialYou(value)\n        }\n    }\n\n    fun setPureBlack(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setPureBlack(value)\n        }\n    }\n\n    fun setFontScale(value: Float) {\n        viewModelScope.launch {\n            userPreferencesRepository.setFontScale(value)\n        }\n    }\n\n    fun setContentScale(value: Float) {\n        viewModelScope.launch {\n            userPreferencesRepository.setContentScale(value)\n        }\n    }\n\n    fun setSZLMId(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setSZLMId(value)\n        }\n    }\n\n    fun setImageQuality(value: Int) {\n        viewModelScope.launch {\n            userPreferencesRepository.setImageQuality(value)\n        }\n    }\n\n    fun setImageFilter(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setImageFilter(value)\n        }\n    }\n\n    fun setOpenInBrowser(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setOpenInBrowser(value)\n        }\n    }\n\n    fun setShowSquare(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setShowSquare(value)\n        }\n    }\n\n    fun setRecordHistory(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setRecordHistory(value)\n        }\n    }\n\n    fun setShowEmoji(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setShowEmoji(value)\n        }\n    }\n\n    fun setCheckUpdate(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setCheckUpdate(value)\n        }\n    }\n\n    fun setCheckCount(value: Boolean) {\n        viewModelScope.launch {\n            userPreferencesRepository.setCheckCount(value)\n        }\n    }\n\n    fun setVersionName(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setVersionName(value)\n        }\n    }\n\n    fun setApiVersion(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setApiVersion(value)\n        }\n    }\n\n    fun setVersionCode(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setVersionCode(value)\n        }\n    }\n\n    fun setManufacturer(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setManufacturer(value)\n        }\n    }\n\n    fun setBrand(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setBrand(value)\n        }\n    }\n\n    fun setModel(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setModel(value)\n        }\n    }\n\n    fun setBuildNumber(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setBuildNumber(value)\n        }\n    }\n\n    fun setSdkInt(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setSdkInt(value)\n        }\n    }\n\n    fun setAndroidVersion(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setAndroidVersion(value)\n        }\n    }\n\n    fun setUserAgent(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setUserAgent(value)\n        }\n    }\n\n    fun setXAppDevice(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setXAppDevice(value)\n        }\n    }\n\n    fun setXAppToken(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setXAppToken(value)\n        }\n    }\n\n    fun setFollowType(value: FollowType) {\n        viewModelScope.launch {\n            userPreferencesRepository.setFollowType(value)\n        }\n    }\n\n    fun setThemeType(value: ThemeType) {\n        viewModelScope.launch {\n            userPreferencesRepository.setThemeType(value)\n        }\n    }\n\n    fun setSeedColor(value: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.setSeedColor(value)\n        }\n    }\n\n    fun setPaletteStyle(value: Int) {\n        viewModelScope.launch {\n            userPreferencesRepository.setPaletteStyle(value)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/theme/Color.kt",
    "content": "package com.example.c001apk.compose.ui.theme\n\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.ui.graphics.Color\n\nval Purple80 = Color(0xFFD0BCFF)\nval PurpleGrey80 = Color(0xFFCCC2DC)\nval Pink80 = Color(0xFFEFB8C8)\n\nval Purple40 = Color(0xFF6650a4)\nval PurpleGrey40 = Color(0xFF625b71)\nval Pink40 = Color(0xFF7D5260)\n\n@Composable\nfun cardBg() = MaterialTheme.colorScheme.surfaceContainer\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/theme/Theme.kt",
    "content": "package com.example.c001apk.compose.ui.theme\n\nimport android.app.Activity\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Build.VERSION_CODES.Q\nimport android.os.Build.VERSION_CODES.S\nimport androidx.annotation.FloatRange\nimport androidx.compose.foundation.isSystemInDarkTheme\nimport androidx.compose.material3.ColorScheme\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.darkColorScheme\nimport androidx.compose.material3.dynamicDarkColorScheme\nimport androidx.compose.material3.dynamicLightColorScheme\nimport androidx.compose.material3.lightColorScheme\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.CompositionLocalProvider\nimport androidx.compose.runtime.SideEffect\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.graphics.toArgb\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalDensity\nimport androidx.compose.ui.platform.LocalView\nimport androidx.compose.ui.unit.Density\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.WindowCompat\nimport com.example.c001apk.compose.ThemeType\nimport com.example.c001apk.compose.constant.Constants.seedColors\nimport com.materialkolor.PaletteStyle\nimport com.materialkolor.rememberDynamicColorScheme\n\nprivate val DarkColorScheme = darkColorScheme(\n    primary = Purple80,\n    secondary = PurpleGrey80,\n    tertiary = Pink80,\n)\n\nprivate val LightColorScheme = lightColorScheme(\n    primary = Purple40,\n    secondary = PurpleGrey40,\n    tertiary = Pink40\n\n    /* Other default colors to override\n    background = Color(0xFFFFFBFE),\n    surface = Color(0xFFFFFBFE),\n    onPrimary = Color.White,\n    onSecondary = Color.White,\n    onTertiary = Color.White,\n    onBackground = Color(0xFF1C1B1F),\n    onSurface = Color(0xFF1C1B1F),\n    */\n)\n\nenum class ColorSchemeMode {\n    LIGHT,\n    DARK,\n    BLACK\n}\n\n@Composable\nfun C001apkComposeTheme(\n    darkTheme: Boolean = isSystemInDarkTheme(),\n    themeType: ThemeType = ThemeType.Default,\n    seedColor: String? = null,\n    materialYou: Boolean = true,\n    pureBlack: Boolean = false,\n    paletteStyle: Int = 0,\n    fontScale: Float = 1.00f,\n    contentScale: Float = 1.00f,\n    content: @Composable () -> Unit\n) {\n\n    val colorSchemeMode =\n        when (darkTheme) {\n            true -> when (pureBlack) {\n                true -> ColorSchemeMode.BLACK\n                false -> ColorSchemeMode.DARK\n            }\n\n            false -> ColorSchemeMode.LIGHT\n        }\n\n    val colorScheme = if (SDK_INT >= S && materialYou) {\n        val context = LocalContext.current\n        when (colorSchemeMode) {\n            ColorSchemeMode.LIGHT -> dynamicLightColorScheme(context)\n            ColorSchemeMode.DARK -> dynamicDarkColorScheme(context)\n            ColorSchemeMode.BLACK -> dynamicDarkColorScheme(context).toAmoled()\n        }\n    } else {\n        val color = Color(\n            seedColors.getOrNull(ThemeType.entries.indexOf(themeType))\n                ?: \"FF$seedColor\".toLongOrNull(16) ?: seedColors[0]\n        )\n        when (colorSchemeMode) {\n            ColorSchemeMode.LIGHT ->\n                rememberDynamicColorScheme(color, false, style = PaletteStyle.entries[paletteStyle])\n\n            ColorSchemeMode.DARK ->\n                rememberDynamicColorScheme(color, true, style = PaletteStyle.entries[paletteStyle])\n\n            ColorSchemeMode.BLACK ->\n                rememberDynamicColorScheme(\n                    color,\n                    true,\n                    style = PaletteStyle.entries[paletteStyle]\n                ).toAmoled()\n        }\n    }\n\n    val view = LocalView.current\n    if (!view.isInEditMode) {\n        SideEffect {\n            val window = (view.context as Activity).window\n            window.statusBarColor = Color.Transparent.toArgb()\n            window.navigationBarColor = Color.Transparent.toArgb()\n            if (SDK_INT >= Q) {\n                window.isStatusBarContrastEnforced = false\n                window.isNavigationBarContrastEnforced = false\n            }\n            WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = !darkTheme\n            WindowCompat.getInsetsController(window, view).isAppearanceLightNavigationBars =\n                !darkTheme\n        }\n    }\n\n    MaterialTheme(\n        colorScheme = colorScheme,\n        typography = Typography,\n        content = {\n            CompositionLocalProvider(\n                LocalDensity provides Density(\n                    LocalDensity.current.density * contentScale,\n                    LocalDensity.current.fontScale * fontScale,\n                )\n            ) {\n                content()\n            }\n        }\n    )\n\n}\n\nfun Color.darken(fraction: Float = 0.5f): Color =\n    Color(toArgb().blend(Color.Black.toArgb(), fraction))\n\nfun Int.blend(\n    color: Int,\n    @FloatRange(from = 0.0, to = 1.0) fraction: Float = 0.5f,\n): Int = ColorUtils.blendARGB(this, color, fraction)\n\nfun ColorScheme.toAmoled(): ColorScheme {\n    return copy(\n        primary = primary.darken(0.3f),\n        onPrimary = onPrimary.darken(0.3f),\n        primaryContainer = primaryContainer.darken(0.3f),\n        onPrimaryContainer = onPrimaryContainer.darken(0.3f),\n        inversePrimary = inversePrimary.darken(0.3f),\n        secondary = secondary.darken(0.3f),\n        onSecondary = onSecondary.darken(0.3f),\n        secondaryContainer = secondaryContainer.darken(0.3f),\n        onSecondaryContainer = onSecondaryContainer.darken(0.3f),\n        tertiary = tertiary.darken(0.3f),\n        onTertiary = onTertiary.darken(0.3f),\n        tertiaryContainer = tertiaryContainer.darken(0.3f),\n        onTertiaryContainer = onTertiaryContainer.darken(0.2f),\n        background = Color.Black,\n        onBackground = onBackground.darken(0.15f),\n        surface = Color.Black,\n        onSurface = onSurface.darken(0.15f),\n        surfaceVariant = surfaceVariant,\n        onSurfaceVariant = onSurfaceVariant,\n        surfaceTint = surfaceTint,\n        inverseSurface = inverseSurface.darken(),\n        inverseOnSurface = inverseOnSurface.darken(0.2f),\n        outline = outline.darken(0.2f),\n        outlineVariant = outlineVariant.darken(0.2f),\n        surfaceContainer = surfaceContainer.darken(),\n        surfaceContainerHigh = surfaceContainerHigh.darken(),\n        surfaceContainerHighest = surfaceContainerHighest.darken(0.4f),\n    )\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/theme/Type.kt",
    "content": "package com.example.c001apk.compose.ui.theme\n\nimport androidx.compose.material3.Typography\nimport androidx.compose.ui.text.TextStyle\nimport androidx.compose.ui.text.font.FontFamily\nimport androidx.compose.ui.text.font.FontWeight\nimport androidx.compose.ui.unit.sp\n\n// Set of Material typography styles to start with\nval Typography = Typography(\n    bodyLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 16.sp,\n        lineHeight = 24.sp,\n        letterSpacing = 0.5.sp\n    )\n    /* Other default text styles to override\n    titleLarge = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Normal,\n        fontSize = 22.sp,\n        lineHeight = 28.sp,\n        letterSpacing = 0.sp\n    ),\n    labelSmall = TextStyle(\n        fontFamily = FontFamily.Default,\n        fontWeight = FontWeight.Medium,\n        fontSize = 11.sp,\n        lineHeight = 16.sp,\n        letterSpacing = 0.5.sp\n    )\n    */\n)"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/topic/TopicContentScreen.kt",
    "content": "package com.example.c001apk.compose.ui.topic\n\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.LaunchedEffect\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.ui.component.CommonScreen\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@Composable\nfun TopicContentScreen(\n    refreshState: Boolean,\n    resetRefreshState: () -> Unit,\n    entityType: String,\n    id: String?,\n    url: String,\n    title: String,\n    sortType: ProductSortType,\n    paddingValues: PaddingValues,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    isScrollingUp: ((Boolean) -> Unit)? = null,\n) {\n\n    val viewModel =\n        hiltViewModel<TopicContentViewModel, TopicContentViewModel.ViewModelFactory>(key = title) { factory ->\n            factory.create(url, title)\n        }\n\n    if (entityType == \"product\" && title == \"讨论\") {\n        LaunchedEffect(sortType) {\n            if (sortType != viewModel.sortType) {\n                viewModel.sortType = sortType\n                viewModel.title = when (sortType) {\n                    ProductSortType.REPLY -> \"最近回复\"\n                    ProductSortType.HOT -> \"热度排序\"\n                    ProductSortType.DATELINE -> \"最新发布\"\n                }\n                viewModel.url = \"/page?url=/product/feedList?type=feed&id=$id&\" + when (sortType) {\n                    ProductSortType.REPLY -> \"ignoreEntityById=1\"\n                    ProductSortType.HOT -> \"listType=rank_score\"\n                    ProductSortType.DATELINE -> \"ignoreEntityById=1&listType=dateline_desc\"\n                }\n                viewModel.refresh()\n            }\n        }\n    }\n\n    CommonScreen(\n        viewModel = viewModel,\n        refreshState = refreshState,\n        resetRefreshState = resetRefreshState,\n        paddingValues = paddingValues,\n        onViewUser = onViewUser,\n        onViewFeed = onViewFeed,\n        onOpenLink = onOpenLink,\n        onCopyText = onCopyText,\n        onReport = onReport,\n        isScrollingUp = isScrollingUp,\n    )\n\n    val context = LocalContext.current\n    viewModel.toastText?.let{\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/topic/TopicContentViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.topic\n\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@HiltViewModel(assistedFactory = TopicContentViewModel.ViewModelFactory::class)\nclass TopicContentViewModel @AssistedInject constructor(\n    @Assisted(\"url\") var url: String,\n    @Assisted(\"title\") var title: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"url\") url: String,\n            @Assisted(\"title\") title: String,\n        ): TopicContentViewModel\n    }\n\n    var sortType = ProductSortType.REPLY\n\n    init {\n        fetchData()\n    }\n\n    override suspend fun customFetchData() =\n        networkRepo.getDataList(url, title, EMPTY_STRING, lastItem, page)\n\n    override fun handleLoadMore(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data> {\n        return response.distinctBy { it.entityId }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/topic/TopicScreen.kt",
    "content": "package com.example.c001apk.compose.ui.topic\n\nimport android.content.Intent\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.slideInVertically\nimport androidx.compose.animation.slideOutVertically\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.layout.wrapContentSize\nimport androidx.compose.foundation.pager.HorizontalPager\nimport androidx.compose.foundation.pager.PagerState\nimport androidx.compose.foundation.pager.rememberPagerState\nimport androidx.compose.foundation.shape.RoundedCornerShape\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.automirrored.filled.ArrowRight\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.FloatingActionButton\nimport androidx.compose.material3.HorizontalDivider\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.RadioButton\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SecondaryScrollableTabRow\nimport androidx.compose.material3.Tab\nimport androidx.compose.material3.TabRowDefaults\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.saveable.rememberSaveable\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.draw.clip\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.core.app.ActivityOptionsCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.res.ResourcesCompat\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.feed.reply.ReplyActivity\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.makeToast\nimport com.google.accompanist.drawablepainter.rememberDrawablePainter\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n\nenum class ProductSortType {\n    REPLY, HOT, DATELINE\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun TopicScreen(\n    onBackClick: () -> Unit,\n    tag: String?,\n    id: String?,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onSearch: (String, String, String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n) {\n\n    val viewModel =\n        hiltViewModel<TopicViewModel, TopicViewModel.ViewModelFactory>(key = id) { factory ->\n            factory.create(\n                url = when {\n                    !tag.isNullOrEmpty() -> \"/v6/topic/newTagDetail\"\n                    !id.isNullOrEmpty() -> \"/v6/product/detail\"\n                    else -> throw IllegalArgumentException(\"empty param\")\n                },\n                tag = tag, id = id\n            )\n        }\n\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    var sortMenuExpanded by remember { mutableStateOf(false) }\n    var pagerState: PagerState = rememberPagerState(pageCount = { 0 })\n    val scope = rememberCoroutineScope()\n    var refreshState by remember { mutableStateOf(false) }\n    var sortType by rememberSaveable { mutableStateOf(ProductSortType.REPLY) }\n    var isScrollingUp by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        text = (viewModel.topicState as? LoadingState.Success)?.response?.title\n                            ?: EMPTY_STRING,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                },\n                actions = {\n                    if (viewModel.topicState is LoadingState.Success) {\n                        Row(Modifier.wrapContentSize(Alignment.TopEnd)) {\n                            IconButton(\n                                onClick = {\n                                    onSearch(\n                                        viewModel.title,\n                                        if (viewModel.entityType == \"topic\") \"tag\" else \"product_phone\",\n                                        if (viewModel.entityType == \"topic\") viewModel.title else viewModel.id.orEmpty()\n                                    )\n                                }\n                            ) {\n                                Icon(Icons.Default.Search, contentDescription = null)\n                            }\n                            Box {\n                                IconButton(onClick = { dropdownMenuExpanded = true }) {\n                                    Icon(\n                                        Icons.Default.MoreVert,\n                                        contentDescription = null\n                                    )\n                                }\n                                DropdownMenu(\n                                    expanded = dropdownMenuExpanded,\n                                    onDismissRequest = { dropdownMenuExpanded = false }\n                                ) {\n                                    if (viewModel.entityType == \"product\"\n                                        && viewModel.tabList?.getOrNull(pagerState.currentPage)?.title == \"讨论\"\n                                    ) {\n                                        DropdownMenuItem(\n                                            text = { Text(\"Sort\") },\n                                            onClick = {\n                                                dropdownMenuExpanded = false\n                                                sortMenuExpanded = true\n                                            },\n                                            trailingIcon = {\n                                                Icon(\n                                                    imageVector = Icons.AutoMirrored.Filled.ArrowRight,\n                                                    contentDescription = null\n                                                )\n                                            }\n                                        )\n                                    }\n                                    if (isLogin) {\n                                        DropdownMenuItem(\n                                            text = {\n                                                Text(\n                                                    if (viewModel.isFollowed) \"UnFollow\"\n                                                    else \"Follow\"\n                                                )\n                                            },\n                                            onClick = {\n                                                dropdownMenuExpanded = false\n                                                if (viewModel.entityType == \"topic\")\n                                                    viewModel.onGetFollow()\n                                                else\n                                                    viewModel.onPostFollow()\n                                            }\n                                        )\n                                    }\n                                    DropdownMenuItem(\n                                        text = {\n                                            Text(\n                                                if (viewModel.isBlocked) \"UnBlock\"\n                                                else \"Block\"\n                                            )\n                                        },\n                                        onClick = {\n                                            dropdownMenuExpanded = false\n                                            viewModel.blockTopic()\n                                        }\n                                    )\n                                }\n                                DropdownMenu(\n                                    expanded = sortMenuExpanded,\n                                    onDismissRequest = { sortMenuExpanded = false }\n                                ) {\n                                    ProductSortType.entries.forEach { sort ->\n                                        Row(\n                                            modifier = Modifier\n                                                .clickable {\n                                                    sortMenuExpanded = false\n                                                    sortType = sort\n                                                },\n                                            verticalAlignment = Alignment.CenterVertically\n                                        ) {\n                                            RadioButton(\n                                                selected = sort == sortType,\n                                                onClick = {\n                                                    sortMenuExpanded = false\n                                                    sortType = sort\n                                                }\n                                            )\n                                            Text(\n                                                text = sort.name,\n                                                style = MaterialTheme.typography.bodyMedium,\n                                                modifier = Modifier\n                                                    .fillMaxWidth()\n                                                    .padding(end = 16.dp)\n                                            )\n                                        }\n                                    }\n                                }\n                            }\n\n                        }\n                    }\n                },\n                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),\n                scrollBehavior = scrollBehavior\n            )\n        },\n        floatingActionButton = {\n            if (isLogin) {\n                AnimatedVisibility(\n                    visible = isScrollingUp,\n                    enter = slideInVertically { it * 2 },\n                    exit = slideOutVertically { it * 2 }\n                ) {\n                    FloatingActionButton(\n                        onClick = {\n                            val intent = Intent(context, ReplyActivity::class.java)\n                            intent.putExtra(\"type\", \"createFeed\")\n                            intent.putExtra(\n                                \"targetType\",\n                                if (viewModel.entityType == \"topic\") \"tag\" else \"product_phone\"\n                            )\n                            intent.putExtra(\"targetId\", viewModel.id)\n                            if (viewModel.entityType == \"topic\")\n                                intent.putExtra(\"title\", viewModel.title)\n                            val animationBundle = ActivityOptionsCompat.makeCustomAnimation(\n                                context,\n                                R.anim.anim_bottom_sheet_slide_up,\n                                R.anim.anim_bottom_sheet_slide_down\n                            ).toBundle()\n                            ContextCompat.startActivity(context, intent, animationBundle)\n                        }\n                    ) {\n                        Icon(\n                            painter = rememberDrawablePainter(\n                                ResourcesCompat.getDrawable(\n                                    context.resources,\n                                    R.drawable.outline_note_alt_24,\n                                    context.theme\n                                )\n                            ),\n                            contentDescription = null\n                        )\n                    }\n                }\n            }\n        }\n    ) { paddingValues ->\n\n        Column(modifier = Modifier.padding(top = paddingValues.calculateTopPadding())) {\n            when (viewModel.topicState) {\n                LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                    Box(modifier = Modifier.fillMaxSize()) {\n                        LoadingCard(\n                            modifier = Modifier\n                                .align(Alignment.Center)\n                                .padding(horizontal = 10.dp),\n                            state = viewModel.topicState,\n                            onClick = if (viewModel.topicState is LoadingState.Loading) null\n                            else viewModel::refresh\n                        )\n                    }\n                }\n\n                is LoadingState.Success -> {\n\n                    viewModel.tabList?.let { tabList ->\n                        val initialPage =\n                            with(tabList.map { it.pageName }.indexOf(viewModel.selectedTab)) {\n                                if (this == -1) 0 else this\n                            }\n\n                        pagerState = rememberPagerState(\n                            initialPage = if (initialPage == -1) 0 else initialPage,\n                            pageCount = { tabList.size }\n                        )\n\n                        SecondaryScrollableTabRow(\n                            modifier = Modifier.padding(\n                                start = paddingValues.calculateLeftPadding(layoutDirection),\n                            ),\n                            selectedTabIndex = pagerState.currentPage,\n                            indicator = {\n                                TabRowDefaults.SecondaryIndicator(\n                                    Modifier\n                                        .tabIndicatorOffset(\n                                            pagerState.currentPage,\n                                            matchContentSize = true\n                                        )\n                                        .clip(RoundedCornerShape(topStart = 3.dp, topEnd = 3.dp))\n                                )\n                            },\n                            divider = {}\n                        ) {\n                            tabList.forEachIndexed { index, tab ->\n                                Tab(\n                                    selected = pagerState.currentPage == index,\n                                    onClick = {\n                                        if (pagerState.currentPage == index) {\n                                            refreshState = true\n                                        }\n                                        scope.launch { pagerState.animateScrollToPage(index) }\n                                    },\n                                    text = { Text(text = tab.title.orEmpty()) }\n                                )\n                            }\n                        }\n\n                        HorizontalDivider()\n\n                        HorizontalPager(\n                            state = pagerState\n                        ) { index ->\n                            TopicContentScreen(\n                                refreshState = refreshState,\n                                resetRefreshState = {\n                                    refreshState = false\n                                },\n                                paddingValues = PaddingValues(\n                                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                                    bottom = paddingValues.calculateBottomPadding(),\n                                ),\n                                entityType = viewModel.entityType,\n                                id = viewModel.id,\n                                url = tabList[index].url.orEmpty(),\n                                title = tabList[index].title.orEmpty(),\n                                sortType = sortType,\n                                onViewUser = onViewUser,\n                                onViewFeed = onViewFeed,\n                                onOpenLink = onOpenLink,\n                                onCopyText = onCopyText,\n                                onReport = onReport,\n                                isScrollingUp = {\n                                    isScrollingUp = it\n                                }\n                            )\n                        }\n\n                    }\n\n                }\n            }\n        }\n\n    }\n\n    viewModel.toastText?.let {\n        context.makeToast(it)\n        viewModel.resetToastText()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/topic/TopicViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.topic\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n@HiltViewModel(assistedFactory = TopicViewModel.ViewModelFactory::class)\nclass TopicViewModel @AssistedInject constructor(\n    @Assisted(\"url\") val url: String,\n    @Assisted(\"tag\") val tag: String?,\n    @Assisted(\"id\") var id: String?,\n    private val networkRepo: NetworkRepo,\n    private val blackListRepo: BlackListRepo,\n) : ViewModel() {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(\n            @Assisted(\"url\") url: String,\n            @Assisted(\"tag\") tag: String?,\n            @Assisted(\"id\") id: String?,\n        ): TopicViewModel\n    }\n\n    var topicState by mutableStateOf<LoadingState<HomeFeedResponse.Data>>(LoadingState.Loading)\n        private set\n\n    init {\n        fetchTopicLayout()\n    }\n\n    lateinit var entityType: String\n    lateinit var title: String\n    var selectedTab: String? = null\n    var tabList: List<HomeFeedResponse.TabList>? = null\n\n    var isFollowed by mutableStateOf(false)\n    var isBlocked by mutableStateOf(false)\n        private set\n\n    private fun fetchTopicLayout() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getTopicLayout(url, tag, id)\n                .collect { state ->\n                    if (state is LoadingState.Success) {\n                        val response = state.response\n                        id = response.id\n                        entityType = response.entityType.orEmpty()\n                        title = response.title.orEmpty()\n                        tabList = response.tabList\n                        selectedTab = response.selectedTab\n                        isFollowed = response.userAction?.follow == 1\n                        checkIsBlocked(title)\n                    }\n                    topicState = state\n                }\n        }\n    }\n\n    fun refresh() {\n        topicState = LoadingState.Loading\n        fetchTopicLayout()\n    }\n\n    var toastText by mutableStateOf<String?>(null)\n        private set\n\n    fun resetToastText() {\n        toastText = null\n    }\n\n    fun onGetFollow() {\n        val followUrl = if (isFollowed) \"/v6/feed/unFollowTag\"\n        else \"/v6/feed/followTag\"\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getFollow(followUrl, title, null)\n                .collect { result ->\n                    val response = result.getOrNull()\n                    if (response != null) {\n                        if (!response.message.isNullOrEmpty()) {\n                            if (response.message.contains(\"关注成功\"))\n                                isFollowed = !isFollowed\n                            toastText = response.message\n                        }\n                    } else {\n                        toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                        result.exceptionOrNull()?.printStackTrace()\n                    }\n                }\n        }\n    }\n\n    private var postFollowData: HashMap<String, String>? = null\n    fun onPostFollow() {\n        if (postFollowData.isNullOrEmpty()) postFollowData = HashMap()\n        postFollowData?.let { map ->\n            map[\"id\"] = id.orEmpty()\n            map[\"status\"] = if (isFollowed) \"0\" else \"1\"\n        }\n        viewModelScope.launch(Dispatchers.IO) {\n            postFollowData?.let {\n                networkRepo.postFollow(it)\n                    .collect { result ->\n                        val response = result.getOrNull()\n                        if (response != null) {\n                            if (!response.message.isNullOrEmpty()) {\n                                if (response.message.contains(\"成功\"))\n                                    isFollowed = !isFollowed\n                                toastText = response.message\n                            }\n                        } else {\n                            toastText = result.exceptionOrNull()?.message ?: \"response is null\"\n                            result.exceptionOrNull()?.printStackTrace()\n                        }\n                    }\n            }\n\n        }\n    }\n\n    fun blockTopic() {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (isBlocked)\n                blackListRepo.deleteTopic(title)\n            else\n                blackListRepo.saveTopic(title)\n            isBlocked = !isBlocked\n        }\n    }\n\n    private fun checkIsBlocked(title: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            isBlocked = blackListRepo.checkTopic(title)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/user/UserScreen.kt",
    "content": "package com.example.c001apk.compose.ui.user\n\nimport androidx.compose.foundation.layout.Arrangement\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.PaddingValues\nimport androidx.compose.foundation.layout.Row\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.foundation.layout.wrapContentSize\nimport androidx.compose.foundation.lazy.LazyColumn\nimport androidx.compose.foundation.lazy.rememberLazyListState\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material.icons.filled.Search\nimport androidx.compose.material3.AlertDialog\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.MaterialTheme\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextButton\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.material3.TopAppBarDefaults\nimport androidx.compose.material3.pulltorefresh.PullToRefreshBox\nimport androidx.compose.material3.pulltorefresh.PullToRefreshDefaults\nimport androidx.compose.material3.pulltorefresh.rememberPullToRefreshState\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Alignment\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.graphics.Color\nimport androidx.compose.ui.input.nestedscroll.nestedScroll\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.res.stringResource\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.compose.ui.unit.dp\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.FooterCard\nimport com.example.c001apk.compose.ui.component.ItemCard\nimport com.example.c001apk.compose.ui.component.cards.LoadingCard\nimport com.example.c001apk.compose.ui.component.cards.UserInfoCard\nimport com.example.c001apk.compose.util.CookieUtil.isLogin\nimport com.example.c001apk.compose.util.DateUtils.timeStamp2Date\nimport com.example.c001apk.compose.util.ReportType\nimport com.example.c001apk.compose.util.ShareType\nimport com.example.c001apk.compose.util.copyText\nimport com.example.c001apk.compose.util.getShareText\nimport com.example.c001apk.compose.util.makeToast\nimport com.example.c001apk.compose.util.shareText\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun UserScreen(\n    uid: String,\n    onBackClick: () -> Unit,\n    onViewUser: (String) -> Unit,\n    onViewFeed: (String, Boolean) -> Unit,\n    onOpenLink: (String, String?) -> Unit,\n    onCopyText: (String?) -> Unit,\n    onSearch: (String, String, String) -> Unit,\n    onViewFFFList: (String, String) -> Unit,\n    onReport: (String, ReportType) -> Unit,\n    onPMUser: (String, String) -> Unit,\n) {\n\n    val layoutDirection = LocalLayoutDirection.current\n\n    val context = LocalContext.current\n    val state = rememberPullToRefreshState()\n    val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()\n    val viewModel =\n        hiltViewModel<UserViewModel, UserViewModel.ViewModelFactory>(key = uid) { factory ->\n            factory.create(uid)\n        }\n    var showUserInfoDialog by remember { mutableStateOf(false) }\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n    val lazyListState = rememberLazyListState()\n    val firstVisibleItemIndex by remember { derivedStateOf { lazyListState.firstVisibleItemIndex } }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        text = if (firstVisibleItemIndex > 0)\n                            (viewModel.userState as? LoadingState.Success)?.response?.username\n                                ?: uid\n                        else EMPTY_STRING,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                },\n                actions = {\n                    if (viewModel.userState is LoadingState.Success) {\n                        Row(Modifier.wrapContentSize(Alignment.TopEnd)) {\n                            IconButton(\n                                onClick = {\n                                    onSearch(viewModel.username, \"user\", viewModel.uid)\n                                }\n                            ) {\n                                Icon(Icons.Default.Search, contentDescription = null)\n                            }\n                            Box {\n                                IconButton(onClick = { dropdownMenuExpanded = true }) {\n                                    Icon(\n                                        Icons.Default.MoreVert,\n                                        contentDescription = null\n                                    )\n                                }\n                                DropdownMenu(\n                                    expanded = dropdownMenuExpanded,\n                                    onDismissRequest = { dropdownMenuExpanded = false }\n                                ) {\n                                    listOf(\"Copy\", \"Share\", \"User Info\")\n                                        .forEachIndexed { index, menu ->\n                                            DropdownMenuItem(\n                                                text = { Text(menu) },\n                                                onClick = {\n                                                    dropdownMenuExpanded = false\n                                                    when (index) {\n                                                        0 -> context.copyText(\n                                                            getShareText(ShareType.USER, uid)\n                                                        )\n\n                                                        1 -> context.shareText(\n                                                            getShareText(ShareType.USER, uid)\n                                                        )\n\n                                                        2 -> showUserInfoDialog = true\n                                                    }\n                                                }\n                                            )\n                                        }\n                                    DropdownMenuItem(\n                                        text = {\n                                            Text(\n                                                if (viewModel.isBlocked) \"UnBlock\" else \"Block\"\n                                            )\n                                        },\n                                        onClick = {\n                                            dropdownMenuExpanded = false\n                                            viewModel.onBlockUser(viewModel.uid)\n                                        }\n                                    )\n                                    if (isLogin) {\n                                        DropdownMenuItem(\n                                            text = { Text(\"Report\") },\n                                            onClick = {\n                                                dropdownMenuExpanded = false\n                                                onReport(uid, ReportType.USER)\n                                            }\n                                        )\n                                    }\n                                }\n                            }\n\n                        }\n                    }\n                },\n                colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.Transparent),\n                scrollBehavior = scrollBehavior\n            )\n        }\n    ) { paddingValues ->\n\n        PullToRefreshBox(\n            modifier = Modifier.padding(\n                start = paddingValues.calculateLeftPadding(layoutDirection),\n            ),\n            state = state,\n            isRefreshing = viewModel.isRefreshing,\n            onRefresh = {\n                viewModel.isPull = true\n                viewModel.refresh()\n            },\n            indicator = {\n                PullToRefreshDefaults.Indicator(\n                    modifier = Modifier.align(Alignment.TopCenter),\n                    isRefreshing = viewModel.isRefreshing,\n                    state = state,\n                    color = MaterialTheme.colorScheme.primary,\n                )\n            }\n        ) {\n            LazyColumn(\n                modifier = Modifier\n                    .fillMaxSize()\n                    .nestedScroll(scrollBehavior.nestedScrollConnection),\n                contentPadding = PaddingValues(bottom = 10.dp + paddingValues.calculateBottomPadding()),\n                verticalArrangement = Arrangement.spacedBy(10.dp),\n                state = lazyListState\n            ) {\n\n                when (viewModel.userState) {\n                    LoadingState.Loading, LoadingState.Empty, is LoadingState.Error -> {\n                        item(key = \"userState\") {\n                            Box(modifier = Modifier.fillParentMaxSize()) {\n                                LoadingCard(\n                                    modifier = Modifier\n                                        .align(Alignment.Center)\n                                        .padding(horizontal = 10.dp),\n                                    state = viewModel.userState,\n                                    onClick = if (viewModel.userState is LoadingState.Loading) null\n                                    else viewModel::refresh\n                                )\n                            }\n                        }\n                    }\n\n                    is LoadingState.Success -> {\n                        item(key = \"userInfo\") {\n                            UserInfoCard(\n                                data = (viewModel.userState as LoadingState.Success).response,\n                                onFollow = viewModel::onFollowUser,\n                                onPMUser = onPMUser,\n                                onViewFFFList = onViewFFFList,\n                            )\n                        }\n                    }\n                }\n\n                if (viewModel.userState is LoadingState.Success) {\n\n                    ItemCard(\n                        loadingState = viewModel.loadingState,\n                        loadMore = viewModel::loadMore,\n                        isEnd = viewModel.isEnd,\n                        onViewUser = { uid ->\n                            if (uid != viewModel.uid) {\n                                onViewUser(uid)\n                            }\n                        },\n                        onViewFeed = onViewFeed,\n                        onOpenLink = onOpenLink,\n                        onCopyText = onCopyText,\n                        onReport = onReport,\n                        onLike = viewModel::onLike,\n                        onDelete = { id, deleteType, _ ->\n                            viewModel.onDelete(id, deleteType)\n                        },\n                        onBlockUser = { uid, _ ->\n                            viewModel.onBlockUser(uid)\n                        },\n                    )\n\n                    FooterCard(\n                        modifier = Modifier.padding(horizontal = 10.dp),\n                        footerState = viewModel.footerState,\n                        loadMore = viewModel::loadMore,\n                    )\n                }\n\n            }\n        }\n\n\n    }\n\n    when {\n        showUserInfoDialog -> {\n            val data = (viewModel.userState as LoadingState.Success).response\n            AlertDialog(\n                onDismissRequest = { showUserInfoDialog = false },\n                confirmButton = {\n                    TextButton(\n                        onClick = { showUserInfoDialog = false }) {\n                        Text(text = stringResource(id = android.R.string.ok))\n                    }\n                },\n                title = {\n                    Text(\n                        modifier = Modifier.fillMaxWidth(),\n                        text = data.username.orEmpty(),\n                    )\n                },\n                text = {\n                    Text(\n                        text = \"\"\"\n                                uid: ${data.uid}\n                                \n                                等级: Lv.${data.level}\n                                \n                                性别: ${if (data.gender == 0) \"女\" else if (data.gender == 1) \"男\" else \"未知\"}\n                                \n                                注册时长: ${((System.currentTimeMillis() / 1000 - (data.regdate ?: 0)) / 24 / 3600)} 天\n                                \n                                注册时间: ${timeStamp2Date(data.regdate ?: 0)}\n                            \"\"\".trimIndent()\n                    )\n                },\n            )\n        }\n    }\n\n    viewModel.toastText?.let {\n        viewModel.resetToastText()\n        context.makeToast(it)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/user/UserViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.user\n\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.setValue\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.model.HomeFeedResponse\nimport com.example.c001apk.compose.logic.repository.BlackListRepo\nimport com.example.c001apk.compose.logic.repository.NetworkRepo\nimport com.example.c001apk.compose.logic.state.LoadingState\nimport com.example.c001apk.compose.ui.base.BaseViewModel\nimport dagger.assisted.Assisted\nimport dagger.assisted.AssistedFactory\nimport dagger.assisted.AssistedInject\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\n@HiltViewModel(assistedFactory = UserViewModel.ViewModelFactory::class)\nclass UserViewModel @AssistedInject constructor(\n    @Assisted var uid: String,\n    networkRepo: NetworkRepo,\n    blackListRepo: BlackListRepo,\n) : BaseViewModel(networkRepo, blackListRepo) {\n\n    @AssistedFactory\n    interface ViewModelFactory {\n        fun create(uid: String): UserViewModel\n    }\n\n    var userState by mutableStateOf<LoadingState<HomeFeedResponse.Data>>(LoadingState.Loading)\n        private set\n\n    init {\n        fetchUserProfile()\n    }\n\n    lateinit var username: String\n    var isBlocked by mutableStateOf(false)\n\n    private fun fetchUserProfile() {\n        viewModelScope.launch(Dispatchers.IO) {\n            networkRepo.getUserSpace(uid)\n                .collect { state ->\n                    userState = state\n                    if (state is LoadingState.Success) {\n                        val response = state.response\n                        uid = response.uid.orEmpty()\n                        username = response.username.orEmpty()\n                        isBlocked = blackListRepo.checkUid(uid)\n                        if (isBlocked)\n                            loadingState = LoadingState.Error(\"$username is blocked\")\n                        else\n                            fetchData()\n                    }\n                    isRefreshing = false\n                }\n        }\n    }\n\n    override suspend fun customFetchData() = networkRepo.getUserFeed(uid, page, lastItem)\n\n    private fun handleBlocked() {\n        viewModelScope.launch {\n            isRefreshing = true\n            delay(50)\n            isRefreshing = false\n        }\n        loadingState = LoadingState.Error(\"$username is blocked\")\n    }\n\n    var isPull = false\n    override fun refresh() {\n        if (!isRefreshing && !isLoadMore) {\n            if (userState is LoadingState.Success) {\n                if (isBlocked) {\n                    handleBlocked()\n                } else {\n                    page = 1\n                    isEnd = false\n                    isLoadMore = false\n                    isRefreshing = true\n                    firstItem = null\n                    lastItem = null\n                    fetchData()\n                }\n            } else {\n                if (isPull) {\n                    isPull = false\n                    viewModelScope.launch {\n                        isRefreshing = true\n                        delay(50)\n                        isRefreshing = false\n                    }\n                }\n                userState = LoadingState.Loading\n                fetchUserProfile()\n            }\n        }\n    }\n\n    override fun loadMore() {\n        if (isBlocked) {\n            handleBlocked()\n        } else {\n            super.loadMore()\n        }\n    }\n\n    override fun handleFollowResponse(follow: Int): Boolean {\n        val response = (userState as LoadingState.Success).response.copy(isFollow = follow)\n        userState = LoadingState.Success(response)\n        return true\n    }\n\n    override fun handleResponse(response: List<HomeFeedResponse.Data>): List<HomeFeedResponse.Data>? {\n        isEnd = response.lastOrNull()?.entityTemplate == \"noMoreDataCard\"\n        return null\n    }\n\n    override fun onBlockUser(uid: String) {\n        viewModelScope.launch(Dispatchers.IO) {\n            if (isBlocked)\n                blackListRepo.deleteUid(uid)\n            else\n                blackListRepo.saveUid(uid)\n            isBlocked = !isBlocked\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/webview/WebViewScreen.kt",
    "content": "package com.example.c001apk.compose.ui.webview\n\nimport androidx.activity.compose.BackHandler\nimport androidx.compose.animation.AnimatedVisibility\nimport androidx.compose.animation.core.animateFloatAsState\nimport androidx.compose.foundation.layout.Box\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.WindowInsets\nimport androidx.compose.foundation.layout.WindowInsetsSides\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.fillMaxWidth\nimport androidx.compose.foundation.layout.only\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.foundation.layout.systemBars\nimport androidx.compose.material.icons.Icons\nimport androidx.compose.material.icons.filled.MoreVert\nimport androidx.compose.material3.DropdownMenu\nimport androidx.compose.material3.DropdownMenuItem\nimport androidx.compose.material3.ExperimentalMaterial3Api\nimport androidx.compose.material3.Icon\nimport androidx.compose.material3.IconButton\nimport androidx.compose.material3.LinearProgressIndicator\nimport androidx.compose.material3.ProgressIndicatorDefaults\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.SnackbarHost\nimport androidx.compose.material3.SnackbarHostState\nimport androidx.compose.material3.SnackbarResult\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TopAppBar\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableFloatStateOf\nimport androidx.compose.runtime.mutableStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.rememberCoroutineScope\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.platform.LocalContext\nimport androidx.compose.ui.platform.LocalLayoutDirection\nimport androidx.compose.ui.text.style.TextOverflow\nimport androidx.hilt.navigation.compose.hiltViewModel\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.WEB_LOGIN_FAILED\nimport com.example.c001apk.compose.logic.providable.LocalUserPreferences\nimport com.example.c001apk.compose.ui.component.BackButton\nimport com.example.c001apk.compose.ui.component.WebView\nimport com.example.c001apk.compose.util.decode\nimport com.example.c001apk.compose.util.makeToast\nimport com.example.c001apk.compose.util.openInBrowser\nimport kotlinx.coroutines.launch\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/9\n */\n\nenum class ActionType {\n    REFRESH, COPY, OPEN, CLEAN, NONE\n}\n\n@OptIn(ExperimentalMaterial3Api::class)\n@Composable\nfun WebViewScreen(\n    onBackClick: () -> Unit,\n    url: String,\n    isLogin: Boolean = false,\n) {\n\n    val viewModel = hiltViewModel<WebViewViewModel>(key = url)\n    val prefs = LocalUserPreferences.current\n    val context = LocalContext.current\n    val layoutDirection = LocalLayoutDirection.current\n    var title by remember { mutableStateOf(EMPTY_STRING) }\n    var dropdownMenuExpanded by remember { mutableStateOf(false) }\n\n    var progress by remember { mutableFloatStateOf(0.0f) }\n    val animatedProgress by animateFloatAsState(\n        targetValue = progress,\n        animationSpec = ProgressIndicatorDefaults.ProgressAnimationSpec, label = EMPTY_STRING\n    )\n    val snackbarHostState = remember(::SnackbarHostState)\n    val scope = rememberCoroutineScope()\n\n    var actionType by remember { mutableStateOf(ActionType.NONE) }\n    var onBack by remember { mutableStateOf(false) }\n\n    Scaffold(\n        modifier = Modifier.fillMaxSize(),\n        topBar = {\n            TopAppBar(\n                windowInsets = WindowInsets.systemBars\n                    .only(WindowInsetsSides.Start + WindowInsetsSides.Top),\n                navigationIcon = {\n                    BackButton { onBackClick() }\n                },\n                title = {\n                    Text(\n                        text = title,\n                        maxLines = 1,\n                        overflow = TextOverflow.Ellipsis\n                    )\n                },\n                actions = {\n                    Box {\n                        IconButton(onClick = { dropdownMenuExpanded = true }) {\n                            Icon(\n                                Icons.Default.MoreVert,\n                                contentDescription = null\n                            )\n                        }\n                        DropdownMenu(\n                            expanded = dropdownMenuExpanded,\n                            onDismissRequest = { dropdownMenuExpanded = false }\n                        ) {\n                            listOf(\"Refresh\", \"Copy\", \"Open in Browser\", \"Clean Caches\")\n                                .forEachIndexed { index, menu ->\n                                    DropdownMenuItem(\n                                        text = { Text(menu) },\n                                        onClick = {\n                                            dropdownMenuExpanded = false\n                                            actionType = when (index) {\n                                                0 -> ActionType.REFRESH\n                                                1 -> ActionType.COPY\n                                                2 -> ActionType.OPEN\n                                                3 -> ActionType.CLEAN\n                                                else -> ActionType.NONE\n                                            }\n                                        }\n                                    )\n                                }\n\n                        }\n                    }\n                },\n            )\n        },\n        snackbarHost = { SnackbarHost(snackbarHostState) },\n    ) { paddingValues ->\n\n        Column(\n            modifier = Modifier\n                .fillMaxSize()\n                .padding(\n                    top = paddingValues.calculateTopPadding(),\n                    bottom = paddingValues.calculateBottomPadding(),\n                    start = paddingValues.calculateLeftPadding(layoutDirection),\n                )\n        ) {\n            AnimatedVisibility(visible = progress != 1.0f) {\n                LinearProgressIndicator(\n                    modifier = Modifier.fillMaxWidth(),\n                    progress = { animatedProgress }\n                )\n            }\n\n            WebView(\n                url = url.decode,\n                isLogin = isLogin,\n                onFinishLogin = { cookie ->\n                    if (cookie.isNotEmpty()) {\n                        val split = cookie.split(\";\")\n                        val uid =\n                            split.find { it.contains(\"uid=\") }?.replace(\"uid=\", EMPTY_STRING)\n                                ?.trim()\n                        val username =\n                            split.find { it.contains(\"username=\") }\n                                ?.replace(\"username=\", EMPTY_STRING)?.trim()\n                        val token =\n                            split.find { it.contains(\"token=\") }?.replace(\"token=\", EMPTY_STRING)\n                                ?.trim()\n                        if (!uid.isNullOrEmpty() && !username.isNullOrEmpty() && !token.isNullOrEmpty()) {\n\n                            viewModel.setIsLogin(uid, username, token)\n\n                        } else {\n                            context.makeToast(WEB_LOGIN_FAILED)\n                        }\n                    } else {\n                        context.makeToast(WEB_LOGIN_FAILED)\n                    }\n                },\n                actionType = actionType,\n                resetActionType = {\n                    actionType = ActionType.NONE\n                },\n                onFinish = onBackClick,\n                onBack = onBack,\n                onBackReset = {\n                    onBack = false\n                },\n                onUpdateProgress = { progress = it },\n                onUpdateTitle = { title = it },\n                onShowSnackbar = { requestUrl ->\n                    scope.launch {\n                        val result = snackbarHostState.showSnackbar(\n                            message = \"当前网页将要打开外部链接，是否打开\",\n                            actionLabel = \"打开\",\n                            withDismissAction = true\n                        )\n                        when (result) {\n                            SnackbarResult.ActionPerformed -> {\n                                context.openInBrowser(requestUrl)\n                            }\n\n                            SnackbarResult.Dismissed -> {}\n                        }\n                    }\n                }\n            )\n\n        }\n\n    }\n\n    if (isLogin && prefs.isLogin) {\n        onBackClick()\n    }\n\n    BackHandler {\n        onBack = true\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/ui/webview/WebViewViewModel.kt",
    "content": "package com.example.c001apk.compose.ui.webview\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewModelScope\nimport com.example.c001apk.compose.logic.repository.UserPreferencesRepository\nimport dagger.hilt.android.lifecycle.HiltViewModel\nimport kotlinx.coroutines.launch\nimport javax.inject.Inject\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/11\n */\n@HiltViewModel\nclass WebViewViewModel @Inject constructor(\n    private val userPreferencesRepository: UserPreferencesRepository\n) : ViewModel() {\n\n    fun setIsLogin(uid: String, username: String, token: String) {\n        viewModelScope.launch {\n            userPreferencesRepository.apply {\n                setUid(uid)\n                setUsername(username)\n                setToken(token)\n                setIsLogin(true)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/AddCookiesInterceptor.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport com.example.c001apk.compose.constant.Constants.APP_ID\nimport com.example.c001apk.compose.constant.Constants.CHANNEL\nimport com.example.c001apk.compose.constant.Constants.DARK_MODE\nimport com.example.c001apk.compose.constant.Constants.LOCALE\nimport com.example.c001apk.compose.constant.Constants.MODE\nimport com.example.c001apk.compose.constant.Constants.REQUEST_WITH\nimport com.example.c001apk.compose.util.CookieUtil.SESSID\nimport com.example.c001apk.compose.util.TokenDeviceUtils.getTokenV2\nimport okhttp3.Interceptor\nimport okhttp3.Request\nimport okhttp3.Response\nimport java.io.IOException\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nobject AddCookiesInterceptor : Interceptor {\n\n    @Throws(IOException::class)\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val builder: Request.Builder = chain.request().newBuilder()\n        val deviceCode = CookieUtil.xAppDevice\n        val token = deviceCode.getTokenV2()\n        builder.apply {\n            addHeader(\"User-Agent\", CookieUtil.userAgent)\n            addHeader(\"X-Requested-With\", REQUEST_WITH)\n            addHeader(\"X-Sdk-Int\", CookieUtil.sdkInt)\n            addHeader(\"X-Sdk-Locale\", LOCALE)\n            addHeader(\"X-App-Id\", APP_ID)\n            addHeader(\"X-App-Token\", token)\n            addHeader(\"X-App-Version\", CookieUtil.versionName)\n            addHeader(\"X-App-Code\", CookieUtil.versionCode)\n            addHeader(\"X-Api-Version\", CookieUtil.apiVersion)\n            addHeader(\"X-App-Device\", deviceCode)\n            addHeader(\"X-Dark-Mode\", DARK_MODE)\n            addHeader(\"X-App-Channel\", CHANNEL)\n            addHeader(\"X-App-Mode\", MODE)\n            addHeader(\"X-App-Supported\", CookieUtil.versionCode)\n            addHeader(\"Content-Type\", \"application/x-www-form-urlencoded\")\n            if (CookieUtil.isLogin)\n                addHeader(\n                    \"Cookie\",\n                    \"uid=${CookieUtil.uid}; username=${CookieUtil.username}; token=${CookieUtil.token}\"\n                )\n            else addHeader(\"Cookie\", SESSID)\n        }\n        return chain.proceed(builder.build())\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/Base64Utils.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport java.io.UnsupportedEncodingException\nimport java.nio.charset.StandardCharsets\n\nobject Base64Utils {\n    private val base64EncodeChars = charArrayOf(\n        'A',\n        'B',\n        'C',\n        'D',\n        'E',\n        'F',\n        'G',\n        'H',\n        'I',\n        'J',\n        'K',\n        'L',\n        'M',\n        'N',\n        'O',\n        'P',\n        'Q',\n        'R',\n        'S',\n        'T',\n        'U',\n        'V',\n        'W',\n        'X',\n        'Y',\n        'Z',\n        'a',\n        'b',\n        'c',\n        'd',\n        'e',\n        'f',\n        'g',\n        'h',\n        'i',\n        'j',\n        'k',\n        'l',\n        'm',\n        'n',\n        'o',\n        'p',\n        'q',\n        'r',\n        's',\n        't',\n        'u',\n        'v',\n        'w',\n        'x',\n        'y',\n        'z',\n        '0',\n        '1',\n        '2',\n        '3',\n        '4',\n        '5',\n        '6',\n        '7',\n        '8',\n        '9',\n        '+',\n        '/'\n    )\n    private val base64DecodeChars = byteArrayOf(\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        62,\n        -1,\n        -1,\n        -1,\n        63,\n        52,\n        53,\n        54,\n        55,\n        56,\n        57,\n        58,\n        59,\n        60,\n        61,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        0,\n        1,\n        2,\n        3,\n        4,\n        5,\n        6,\n        7,\n        8,\n        9,\n        10,\n        11,\n        12,\n        13,\n        14,\n        15,\n        16,\n        17,\n        18,\n        19,\n        20,\n        21,\n        22,\n        23,\n        24,\n        25,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1,\n        26,\n        27,\n        28,\n        29,\n        30,\n        31,\n        32,\n        33,\n        34,\n        35,\n        36,\n        37,\n        38,\n        39,\n        40,\n        41,\n        42,\n        43,\n        44,\n        45,\n        46,\n        47,\n        48,\n        49,\n        50,\n        51,\n        -1,\n        -1,\n        -1,\n        -1,\n        -1\n    )\n\n    /**\n     * 加密\n     *\n     * @param data\n     * @return\n     */\n    fun encode(data: ByteArray): String {\n        val sb = StringBuffer()\n        val len = data.size\n        var i = 0\n        var b1: Int\n        var b2: Int\n        var b3: Int\n        while (i < len) {\n            b1 = data[i++].toInt() and 0xff\n            if (i == len) {\n                sb.append(base64EncodeChars[b1 ushr 2])\n                sb.append(base64EncodeChars[b1 and 0x3 shl 4])\n                sb.append(\"==\")\n                break\n            }\n            b2 = data[i++].toInt() and 0xff\n            if (i == len) {\n                sb.append(base64EncodeChars[b1 ushr 2])\n                sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0 ushr 4)])\n                sb.append(base64EncodeChars[b2 and 0x0f shl 2])\n                sb.append(\"=\")\n                break\n            }\n            b3 = data[i++].toInt() and 0xff\n            sb.append(base64EncodeChars[b1 ushr 2])\n            sb.append(base64EncodeChars[b1 and 0x03 shl 4 or (b2 and 0xf0 ushr 4)])\n            sb.append(base64EncodeChars[b2 and 0x0f shl 2 or (b3 and 0xc0 ushr 6)])\n            sb.append(base64EncodeChars[b3 and 0x3f])\n        }\n        return sb.toString()\n    }\n\n    /**\n     * 解密\n     *\n     * @param str\n     * @return\n     */\n    fun decode(str: String): ByteArray {\n        try {\n            return decodePrivate(str)\n        } catch (e: UnsupportedEncodingException) {\n            e.printStackTrace()\n        }\n        return byteArrayOf()\n    }\n\n    @Throws(UnsupportedEncodingException::class)\n    private fun decodePrivate(str: String): ByteArray {\n        val sb = StringBuffer()\n        val data: ByteArray?\n        data = str.toByteArray(StandardCharsets.US_ASCII)\n        val len = data.size\n        var i = 0\n        var b1: Int\n        var b2: Int\n        var b3: Int\n        var b4: Int\n        while (i < len) {\n            do {\n                b1 = base64DecodeChars[data[i++].toInt()].toInt()\n            } while (i < len && b1 == -1)\n            if (b1 == -1) break\n            do {\n                b2 = base64DecodeChars[data[i++].toInt()].toInt()\n            } while (i < len && b2 == -1)\n            if (b2 == -1) break\n            sb.append((b1 shl 2 or (b2 and 0x30 ushr 4)).toChar())\n            do {\n                b3 = data[i++].toInt()\n                if (b3 == 61) return sb.toString().toByteArray(charset(\"iso8859-1\"))\n                b3 = base64DecodeChars[b3].toInt()\n            } while (i < len && b3 == -1)\n            if (b3 == -1) break\n            sb.append((b2 and 0x0f shl 4 or (b3 and 0x3c ushr 2)).toChar())\n            do {\n                b4 = data[i++].toInt()\n                if (b4 == 61) return sb.toString().toByteArray(charset(\"iso8859-1\"))\n                b4 = base64DecodeChars[b4].toInt()\n            } while (i < len && b4 == -1)\n            if (b4 == -1) break\n            sb.append((b3 and 0x03 shl 6 or b4).toChar())\n        }\n        return sb.toString().toByteArray(charset(\"iso8859-1\"))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/CacheDataManager.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.Context\nimport android.os.Environment\nimport coil.Coil\nimport coil.annotation.ExperimentalCoilApi\nimport java.io.File\nimport java.text.DecimalFormat\n\nobject CacheDataManager {\n    /**\n     * 获取整体缓存大小\n     *\n     * @param context\n     * @return\n     * @throws Exception\n     */\n    @Throws(Exception::class)\n    fun getTotalCacheSize(context: Context): String {\n        var cacheSize = getFolderSize(context.cacheDir)\n        if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {\n            cacheSize += getFolderSize(context.externalCacheDir)\n        }\n        return getFormatSize(cacheSize)\n    }\n\n    /**\n     * 获取文件\n     * Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/ 目录，一般放一些长时间保存的数据\n     * Context.getExternalCacheDir() --> SDCard/Android/data/你的应用包名/cache/目录，一般存放临时缓存数据\n     *\n     * @param file\n     * @return\n     * @throws Exception\n     */\n    @Throws(Exception::class)\n    fun getFolderSize(file: File?): Long {\n        var size: Long = 0\n        try {\n            file?.listFiles()?.forEach { value ->\n                // 如果下面还有文件\n                size = if (value.isDirectory()) {\n                    size + getFolderSize(value)\n                } else {\n                    size + value.length()\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        return size\n    }\n\n    /**\n     * 格式化单位\n     *\n     * @param size\n     */\n    private fun getFormatSize(size: Long): String {\n        val k = 1.shl(10)\n        val m = 1.shl(20)\n        val g = 1.shl(30)\n        val df = DecimalFormat(\"0.00\")\n        return if (size >= g) {\n            df.format(size.toFloat() / g) + \"GB\"\n        } else if (size >= m) {\n            df.format(size.toFloat() / m) + \"MB\"\n        } else if (size >= k) {\n            df.format(size.toFloat() / k) + \"KB\"\n        } else {\n            \"$size B\"\n        }\n    }\n\n    /**\n     * 清空方法\n     *\n     * @param context\n     */\n    @OptIn(ExperimentalCoilApi::class)\n    fun clearAllCache(context: Context) {\n        deleteDir(context.cacheDir)\n        if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {\n            deleteDir(context.externalCacheDir)\n        }\n        Coil.imageLoader(context).memoryCache?.clear()\n        Coil.imageLoader(context).diskCache?.clear()\n    }\n\n    private fun deleteDir(dir: File?): Boolean {\n        if (dir != null && dir.isDirectory()) {\n            dir.list()?.forEach { child ->\n                val success = deleteDir(File(dir, child))\n                if (!success) {\n                    return false\n                }\n            }\n        }\n        assert(dir != null)\n        return dir?.delete() ?: false\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/CommentHelper.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.Context\nimport android.text.Editable\nimport android.text.Spannable\nimport android.text.SpannableStringBuilder\nimport android.text.TextWatcher\nimport android.text.style.ForegroundColorSpan\nimport android.view.KeyEvent\nimport android.view.View\nimport android.widget.EditText\nimport androidx.appcompat.content.res.AppCompatResources\nimport com.example.c001apk.compose.view.CenteredImageSpan\nimport java.util.regex.Pattern\n\nclass EmojiTextWatcher(\n    private val context: Context,\n    private val size: Float,\n    private val primary: Int,\n    private val onAfterTextChanged: (() -> Unit)? = null,\n) : TextWatcher {\n\n    companion object {\n        private val AT_PATTERN = Pattern.compile(\"@[\\\\w\\\\-._]+\")\n        private val TAG_PATTERN = Pattern.compile(\"#[^# @]+#\")\n        private val EMOJI_PATTERN = Pattern.compile(\"\\\\[[^\\\\]]+\\\\]\")\n    }\n\n    override fun afterTextChanged(editable: Editable) {\n        onAfterTextChanged?.let { it() }\n    }\n\n    override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}\n    override fun onTextChanged(\n        charSequence: CharSequence,\n        start: Int,\n        before: Int,\n        count: Int\n    ) {\n        val spannable = charSequence as Spannable\n        val total = start + count\n        setEmoticonSpan(spannable, EMOJI_PATTERN, start, total)\n        tintPatternColor(spannable, AT_PATTERN, start, total)\n        tintPatternColor(spannable, TAG_PATTERN, start, total)\n    }\n\n    private fun tintPatternColor(spannable: Spannable, pattern: Pattern, start: Int, total: Int) {\n        val region = pattern.matcher(spannable).region(start, total)\n        while (region.find()) {\n            val group = region.group()\n            spannable.setSpan(\n                ForegroundColorSpan(primary),\n                region.start(),\n                region.start() + group.length,\n                33\n            )\n        }\n    }\n\n    private fun setEmoticonSpan(spannable: Spannable, pattern: Pattern, start: Int, total: Int) {\n        val matcher = pattern.matcher(spannable).region(start, total)\n        while (matcher.find()) {\n            val group = matcher.group()\n            EmojiUtils.emojiMap[group]?.let {\n                AppCompatResources.getDrawable(context, it)?.let { emoji ->\n                    if (group in listOf(\"[楼主]\", \"[层主]\", \"[置顶]\"))\n                        emoji.setBounds(0, 0, (size * 2).toInt(), size.toInt())\n                    else\n                        emoji.setBounds(0, 0, (size * 1.4).toInt(), (size * 1.4).toInt())\n                    val imageSpan = CenteredImageSpan(emoji, (size * 1.4).toInt(), group)\n                    spannable.setSpan(\n                        imageSpan,\n                        matcher.start(),\n                        matcher.end(),\n                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE\n                    )\n                }\n\n            }\n        }\n    }\n\n}\n\nclass OnTextInputListener(\n    private val text: String,\n    private val onTextChange: () -> Unit\n) :\n    TextWatcher {\n    override fun afterTextChanged(editable: Editable) {}\n    override fun beforeTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}\n    override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {\n        if (before == 0 && count == text.length\n            && s.subSequence(start, start + count).toString() == text\n        ) {\n            onTextChange()\n        }\n    }\n}\n\nobject FastDeleteAtUserKeyListener : View.OnKeyListener {\n    override fun onKey(view: View, keyCode: Int, keyEvent: KeyEvent): Boolean {\n        val editText = view as EditText\n        if (keyCode == KeyEvent.KEYCODE_DEL && keyEvent.action == KeyEvent.ACTION_DOWN) {\n            if (removeFastDelete(editText)) {\n                return true\n            }\n\n            val text = editText.text\n            val selectionStart = editText.selectionStart\n            if (selectionStart <= 0) {\n                return false\n            }\n            val charAt = text[selectionStart - 1]\n            if (charAt != ' ' && charAt != ':' || selectionStart != editText.selectionEnd) {\n                return false\n            }\n            val lastIndexOfAt = lastIndexOfAt(text, selectionStart)\n            val lastIndexOfTopicStart = lastIndexOfTopicStart(text, selectionStart)\n            if (lastIndexOfAt >= 0 && lastIndexOfAt > lastIndexOfTopicStart) {\n                val cArr = CharArray(selectionStart - lastIndexOfAt)\n                text.getChars(lastIndexOfAt, selectionStart, cArr, 0)\n                if (!AT_PATTERN.matcher(String(cArr)).matches()) {\n                    return false\n                }\n                text.delete(lastIndexOfAt, selectionStart)\n                return true\n            }\n            if (lastIndexOfTopicStart >= 0 && lastIndexOfTopicStart > lastIndexOfAt) {\n                val cArr2 = CharArray(selectionStart - lastIndexOfTopicStart)\n                text.getChars(lastIndexOfTopicStart, selectionStart, cArr2, 0)\n                if (TAG_PATTERN.matcher(String(cArr2)).matches()) {\n                    text.delete(lastIndexOfTopicStart, selectionStart)\n                    return true\n                }\n            }\n        }\n        return false\n    }\n\n    private fun lastIndexOfAt(editable: Editable, i: Int): Int {\n        for (i2 in i - 1 downTo 0) {\n            if (editable[i2] == '@') {\n                return i2\n            }\n        }\n        return -1\n    }\n\n    private fun lastIndexOfTopicStart(editable: Editable, i: Int): Int {\n        var i2 = 0\n        for (i3 in i - 1 downTo 0) {\n            if (editable[i3] == '#') {\n                i2++\n            }\n            if (i2 == 2) {\n                return i3\n            }\n        }\n        return -1\n    }\n\n    private fun removeFastDelete(editText: EditText): Boolean {\n        val spannableStringBuilder = editText.text as SpannableStringBuilder\n        var selectionStart = editText.selectionStart\n        val selectionEnd = editText.selectionEnd\n        if (selectionEnd == selectionStart && selectionStart > 0) {\n            selectionStart--\n        }\n        var z = false\n        for (fastDeleteSpan in\n        spannableStringBuilder.getSpans(\n            selectionStart, selectionEnd, FastDeleteSpan::class.java\n        ) as Array<FastDeleteSpan?>) {\n            val spanStart = spannableStringBuilder.getSpanStart(fastDeleteSpan)\n            val spanEnd = spannableStringBuilder.getSpanEnd(fastDeleteSpan)\n            spannableStringBuilder.delete(spanStart, spanEnd)\n            spannableStringBuilder.removeSpan(fastDeleteSpan)\n            if (spanEnd == selectionEnd) {\n                z = true\n            }\n        }\n        return z\n    }\n\n    val AT_PATTERN = Pattern.compile(\"@[\\\\w\\\\-._]+[\\\\s:]\")\n    val TAG_PATTERN = Pattern.compile(\"#[^# @]+#\\\\s\")\n\n    class FastDeleteSpan\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/CookieUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport com.example.c001apk.compose.constant.Constants.API_VERSION\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.VERSION_CODE\nimport com.example.c001apk.compose.constant.Constants.VERSION_NAME\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nobject CookieUtil {\n\n    var SESSID = EMPTY_STRING\n    var isPreGetLoginParam = false\n    var isGetLoginParam = false\n    var isTryLogin = false\n    var isGetCaptcha = false\n    var isGetSmsLoginParam = false\n    var isGetSmsToken = false\n\n    var atme: Int? = null\n    var atcommentme: Int? = null\n    var feedlike: Int? = null\n    var contacts_follow: Int? = null\n    var message: Int? = null\n    var notification: Int = 0\n\n    var isLogin = false\n    var szlmId = EMPTY_STRING\n    var xAppDevice = EMPTY_STRING\n    var uid = EMPTY_STRING\n    var username = EMPTY_STRING\n    var token = EMPTY_STRING\n    var userAgent = EMPTY_STRING\n    var sdkInt = EMPTY_STRING\n    var versionName = VERSION_NAME\n    var versionCode = VERSION_CODE\n    var apiVersion = API_VERSION\n    var imageQuality = 0\n    var showEmoji = true\n    var imageFilter = true\n    var isDarkMode = false\n    var showSquare = true\n    var openInBrowser = false\n    var recordHistory = true\n    var materialYou = true\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/DateUtils.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.annotation.SuppressLint\nimport java.text.SimpleDateFormat\nimport java.util.Calendar\nimport java.util.Date\n\nobject DateUtils {\n    private const val ONE_MINUTE: Long = 60\n    private const val ONE_HOUR: Long = 3600\n    private const val ONE_DAY: Long = 86400\n    private const val ONE_MONTH: Long = 2592000\n    private const val ONE_YEAR: Long = 31104000\n    private var calendar: Calendar = Calendar.getInstance()\n    val date: String\n        /**\n         * @return yyyy-mm-dd\n         * 2012-12-25\n         */\n        get() = \"$year-$month-$day\"\n\n    /**\n     * @param format\n     * @return yyyy年MM月dd HH:mm\n     * MM-dd HH:mm 2012-12-25\n     */\n    @SuppressLint(\"SimpleDateFormat\")\n    fun getDate(format: String?): String {\n        val simple = SimpleDateFormat(format)\n        return simple.format(calendar.time)\n    }\n\n    val dateAndMinute: String\n        /**\n         * @return yyyy-MM-dd HH:mm\n         * 2012-12-29 23:47\n         */\n        @SuppressLint(\"SimpleDateFormat\")\n        get() {\n            val simple = SimpleDateFormat(\"yyyy-MM-dd HH:mm\")\n            return simple.format(calendar.time)\n        }\n    val fullDate: String\n        /**\n         * @return yyyy-MM-dd HH:mm:ss\n         * 2012-12-29 23:47:36\n         */\n        @SuppressLint(\"SimpleDateFormat\")\n        get() {\n            val simple = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\")\n            return simple.format(calendar.time)\n        }\n\n    /**\n     * 距离今天多久\n     *\n     * @param time\n     * @return\n     */\n    @JvmStatic\n    @Suppress(\"DEPRECATION\")\n    @SuppressLint(\"SimpleDateFormat\")\n    fun fromToday(time: Long): String {\n        //Calendar calendar = Calendar.getInstance();\n        //calendar.setTime(date);\n\n        //long time = date.getTime() / 1000;\n        val now = Date().time / 1000\n        val ago = now - time\n        return if (ago == 0L) {\n            \"刚刚\"\n        } else if (ago < 60) {\n            ago.toString() + \"秒前\"\n        } else if (ago <= ONE_HOUR) (ago / ONE_MINUTE).toString() + \"分钟前\" else if (ago <= ONE_DAY) (ago / ONE_HOUR).toString() + \"小时前\" //+ (ago % ONE_HOUR / ONE_MINUTE) + \"分钟前\";\n        else if (ago <= ONE_DAY * 2) \"1天前\" //+ calendar.get(Calendar.HOUR_OF_DAY) + \":\" + calendar.get(Calendar.MINUTE) + \":\" + calendar.get(Calendar.SECOND);\n        else if (ago <= ONE_DAY * 3) \"2天前\" //+ calendar.get(Calendar.HOUR_OF_DAY) + \":\" + calendar.get(Calendar.MINUTE) + \":\" + calendar.get(Calendar.SECOND);\n        else if (ago <= ONE_MONTH) {\n            val day = ago / ONE_DAY\n            day.toString() + \"天前\" //+ calendar.get(Calendar.HOUR_OF_DAY) + \":\" + calendar.get(Calendar.MINUTE) + \":\" + calendar.get(Calendar.SECOND);\n        } else {\n            val date = (time * 1000).toString().toLong()\n            val sdf: SimpleDateFormat = if (Date(time * 1000).year == Date().year) {\n                SimpleDateFormat(\"M月d日\")\n                /*long month = ago / ONE_MONTH;\n                long day = ago % ONE_MONTH / ONE_DAY;\n                return month + \"个月\" + day + \"天前\"\n                        + calendar.get(Calendar.HOUR_OF_DAY) + \"点\"\n                        + calendar.get(Calendar.MINUTE) + \"分\";*/\n            } else {\n                SimpleDateFormat(\"yyyy年M月d日\")\n                /* long year = ago / ONE_YEAR;\n                int month = calendar.get(Calendar.MONTH) + 1;// JANUARY which is 0 so month+1\n                return year + \"年前\" + month + \"月\" + calendar.get(Calendar.DATE)\n                        + \"日\";*/\n            }\n            sdf.format(Date(date))\n        }\n    }\n\n    @SuppressLint(\"SimpleDateFormat\")\n    fun getDate(time: Long): String {\n        val date = (time * 1000).toString().toLong()\n        val sdf: SimpleDateFormat = SimpleDateFormat(\"yyyy-MM-dd\")\n        return sdf.format(Date(date))\n    }\n\n    /**\n     * 距离截止日期还有多长时间\n     *\n     * @param date\n     * @return\n     */\n    fun fromDeadline(date: Date): String {\n        val deadline = date.time / 1000\n        val now = Date().time / 1000\n        val remain = deadline - now\n        return if (remain <= ONE_HOUR) \"只剩下\" + remain / ONE_MINUTE + \"分钟\" else if (remain <= ONE_DAY) \"只剩下\" + remain / ONE_HOUR + \"小时\" + remain % ONE_HOUR / ONE_MINUTE + \"分钟\" else {\n            val day = remain / ONE_DAY\n            val hour =\n                remain % ONE_DAY / ONE_HOUR\n            val minute =\n                remain % ONE_DAY % ONE_HOUR / ONE_MINUTE\n            \"只剩下\" + day + \"天\" + hour + \"小时\" + minute + \"分钟\"\n        }\n    }\n\n    /**\n     * 距离今天的绝对时间\n     *\n     * @param date\n     * @return\n     */\n    fun toToday(date: Date): String {\n        val time = date.time / 1000\n        val now = Date().time / 1000\n        val ago = now - time\n        return if (ago <= ONE_HOUR) (ago / ONE_MINUTE).toString() + \"分钟\" else if (ago <= ONE_DAY) (ago / ONE_HOUR).toString() + \"小时\" + ago % ONE_HOUR / ONE_MINUTE + \"分钟\" else if (ago <= ONE_DAY * 2) \"昨天\" + (ago - ONE_DAY) / ONE_HOUR + \"点\" + ((ago - ONE_DAY)\n                % ONE_HOUR / ONE_MINUTE) + \"分\" else if (ago <= ONE_DAY * 3) {\n            val hour = ago - ONE_DAY * 2\n            \"前天\" + hour / ONE_HOUR + \"点\" + hour % ONE_HOUR / ONE_MINUTE + \"分\"\n        } else if (ago <= ONE_MONTH) {\n            val day = ago / ONE_DAY\n            val hour =\n                ago % ONE_DAY / ONE_HOUR\n            val minute =\n                ago % ONE_DAY % ONE_HOUR / ONE_MINUTE\n            day.toString() + \"天前\" + hour + \"点\" + minute + \"分\"\n        } else if (ago <= ONE_YEAR) {\n            val month = ago / ONE_MONTH\n            val day =\n                ago % ONE_MONTH / ONE_DAY\n            val hour =\n                ago % ONE_MONTH % ONE_DAY / ONE_HOUR\n            val minute =\n                ago % ONE_MONTH % ONE_DAY % ONE_HOUR / ONE_MINUTE\n            month.toString() + \"个月\" + day + \"天\" + hour + \"点\" + minute + \"分前\"\n        } else {\n            val year = ago / ONE_YEAR\n            val month =\n                ago % ONE_YEAR / ONE_MONTH\n            val day =\n                ago % ONE_YEAR % ONE_MONTH / ONE_DAY\n            year.toString() + \"年前\" + month + \"月\" + day + \"天\"\n        }\n    }\n\n    private val year: String\n        get() = calendar[Calendar.YEAR].toString()\n    private val month: String\n        get() {\n            val month = calendar[Calendar.MONTH] + 1\n            return month.toString()\n        }\n    private val day: String\n        get() = calendar[Calendar.DATE].toString()\n\n    fun get24Hour(): String {\n        return calendar[Calendar.HOUR_OF_DAY].toString()\n    }\n\n    val minute: String\n        get() = calendar[Calendar.MINUTE].toString()\n    val second: String\n        get() = calendar[Calendar.SECOND].toString()\n\n    @SuppressLint(\"SimpleDateFormat\")\n    fun timeStamp2Date(time: Long): String {\n        val format = SimpleDateFormat(\"yyyy-MM-dd HH:mm:ss\")\n        return format.format(time * 1000)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/EmojiUtils.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport com.example.c001apk.compose.R\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\nobject EmojiUtils {\n\n    val emojiMap: LinkedHashMap<String, Int> = LinkedHashMap()\n\n    val dataList by lazy { emojiMap.toList() }\n    val emojiList = ArrayList<List<Pair<String, Int>>>()\n    val coolBList = ArrayList<List<Pair<String, Int>>>()\n\n    init {\n        emojiMap[\"[置顶]\"] = R.drawable.ic_feed_top\n        emojiMap[\"[楼主]\"] = R.drawable.ic_author\n        emojiMap[\"[层主]\"] = R.drawable.ic_subauthor\n        emojiMap[\"[图片]\"] = R.drawable.ic_photo\n        emojiMap[\"[哈哈哈]\"] = R.drawable.coolapk_emotion_1_hahaha\n        emojiMap[\"[惊讶]\"] = R.drawable.coolapk_emotion_2_jingya\n        emojiMap[\"[呲牙]\"] = R.drawable.coolapk_emotion_3_ciya\n        emojiMap[\"[流泪]\"] = R.drawable.coolapk_emotion_4_liulei\n        emojiMap[\"[可爱]\"] = R.drawable.coolapk_emotion_5_keai\n        emojiMap[\"[微笑]\"] = R.drawable.coolapk_emotion_6_weixiao\n        emojiMap[\"[呵呵]\"] = R.drawable.coolapk_emotion_7_hehe\n        emojiMap[\"[撇嘴]\"] = R.drawable.coolapk_emotion_8_piezui\n        emojiMap[\"[色]\"] = R.drawable.coolapk_emotion_9_se\n        emojiMap[\"[傲慢]\"] = R.drawable.coolapk_emotion_10_aoman\n        emojiMap[\"[疑问]\"] = R.drawable.coolapk_emotion_11_yiwen\n        emojiMap[\"[无语]\"] = R.drawable.coolapk_emotion_12_wuyu\n        emojiMap[\"[坏笑]\"] = R.drawable.coolapk_emotion_13_huaixiao\n        emojiMap[\"[鄙视]\"] = R.drawable.coolapk_emotion_14_bishi\n        emojiMap[\"[发怒]\"] = R.drawable.coolapk_emotion_15_fanu\n        emojiMap[\"[爆怒]\"] = R.drawable.coolapk_emotion_104\n        emojiMap[\"[托腮]\"] = R.drawable.coolapk_emotion_16_tuosai\n        emojiMap[\"[吐舌]\"] = R.drawable.coolapk_emotion_17_tushe\n        emojiMap[\"[汗]\"] = R.drawable.coolapk_emotion_18_han\n        emojiMap[\"[抠鼻]\"] = R.drawable.coolapk_emotion_19_koubi\n        emojiMap[\"[亲亲]\"] = R.drawable.coolapk_emotion_20_qinqin\n        emojiMap[\"[喷血]\"] = R.drawable.coolapk_emotion_21_penxue\n        emojiMap[\"[笑眼]\"] = R.drawable.coolapk_emotion_22_xiaoyan\n        emojiMap[\"[睡]\"] = R.drawable.coolapk_emotion_23_shui\n        emojiMap[\"[捂嘴笑]\"] = R.drawable.coolapk_emotion_24_wuzuixiao\n        emojiMap[\"[再见]\"] = R.drawable.coolapk_emotion_25_zaijian\n        emojiMap[\"[可怜]\"] = R.drawable.coolapk_emotion_26_kelian\n        emojiMap[\"[笑哭]\"] = R.drawable.coolapk_emotion_31_xiaoku\n        emojiMap[\"[强]\"] = R.drawable.coolapk_emotion_27_qiang\n        emojiMap[\"[弱]\"] = R.drawable.coolapk_emotion_28_ruo\n        emojiMap[\"[抱拳]\"] = R.drawable.coolapk_emotion_29_baoquan\n        emojiMap[\"[ok]\"] = R.drawable.coolapk_emotion_30_ok\n        emojiMap[\"[嘿哈]\"] = R.drawable.coolapk_emotion_32_heiha\n        emojiMap[\"[捂脸]\"] = R.drawable.coolapk_emotion_33_wulian\n        emojiMap[\"[机智]\"] = R.drawable.coolapk_emotion_34_jizhi\n        emojiMap[\"[耶]\"] = R.drawable.coolapk_emotion_35_ye\n        emojiMap[\"[我最美]\"] = R.drawable.coolapk_emotion_38_wozuimei\n        emojiMap[\"[酷]\"] = R.drawable.coolapk_emotion_36_ku\n        emojiMap[\"[黑线]\"] = R.drawable.coolapk_emotion_43_heixian\n        emojiMap[\"[喷]\"] = R.drawable.coolapk_emotion_44_pen\n        emojiMap[\"[阴险]\"] = R.drawable.coolapk_emotion_45_yinxian\n        emojiMap[\"[难过]\"] = R.drawable.coolapk_emotion_46_nanguo\n        emojiMap[\"[委屈]\"] = R.drawable.coolapk_emotion_47_weiqu\n        emojiMap[\"[吃瓜]\"] = R.drawable.coolapk_emotion_51_chigua\n        emojiMap[\"[喝酒]\"] = R.drawable.coolapk_emotion_52_hejiu\n        emojiMap[\"[噗]\"] = R.drawable.coolapk_emotion_53_pu\n        emojiMap[\"[微微一笑]\"] = R.drawable.coolapk_emotion_48_weiweiyixiao\n        emojiMap[\"[欢呼]\"] = R.drawable.coolapk_emotion_49_huanhu\n        emojiMap[\"[白眼]\"] = R.drawable.coolapk_emotion_84_baiyan\n        emojiMap[\"[耐克嘴]\"] = R.drawable.coolapk_emotion_81_naikezui\n        emojiMap[\"[t耐克嘴]\"] = R.drawable.coolapk_emotion_105\n        emojiMap[\"[害羞]\"] = R.drawable.coolapk_emotion_97_haixiu\n        emojiMap[\"[无奈]\"] = R.drawable.coolapk_emotion_98_wunai\n        emojiMap[\"[皱眉]\"] = R.drawable.coolapk_emotion_99_zhoumei\n        emojiMap[\"[qqdoge]\"] = R.drawable.coolapk_emotion_100_qqdoge\n        emojiMap[\"[发呆]\"] = R.drawable.coolapk_emotion_102_fadai\n        emojiMap[\"[舒服]\"] = R.drawable.coolapk_emotion_106\n        emojiMap[\"[懒得理]\"] = R.drawable.coolapk_emotion_107\n        emojiMap[\"[不开心]\"] = R.drawable.coolapk_emotion_108\n        emojiMap[\"[挑眉坏笑]\"] = R.drawable.coolapk_emotion_109\n        emojiMap[\"[害怕]\"] = R.drawable.coolapk_emotion_1010\n        emojiMap[\"[哼唧]\"] = R.drawable.coolapk_emotion_1011\n        emojiMap[\"[挨打]\"] = R.drawable.coolapk_emotion_1012\n        emojiMap[\"[假笑]\"] = R.drawable.coolapk_emotion_1014\n        emojiMap[\"[偷看]\"] = R.drawable.coolapk_emotion_1015\n        emojiMap[\"[喝茶]\"] = R.drawable.coolapk_emotion_1016\n        emojiMap[\"[哦吼吼]\"] = R.drawable.coolapk_emotion_1017\n        emojiMap[\"[掩面笑]\"] = R.drawable.coolapk_emotion_1018\n        emojiMap[\"[表面哭泣]\"] = R.drawable.coolapk_emotion_1019\n        emojiMap[\"[表面开心]\"] = R.drawable.coolapk_emotion_1020\n        emojiMap[\"[滑稽]\"] = R.drawable.coolapk_emotion_62_huaji\n        emojiMap[\"[流汗滑稽]\"] = R.drawable.coolapk_emotion_63_liuhanhuaji\n        emojiMap[\"[受虐滑稽]\"] = R.drawable.coolapk_emotion_64_shounuehuaji\n        emojiMap[\"[cos滑稽]\"] = R.drawable.coolapk_emotion_65_coshuaji\n        emojiMap[\"[斗鸡眼滑稽]\"] = R.drawable.coolapk_emotion_66_doujiyanhuaji\n        emojiMap[\"[墨镜滑稽]\"] = R.drawable.coolapk_emotion_67_mojinghuaji\n        emojiMap[\"[小嘴滑稽]\"] = R.drawable.coolapk_emotion_1013\n        emojiMap[\"[针不戳]\"] = R.drawable.coolapk_emotion_1022_zhenbuchuo\n        emojiMap[\"[列文虎克]\"] = R.drawable.coolapk_emotion_1023_liewenhuke\n        emojiMap[\"[真正的音乐]\"] = R.drawable.coolapk_emotion_1024\n        emojiMap[\"[感知不强]\"] = R.drawable.coolapk_emotion_1025\n        emojiMap[\"[给我整一个]\"] = R.drawable.coolapk_emotion_1026\n        emojiMap[\"[yyds]\"] = R.drawable.coolapk_emotion_1027\n        emojiMap[\"[doge]\"] = R.drawable.coolapk_emotion_37_doge\n        emojiMap[\"[doge笑哭]\"] = R.drawable.coolapk_emotion_56_dogexiaoku\n        emojiMap[\"[doge呵斥]\"] = R.drawable.coolapk_emotion_57_dogehechi\n        emojiMap[\"[doge原谅ta]\"] = R.drawable.coolapk_emotion_58_dogeyuanliangta\n        emojiMap[\"[喵喵]\"] = R.drawable.coolapk_emotion_82_miaomiao\n        emojiMap[\"[二哈]\"] = R.drawable.coolapk_emotion_59_erha\n        emojiMap[\"[二哈盯]\"] = R.drawable.coolapk_emotion_95_erhading\n        emojiMap[\"[爱心]\"] = R.drawable.coolapk_emotion_40_aixin\n        emojiMap[\"[心碎]\"] = R.drawable.coolapk_emotion_50_xinsui\n        emojiMap[\"[玫瑰]\"] = R.drawable.coolapk_emotion_41_meigui\n        emojiMap[\"[凋谢]\"] = R.drawable.coolapk_emotion_42_diaoxie\n        emojiMap[\"[菜刀]\"] = R.drawable.coolapk_emotion_39_caidao\n        emojiMap[\"[牛啤]\"] = R.drawable.coolapk_emotion_103_nb\n        emojiMap[\"[py交易]\"] = R.drawable.coolapk_emotion_101_pyjiaoyi\n        emojiMap[\"[绿药丸]\"] = R.drawable.coolapk_emotion_55_lvyaowan\n        emojiMap[\"[红药丸]\"] = R.drawable.coolapk_emotion_54_hongyaowan\n        emojiMap[\"[酷安]\"] = R.drawable.coolapk_emotion_60_kuan\n        emojiMap[\"[酷安钓鱼]\"] = R.drawable.coolapk_emotion_1021\n        emojiMap[\"[绿帽]\"] = R.drawable.coolapk_emotion_61_lvmao\n        emojiMap[\"[酷安绿帽]\"] = R.drawable.coolapk_emotion_96_kuanlvmao\n        emojiMap[\"[火把]\"] = R.drawable.coolapk_emotion_83_huoba\n        emojiMap[\"[夏阁艾迪剑]\"] = R.drawable.coolapk_emotion_1028\n        emojiMap[\"[下次一定]\"] = R.drawable.coolapk_emotion_1029\n        emojiMap[\"[酷安土豆]\"] = R.drawable.coolapk_emotion_1030\n        emojiMap[\"[头条通知书]\"] = R.drawable.coolapk_emotion_1031\n        emojiMap[\"[酷币]\"] = R.drawable.c_coolb\n        emojiMap[\"[酷币1分]\"] = R.drawable.c_onef\n        emojiMap[\"[酷币2分]\"] = R.drawable.c_twof\n        emojiMap[\"[酷币5分]\"] = R.drawable.c_fivef\n        emojiMap[\"[酷币1毛]\"] = R.drawable.c_onem\n        emojiMap[\"[酷币2毛]\"] = R.drawable.c_twom\n        emojiMap[\"[酷币5毛]\"] = R.drawable.c_fivem\n        emojiMap[\"[酷币1块]\"] = R.drawable.c_oney\n        emojiMap[\"[酷币2块]\"] = R.drawable.c_twoy\n        emojiMap[\"[酷币5块]\"] = R.drawable.c_fivey\n        emojiMap[\"[酷币10块]\"] = R.drawable.c_teny\n        emojiMap[\"[酷币20块]\"] = R.drawable.c_ty\n        emojiMap[\"[酷币50块]\"] = R.drawable.c_fy\n        emojiMap[\"[酷币100块]\"] = R.drawable.c_oy\n        emojiMap[\"[酷币1$]\"] = R.drawable.c_oned\n        emojiMap[\"[酷币2$]\"] = R.drawable.c_twod\n        emojiMap[\"[酷币5$]\"] = R.drawable.c_fived\n        emojiMap[\"[酷币1€]\"] = R.drawable.c_oneo\n        emojiMap[\"[酷币2€]\"] = R.drawable.c_twoo\n        emojiMap[\"[酷币5€]\"] = R.drawable.c_fiveo\n        emojiMap[\"[灰色酷币]\"] = R.drawable.coolapk_emotion_68\n        emojiMap[\"[绿色酷币]\"] = R.drawable.coolapk_emotion_69\n        emojiMap[\"[白纹酷币]\"] = R.drawable.coolapk_emotion_70\n        emojiMap[\"[新酷币]\"] = R.drawable.coolapk_emotion_71\n        emojiMap[\"[新币1分]\"] = R.drawable.coolapk_emotion_72\n        emojiMap[\"[新酷币2分]\"] = R.drawable.coolapk_emotion_85\n        emojiMap[\"[新酷币5分]\"] = R.drawable.coolapk_emotion_86\n        emojiMap[\"[新酷币1毛]\"] = R.drawable.coolapk_emotion_87\n        emojiMap[\"[新酷币2毛]\"] = R.drawable.coolapk_emotion_88\n        emojiMap[\"[新酷币5毛]\"] = R.drawable.coolapk_emotion_89\n        emojiMap[\"[新酷币1块]\"] = R.drawable.coolapk_emotion_90\n        emojiMap[\"[新酷币2块]\"] = R.drawable.coolapk_emotion_91\n        emojiMap[\"[新酷币5块]\"] = R.drawable.coolapk_emotion_92\n        emojiMap[\"[新酷币10块]\"] = R.drawable.coolapk_emotion_93\n        emojiMap[\"[新酷币20块]\"] = R.drawable.coolapk_emotion_94\n        emojiMap[\"[新酷币50块]\"] = R.drawable.coolapk_emotion_73\n        emojiMap[\"[新酷币100块]\"] = R.drawable.coolapk_emotion_74\n        emojiMap[\"[新酷币1$]\"] = R.drawable.coolapk_emotion_75\n        emojiMap[\"[新酷币2$]\"] = R.drawable.coolapk_emotion_76\n        emojiMap[\"[新酷币5$]\"] = R.drawable.coolapk_emotion_77\n        emojiMap[\"[新酷币1€]\"] = R.drawable.coolapk_emotion_78\n        emojiMap[\"[新酷币2€]\"] = R.drawable.coolapk_emotion_79\n        emojiMap[\"[新酷币5€]\"] = R.drawable.coolapk_emotion_80\n\n        for (i in 0..3) {\n            emojiList.add(dataList.subList(i * 27 + 4, (i + 1) * 27 + 4))\n        }\n        coolBList.add(dataList.subList(112, 139))\n        coolBList.add(dataList.subList(139, 155))\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/Extensions.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.ClipData\nimport android.content.ClipboardManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageInfo\nimport android.content.res.Resources\nimport android.net.Uri\nimport android.os.Build.VERSION.SDK_INT\nimport android.util.TypedValue\nimport android.widget.Toast\nimport androidx.compose.foundation.clickable\nimport androidx.compose.foundation.gestures.detectTapGestures\nimport androidx.compose.foundation.interaction.MutableInteractionSource\nimport androidx.compose.foundation.lazy.LazyListState\nimport androidx.compose.foundation.selection.toggleable\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.derivedStateOf\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.mutableIntStateOf\nimport androidx.compose.runtime.mutableLongStateOf\nimport androidx.compose.runtime.remember\nimport androidx.compose.runtime.setValue\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.composed\nimport androidx.compose.ui.geometry.Offset\nimport androidx.compose.ui.input.pointer.pointerInput\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.PREFIX_APP\nimport com.example.c001apk.compose.constant.Constants.PREFIX_FEED\nimport com.example.c001apk.compose.constant.Constants.PREFIX_TOPIC\nimport com.example.c001apk.compose.constant.Constants.PREFIX_USER\nimport com.example.c001apk.compose.constant.Constants.UTF8\nimport org.jsoup.nodes.Document\nimport java.net.URLDecoder\nimport java.net.URLEncoder\nimport java.util.regex.Pattern\n\ninline val Number.dp get() = (toFloat() * Resources.getSystem().displayMetrics.density).toInt()\n\ninline val Number.sp: Float\n    get() = TypedValue.applyDimension(\n        TypedValue.COMPLEX_UNIT_SP,\n        this.toFloat(),\n        Resources.getSystem().displayMetrics\n    )\n\ninline val String.http2https: String\n    get() = if (this.getOrElse(4) { 's' } == 's') this\n    else StringBuilder(this).insert(4, 's').toString()\n\nfun Context.makeToast(text: String) {\n    Toast.makeText(this, text, Toast.LENGTH_SHORT).show()\n}\n\nfun Modifier.noRippleToggleable(\n    value: Boolean,\n    onValueChange: (Boolean) -> Unit\n): Modifier = composed {\n    toggleable(\n        value = value,\n        indication = null,\n        interactionSource = remember { MutableInteractionSource() },\n        onValueChange = onValueChange\n    )\n}\n\ninline fun Modifier.noRippleClickable(\n    enabled: Boolean = true,\n    crossinline onClick: () -> Unit\n): Modifier = composed {\n    clickable(\n        enabled = enabled,\n        indication = null,\n        interactionSource = remember { MutableInteractionSource() }) {\n        onClick()\n    }\n}\n\nfun Context.copyText(text: String?, showToast: Boolean = true) {\n    text?.let {\n        val clipboardManager = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager\n        ClipData.newPlainText(\"copy text\", it)?.let { clipboardManager.setPrimaryClip(it) }\n        if (showToast)\n            makeToast(\"已复制: $it\")\n    }\n}\n\nfun Context.shareText(text: String) {\n    val title = \"Share\"\n    try {\n        val intent = Intent(Intent.ACTION_SEND)\n        intent.type = \"text/plain\"\n        intent.putExtra(Intent.EXTRA_SUBJECT, title)\n        intent.putExtra(Intent.EXTRA_TEXT, text)\n        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n        startActivity(Intent.createChooser(intent, title))\n    } catch (e: Exception) {\n        makeToast(e.message ?: \"failed to share text\")\n    }\n}\n\nenum class ShareType {\n    FEED, APP, TOPIC, USER\n}\n\nfun getShareText(type: ShareType, id: String): String {\n    val prefix = when (type) {\n        ShareType.APP -> PREFIX_APP\n        ShareType.FEED -> PREFIX_FEED\n        ShareType.TOPIC -> PREFIX_TOPIC\n        ShareType.USER -> PREFIX_USER\n    }\n    return \"https://www.coolapk1s.com$prefix$id\"\n}\n\ninline val String.getAllLinkAndText: String\n    get() = if (isEmpty()) EMPTY_STRING else\n        Pattern.compile(\"<a class=\\\"feed-link-url\\\"\\\\s+href=\\\"([^<>\\\"]*)\\\"[^<]*[^>]*>\")\n            .matcher(this).replaceAll(\" $1 \")\n\n// onDoubleClick = {} //双击时回调\n// onPress = {} //按下时回调\n// onLongPress = {} //长按时回调\n// onTap = {} //轻触时回调(按下并抬起)\nfun Modifier.doubleClick(onDoubleClick: (Offset) -> Unit): Modifier =\n    pointerInput(this) {\n        detectTapGestures(\n            onDoubleTap = onDoubleClick\n        )\n    }\n\nfun Modifier.longClick(onLongClick: (Offset) -> Unit): Modifier =\n    pointerInput(this) {\n        detectTapGestures(\n            onLongPress = onLongClick\n        )\n    }\n\n@Composable\ninline fun composeClick(\n    time: Int = 500,\n    crossinline onClick: () -> Unit\n): () -> Unit {\n    var lastClickTime by remember { mutableLongStateOf(value = 0L) }\n    return {\n        val currentTimeMillis = System.currentTimeMillis()\n        if (currentTimeMillis - lastClickTime >= time) {\n            onClick()\n            lastClickTime = currentTimeMillis\n        }\n    }\n}\n\n@Composable\nfun LazyListState.isScrollingUp(): Boolean {\n    var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) }\n    var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) }\n    return remember(this) {\n        derivedStateOf {\n            if (previousIndex != firstVisibleItemIndex) {\n                previousIndex > firstVisibleItemIndex\n            } else {\n                previousScrollOffset >= firstVisibleItemScrollOffset\n            }.also {\n                previousIndex = firstVisibleItemIndex\n                previousScrollOffset = firstVisibleItemScrollOffset\n            }\n        }\n    }.value\n}\n\ninline val String?.encode: String\n    get() = URLEncoder.encode(this?.replace(\"%\", \"%25\")?.replace(\"+\", \"%2B\"), UTF8)\ninline val String.decode: String\n    get() = URLDecoder.decode(this, UTF8)\n\nfun Context.openInBrowser(url: String) {\n    try {\n        startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(url)))\n    } catch (e: Exception) {\n        makeToast(\"打开失败\")\n        copyText(url)\n        e.printStackTrace()\n    }\n}\n\n@Suppress(\"DEPRECATION\")\ninline val PackageInfo.longVersionCodeCompat: Long\n    get() {\n        return if (SDK_INT >= 28)\n            longVersionCode\n        else\n            versionCode.toLong()\n    }\n\nfun Document.createRequestHash(): String =\n    this.getElementsByTag(\"Body\").attr(\"data-request-hash\")\n\nfun createRandomNumber() = Math.random().toString().replace(\".\", \"undefined\")\n\nfun Context.downloadApk(downloadUrl: String, name: String) {\n    try {\n        Utils.downloadApk(this, downloadUrl, name)\n        copyText(downloadUrl)\n    } catch (e: Exception) {\n        e.printStackTrace()\n        try {\n            openInBrowser(downloadUrl)\n        } catch (e: Exception) {\n            makeToast(\"下载失败\")\n            copyText(downloadUrl)\n        }\n    }\n}\n\nenum class ReportType {\n    FEED, REPLY, USER\n}\n\nfun getReportUrl(id: String, type: ReportType): String =\n    when (type) {\n        ReportType.FEED -> \"https://m.coolapk.com/mp/do?c=feed&m=report&type=feed&id=$id\"\n        ReportType.REPLY -> \"https://m.coolapk.com/mp/do?c=feed&m=report&type=feed_reply&id=$id\"\n        ReportType.USER -> \"https://m.coolapk.com/mp/do?c=user&m=report&id=$id\"\n    }\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/ImageDownloadUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.ContentValues\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Environment\nimport android.provider.MediaStore\nimport android.widget.Toast\nimport androidx.core.graphics.drawable.toBitmap\nimport coil.ImageLoader\nimport coil.request.ImageRequest\nimport com.example.c001apk.compose.R\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\nimport java.io.FileOutputStream\n\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/7\n */\nobject ImageDownloadUtil {\n\n    suspend fun downloadImage(\n        context: Context,\n        imageUrl: String,\n        fileName: String,\n        isEnd: Boolean,\n        isShare: Boolean = false,\n        userAgent: String?,\n    ) {\n        val imageLoader = ImageLoader.Builder(context).build()\n\n        val request = ImageRequest.Builder(context)\n            .data(imageUrl)\n            .addHeader(\"User-Agent\", userAgent ?: \"\")\n            .target(\n                onSuccess = { drawable ->\n                    CoroutineScope(Dispatchers.IO).launch {\n                        val result =\n                            if (isShare || SDK_INT < 29) {\n                                saveImageBelowQ(context, drawable, fileName, isShare)\n                            } else {\n                                saveImageAboveQ(context, drawable, fileName)\n                            }\n                        if (!isShare && isEnd) {\n                            withContext(Dispatchers.Main) {\n                                context.makeToast(\n                                    if (result) \"Image saved successfully\"\n                                    else \"Failed to save image\"\n                                )\n                            }\n                        }\n                        if (isShare && result) {\n                            ImageShowUtil.shareImage(\n                                context,\n                                File(context.externalCacheDir, \"imageShare/$fileName\"),\n                                null\n                            )\n                        }\n                    }\n                },\n                onError = {\n                    Toast.makeText(context, \"Failed to download image\", Toast.LENGTH_SHORT).show()\n                }\n            )\n            .build()\n\n        imageLoader.enqueue(request)\n    }\n\n    private fun saveImageAboveQ(\n        context: Context,\n        drawable: Drawable,\n        fileName: String\n    ): Boolean {\n        return try {\n            val resolver = context.contentResolver\n            val contentValues = ContentValues().apply {\n                put(MediaStore.Images.Media.DISPLAY_NAME, fileName)\n                put(MediaStore.Images.Media.DESCRIPTION, fileName)\n                put(MediaStore.Images.Media.MIME_TYPE, \"image/jpeg\")\n                put(\n                    MediaStore.Images.Media.RELATIVE_PATH,\n                    Environment.DIRECTORY_PICTURES + \"/${context.getString(R.string.app_name)}/\"\n                )\n            }\n            resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)\n                ?.let { uri ->\n                    resolver.openOutputStream(uri)?.use { outputStream ->\n                        drawable.toBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)\n                    } ?: false\n                } ?: false\n            true\n        } catch (e: Exception) {\n            e.printStackTrace()\n            false\n        }\n    }\n\n    private suspend fun saveImageBelowQ(\n        context: Context,\n        drawable: Drawable,\n        fileName: String,\n        isShare: Boolean\n    ): Boolean {\n        return withContext(Dispatchers.IO) {\n            val imagesDir =\n                // Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)\n                Environment.getExternalStorageDirectory().toString() +\n                        \"/${context.getString(R.string.app_name)}/\"\n            val imageFile =\n                if (isShare) File(context.externalCacheDir, \"/imageShare/$fileName\") else File(\n                    imagesDir,\n                    fileName\n                )\n            if (imageFile.parentFile?.exists() == false)\n                imageFile.parentFile?.mkdirs()\n\n            try {\n                FileOutputStream(imageFile).use { outputStream ->\n                    drawable.toBitmap().compress(Bitmap.CompressFormat.JPEG, 100, outputStream)\n                }\n                true\n            } catch (e: Exception) {\n                e.printStackTrace()\n                false\n            }\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/ImageShowUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.ActivityNotFoundException\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Environment\nimport android.util.Log\nimport android.view.View\nimport android.widget.ImageView\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.FileProvider\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.FragmentActivity\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.SUFFIX_THUMBNAIL\nimport com.example.c001apk.compose.view.CircleIndexIndicator\nimport com.example.c001apk.compose.view.NineGridImageView\nimport com.google.android.material.dialog.MaterialAlertDialogBuilder\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport net.mikaelzero.mojito.Mojito\nimport net.mikaelzero.mojito.ext.mojito\nimport net.mikaelzero.mojito.impl.DefaultPercentProgress\nimport net.mikaelzero.mojito.impl.DefaultTargetFragmentCover\nimport net.mikaelzero.mojito.impl.SimpleMojitoViewCallback\nimport java.io.File\n\nobject ImageShowUtil {\n\n    fun startBigImgView(\n        nineGridView: NineGridImageView,\n        imageView: ImageView,\n        urlList: List<String>,\n        position: Int,\n        cookie: String? = null,\n        userAgent: String? = null,\n    ) {\n        val thumbnailList = urlList.map { it.http2https }\n        val originList = urlList.map {\n            if (it.endsWith(SUFFIX_THUMBNAIL)) it.replace(SUFFIX_THUMBNAIL, EMPTY_STRING).http2https\n            else it.http2https\n        }\n        Mojito.start(imageView.context) {\n            cookie(cookie)\n            userAgent(userAgent)\n            urls(thumbnailList, originList)\n            position(position)\n            progressLoader {\n                DefaultPercentProgress()\n            }\n            if (urlList.size != 1)\n                setIndicator(CircleIndexIndicator())\n            views(nineGridView.getImageViews().toTypedArray())\n            when (CookieUtil.imageQuality) {\n                0 -> if (NetWorkUtil.isWifiConnected())\n                    autoLoadTarget(true)\n                else\n                    autoLoadTarget(false)\n\n                1 -> autoLoadTarget(true)\n\n                2 -> autoLoadTarget(false)\n            }\n            fragmentCoverLoader {\n                DefaultTargetFragmentCover()\n            }\n            setOnMojitoListener(\n                object : SimpleMojitoViewCallback() {\n                    override fun onStartAnim(position: Int) {\n                        nineGridView.getImageViewAt(position)?.apply {\n                            postDelayed({\n                                this.isVisible = false\n                            }, 200)\n                        }\n                    }\n\n                    override fun onMojitoViewFinish(pagePosition: Int) {\n                        nineGridView.getImageViews().forEach {\n                            it.isVisible = true\n                        }\n                    }\n\n                    override fun onViewPageSelected(position: Int) {\n                        nineGridView.getImageViews().forEachIndexed { index, imageView ->\n                            imageView.isVisible = position != index\n                        }\n                    }\n\n                    override fun onLongClick(\n                        fragmentActivity: FragmentActivity?,\n                        view: View,\n                        x: Float,\n                        y: Float,\n                        position: Int\n                    ) {\n                        if (fragmentActivity != null) {\n                            showSaveImgDialog(\n                                fragmentActivity,\n                                originList[position],\n                                originList,\n                                userAgent,\n                            )\n                        } else {\n                            Log.i(\"Mojito\", \"fragmentActivity is null, skip save image\")\n                        }\n                    }\n                },\n            )\n        }\n\n    }\n\n    fun startBigImgViewSimple(\n        context: Context,\n        urlList: List<String>,\n        cookie: String? = null,\n        userAgent: String? = null,\n    ) {\n        val thumbnailList = urlList.map { \"${it.http2https}$SUFFIX_THUMBNAIL\" }\n        val originList = urlList.map { it.http2https }\n        Mojito.start(context) {\n            cookie(cookie)\n            userAgent(userAgent)\n            urls(thumbnailList, originList)\n            when (CookieUtil.imageQuality) {\n                0 -> if (NetWorkUtil.isWifiConnected())\n                    autoLoadTarget(true)\n                else\n                    autoLoadTarget(false)\n\n                1 -> autoLoadTarget(true)\n\n                2 -> autoLoadTarget(false)\n            }\n            fragmentCoverLoader {\n                DefaultTargetFragmentCover()\n            }\n            progressLoader {\n                DefaultPercentProgress()\n            }\n            if (urlList.size > 1)\n                setIndicator(CircleIndexIndicator())\n            setOnMojitoListener(object : SimpleMojitoViewCallback() {\n                override fun onLongClick(\n                    fragmentActivity: FragmentActivity?,\n                    view: View,\n                    x: Float,\n                    y: Float,\n                    position: Int\n                ) {\n                    if (fragmentActivity != null) {\n                        showSaveImgDialog(\n                            fragmentActivity,\n                            originList[position],\n                            originList,\n                            userAgent\n                        )\n                    } else {\n                        Log.i(\"Mojito\", \"fragmentActivity is null, skip save image\")\n                    }\n                }\n            })\n        }\n    }\n\n    fun startBigImgViewSimple(\n        imageView: ImageView,\n        url: String,\n        cookie: String? = null,\n        userAgent: String? = null,\n    ) {\n        imageView.mojito(\n            url = url,\n            builder = {\n                progressLoader {\n                    DefaultPercentProgress()\n                }\n                setOnMojitoListener(object : SimpleMojitoViewCallback() {\n                    override fun onLongClick(\n                        fragmentActivity: FragmentActivity?,\n                        view: View,\n                        x: Float,\n                        y: Float,\n                        position: Int\n                    ) {\n                        if (fragmentActivity != null) {\n                            showSaveImgDialog(fragmentActivity, url, null, userAgent)\n                        } else {\n                            Log.i(\"Mojito\", \"fragmentActivity is null, skip save image\")\n                        }\n                    }\n                })\n            },\n            cookie = cookie,\n            userAgent = userAgent,\n        )\n    }\n\n    private fun showSaveImgDialog(\n        context: Context,\n        url: String,\n        urlList: List<String>?,\n        userAgent: String?,\n    ) {\n        val items = arrayOf(\"保存图片\", \"保存全部图片\", \"图片分享\", \"复制图片地址\")\n        MaterialAlertDialogBuilder(context).apply {\n            setItems(items) { _: DialogInterface?, position: Int ->\n                when (position) {\n                    0 -> CoroutineScope(Dispatchers.IO).launch {\n                        checkImageExist(context, url, true, userAgent)\n                    }\n\n                    1 -> CoroutineScope(Dispatchers.IO).launch {\n                        if (urlList.isNullOrEmpty()) {\n                            checkImageExist(context, url, true, userAgent)\n                        } else {\n                            urlList.forEachIndexed { index, url ->\n                                checkImageExist(context, url, index == urlList.lastIndex, userAgent)\n                            }\n                        }\n                    }\n\n                    2 -> CoroutineScope(Dispatchers.IO).launch {\n                        val index = url.lastIndexOf('/')\n                        val filename = url.substring(index + 1)\n                        if (checkShareImageExist(context, filename)) {\n                            shareImage(\n                                context,\n                                File(context.externalCacheDir, \"imageShare/$filename\"),\n                                null\n                            )\n                        } else {\n                            ImageDownloadUtil.downloadImage(\n                                context, url, filename,\n                                isEnd = true,\n                                isShare = true,\n                                userAgent = userAgent,\n                            )\n                        }\n                    }\n\n                    3 -> context.copyText(url)\n                }\n            }\n            show()\n        }\n    }\n\n    private fun checkShareImageExist(context: Context, filename: String): Boolean {\n        val imageCheckDir = File(context.externalCacheDir, \"imageShare/$filename\")\n        return imageCheckDir.exists()\n    }\n\n    private suspend fun checkImageExist(\n        context: Context,\n        url: String,\n        isEnd: Boolean,\n        userAgent: String?,\n    ) {\n        val filename = url.substring(url.lastIndexOf('/') + 1)\n        val path = \"${context.getString(R.string.app_name)}/$filename\"\n        val imageFile = if (SDK_INT >= 29) File(\n            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), path\n        )\n        else File(Environment.getExternalStorageDirectory().toString(), path)\n        if (imageFile.exists()) {\n            if (isEnd)\n                withContext(Dispatchers.Main) {\n                    context.makeToast(\"文件已存在\")\n                }\n        } else {\n            ImageDownloadUtil.downloadImage(context, url, filename, isEnd, userAgent = userAgent)\n        }\n    }\n\n    private fun getFileProvider(context: Context, file: File): Uri {\n        val authority = context.packageName + \".fileprovider\"\n        return FileProvider.getUriForFile(context, authority, file)\n    }\n\n    fun shareImage(context: Context, file: File, title: String?) {\n        try {\n            val contentUri = getFileProvider(context, file)\n            val intent = Intent(Intent.ACTION_SEND)\n            intent.type = \"image/*\"\n            intent.putExtra(Intent.EXTRA_STREAM, contentUri)\n            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n            ContextCompat.startActivity(context, Intent.createChooser(intent, title), null)\n        } catch (e: ActivityNotFoundException) {\n            context.makeToast(\"failed to share image\")\n            e.printStackTrace()\n        }\n    }\n\n    fun getImageLp(url: String): Pair<Int, Int> {\n        var imgWidth = 1\n        var imgHeight = 1\n        val at = url.lastIndexOf(\"@\")\n        val x = url.lastIndexOf(\"x\")\n        val dot = url.lastIndexOf(\".\")\n        if (at != -1 && x != -1 && dot != -1) {\n            imgWidth = url.substring(at + 1, x).toInt()\n            imgHeight = url.substring(x + 1, dot).toInt()\n        }\n        return Pair(imgWidth, imgHeight)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/LoginCookiesInterceptor.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport com.example.c001apk.compose.constant.Constants.APP_ID\nimport com.example.c001apk.compose.constant.Constants.REQUEST_WITH\nimport com.example.c001apk.compose.util.CookieUtil.SESSID\nimport com.example.c001apk.compose.util.CookieUtil.isGetCaptcha\nimport com.example.c001apk.compose.util.CookieUtil.isGetLoginParam\nimport com.example.c001apk.compose.util.CookieUtil.isGetSmsLoginParam\nimport com.example.c001apk.compose.util.CookieUtil.isGetSmsToken\nimport com.example.c001apk.compose.util.CookieUtil.isPreGetLoginParam\nimport com.example.c001apk.compose.util.CookieUtil.isTryLogin\nimport okhttp3.Interceptor\nimport okhttp3.Request\nimport okhttp3.Response\nimport java.io.IOException\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/3\n */\nobject LoginCookiesInterceptor : Interceptor {\n    @Throws(IOException::class)\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val builder: Request.Builder = chain.request().newBuilder()\n        builder.apply {\n            if (isGetLoginParam) {\n                isGetLoginParam = false\n\n                addHeader(\n                    \"sec-ch-ua\",\n                    \"\"\"\"Android WebView\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"\"\"\"\n                )\n                addHeader(\"sec-ch-ua-mobile\", \"?1\")\n                addHeader(\"sec-ch-ua-platform\", \"Android\")\n                addHeader(\"Upgrade-Insecure-Requests\", \"1\")\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\n                    \"Accept\",\n                    \"\"\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\"\"\"\n                )\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\"X-Requested-With\", APP_ID)\n                addHeader(\"Cookie\", SESSID)\n            }\n            if (isPreGetLoginParam) {\n                isPreGetLoginParam = false\n\n                addHeader(\n                    \"sec-ch-ua\",\n                    \"\"\"\"Android WebView\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"\"\"\"\n                )\n                addHeader(\"sec-ch-ua-mobile\", \"?1\")\n                addHeader(\"sec-ch-ua-platform\", \"Android\")\n                addHeader(\"Upgrade-Insecure-Requests\", \"1\")\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\n                    \"Accept\",\n                    \"\"\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\"\"\"\n                )\n                addHeader(\"X-Requested-With\", APP_ID)\n                /*addHeader(\"Sec-Fetch-Site\", \"none\")\n                addHeader(\"Sec-Fetch-Mode\", \"navigate\")\n                addHeader(\"Sec-Fetch-User\", \"?1\")\n                addHeader(\"Sec-Fetch-Dest\", \"document\")\n                addHeader(\"Accept-Encoding\", \"gzip, deflate, br\")\n                addHeader(\"Accept-Language\", \"zh-CM,zh;q=0.9,en-US;q=0.8,en;q=0.7\")*/\n            } else if (isTryLogin) {\n                isTryLogin = false\n\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\"Cookie\", \"$SESSID; forward=https://www.coolapk.com\")\n                addHeader(\"X-Requested-With\", REQUEST_WITH)\n                addHeader(\"Content-Type\", \"application/x-www-form-urlencoded\")\n            } else if (isGetCaptcha) {\n                isGetCaptcha = false\n\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\n                    \"sec-ch-ua\",\n                    \"\"\"\"Android WebView\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"\"\"\"\n                )\n                addHeader(\"sec-ch-ua-mobile\", \"?1\")\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\"sec-ch-ua-platform\", \"Android\")\n                addHeader(\n                    \"Accept\",\n                    \"\"\"image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8\"\"\"\n                )\n                addHeader(\"X-Requested-With\", APP_ID)\n                addHeader(\"Sec-Fetch-Site\", \"same-origin\")\n                addHeader(\"Sec-Fetch-Mode\", \"no-cors\")\n                addHeader(\"Sec-Fetch-Dest\", \"image\")\n                addHeader(\"Referer\", \"https://account.coolapk.com/auth/loginByCoolapk\")\n                addHeader(\"Cookie\", \"$SESSID; forward=https://www.coolapk.com\")\n            } else if (isGetSmsToken) {\n                isGetSmsToken = false\n                addHeader(\n                    \"sec-ch-ua\",\n                    \"\"\"\"Android WebView\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"\"\"\"\n                )\n                addHeader(\"Content-Type\", \"application/x-www-form-urlencoded\")\n                addHeader(\"X-Requested-With\", REQUEST_WITH)\n                addHeader(\"sec-ch-ua-mobile\", \"?1\")\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\"sec-ch-ua-platform\", \"Android\")\n                addHeader(\"Accept\", \"*/*\")\n                addHeader(\"Origin\", \"https://account.coolapk.com\")\n                addHeader(\"Sec-Fetch-Site\", \"same-origin\")\n                addHeader(\"Sec-Fetch-Mode\", \"cors\")\n                addHeader(\"Sec-Fetch-Dest\", \"empty\")\n                addHeader(\"Referer\", \"https://account.coolapk.com/auth/login?type=mobile\")\n                addHeader(\"Accept-Encoding\", \"gzip, deflate, br\")\n                addHeader(\"Accept-Language\", \"zh-CM,zh;q=0.9,en-US;q=0.8,en;q=0.7\")\n                addHeader(\"Cookie\", \"$SESSID; forward=https://www.coolapk.com\")\n            } else if (isGetSmsLoginParam) {\n                isGetSmsLoginParam = false\n                addHeader(\n                    \"sec-ch-ua\",\n                    \"\"\"\"Android WebView\";v=\"117\", \"Not;A=Brand\";v=\"8\", \"Chromium\";v=\"117\"\"\"\"\n                )\n                addHeader(\"sec-ch-ua-mobile\", \"?1\")\n                addHeader(\"sec-ch-ua-platform\", \"Android\")\n                addHeader(\"Upgrade=Insecure-Requests\", \"1\")\n                addHeader(\"User-Agent\", CookieUtil.userAgent)\n                addHeader(\n                    \"Accept\",\n                    \"\"\"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\"\"\"\n                )\n                addHeader(\"X-Requested-With\", APP_ID)\n                addHeader(\"Sec-Fetch-Site\", \"none\")\n                addHeader(\"Sec-Fetch-Mode\", \"navigate\")\n                addHeader(\"Sec-Fetch-User\", \"?1\")\n                addHeader(\"Sec-Fetch-Dest\", \"document\")\n                addHeader(\"Accept-Encoding\", \"gzip, deflate, br\")\n                addHeader(\"Accept-Language\", \"zh-CM,zh;q=0.9,en-US;q=0.8,en;q=0.7\")\n            }\n\n        }\n        return chain.proceed(builder.build())\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/NetWorkUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.Context\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.net.NetworkCapabilities\nimport com.example.c001apk.compose.c001Application\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\nobject NetWorkUtil {\n\n    fun isWifiConnected(): Boolean {\n        val cm = c001Application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager\n        val network: Network? = cm.activeNetwork\n        if (network != null) {\n            val nc = cm.getNetworkCapabilities(network)\n            nc?.let {\n                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {\n                    return true\n                } else if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {\n                    return false\n                }\n            }\n        }\n        return false\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/OSSUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.ContentResolver\nimport android.graphics.BitmapFactory\nimport android.net.Uri\nimport androidx.exifinterface.media.ExifInterface\nimport java.io.IOException\nimport java.io.InputStream\nimport java.security.MessageDigest\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/7\n */\nobject OSSUtil {\n\n    fun getImageDimensionsAndMD5(\n        contentResolver: ContentResolver,\n        uri: Uri\n    ): Pair<Triple<Int, Int, String>?, ByteArray?> {\n        var dimensions: Triple<Int, Int, String>? = null\n        var md5Hash: ByteArray? = null\n\n        try {\n            dimensions = contentResolver.openInputStream(uri)?.use { inputStream ->\n                val options = BitmapFactory.Options().apply {\n                    inJustDecodeBounds = true\n                }\n                BitmapFactory.decodeStream(inputStream, null, options)\n                var width = options.outWidth\n                var height = options.outHeight\n                with(getRotation(contentResolver, uri)) {\n                    if (this == 90 || this == 270) {\n                        width = height.apply {\n                            height = width\n                        }\n                    }\n                }\n                Triple(width, height, options.outMimeType)\n            }\n            md5Hash = contentResolver.openInputStream(uri)?.use { inputStream ->\n                calculateMD5(inputStream)\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n        return Pair(dimensions, md5Hash)\n    }\n\n    private fun getRotation(\n        contentResolver: ContentResolver,\n        uri: Uri\n    ): Int {\n        var rotation = 0\n        try {\n            val exif = contentResolver.openInputStream(uri)?.let { ExifInterface(it) }\n            val orientation = exif?.getAttributeInt(\n                ExifInterface.TAG_ORIENTATION,\n                ExifInterface.ORIENTATION_NORMAL\n            )\n            rotation = when (orientation) {\n                ExifInterface.ORIENTATION_ROTATE_90 -> 90\n                ExifInterface.ORIENTATION_ROTATE_180 -> 180\n                ExifInterface.ORIENTATION_ROTATE_270 -> 270\n                else -> 0\n            }\n        } catch (e: IOException) {\n            e.printStackTrace()\n        }\n        return rotation\n    }\n\n    private fun calculateMD5(input: InputStream): ByteArray {\n        val md = MessageDigest.getInstance(\"MD5\")\n        val buffer = ByteArray(8192)\n        var bytesRead = input.read(buffer)\n        while (bytesRead > 0) {\n            md.update(buffer, 0, bytesRead)\n            bytesRead = input.read(buffer)\n        }\n        return md.digest()\n    }\n\n    fun ByteArray.toHex(): String {\n        val hexString = StringBuilder()\n        for (byte in this) {\n            hexString.append(String.format(\"%02x\", byte))\n        }\n        return hexString.toString()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/OssUploadUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Base64\nimport android.util.Log\nimport com.alibaba.sdk.android.oss.ClientConfiguration\nimport com.alibaba.sdk.android.oss.ClientException\nimport com.alibaba.sdk.android.oss.OSSClient\nimport com.alibaba.sdk.android.oss.ServiceException\nimport com.alibaba.sdk.android.oss.callback.OSSCompletedCallback\nimport com.alibaba.sdk.android.oss.common.OSSLog\nimport com.alibaba.sdk.android.oss.common.auth.OSSStsTokenCredentialProvider\nimport com.alibaba.sdk.android.oss.model.ObjectMetadata\nimport com.alibaba.sdk.android.oss.model.PutObjectRequest\nimport com.alibaba.sdk.android.oss.model.PutObjectResult\nimport com.example.c001apk.compose.logic.model.OSSUploadPrepareResponse\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\n/**\n * Created by bggRGjQaUbCoE on 2024/5/7\n */\nobject OssUploadUtil {\n    suspend fun ossUpload(\n        context: Context,\n        responseData: OSSUploadPrepareResponse.Data,\n        uriList: List<Uri>,\n        typeList: List<String>,\n        md5List: List<ByteArray?>,\n        iOnSuccess: (Int) -> Unit,\n        iOnFailure: () -> Unit,\n        closeDialog: () -> Unit,\n    ) {\n        try {\n            val accessKeyId = responseData.uploadPrepareInfo.accessKeyId\n            val accessKeySecret = responseData.uploadPrepareInfo.accessKeySecret\n            val securityToken = responseData.uploadPrepareInfo.securityToken\n\n            val endPoint = responseData.uploadPrepareInfo.endPoint.replace(\"https://\", \"\")\n            val bucket = responseData.uploadPrepareInfo.bucket\n            val callbackUrl = responseData.uploadPrepareInfo.callbackUrl\n\n            val conf = ClientConfiguration()\n            conf.connectionTimeout = 5 * 60 * 1000\n            conf.socketTimeout = 5 * 60 * 1000\n            conf.maxConcurrentRequest = 5\n            conf.maxErrorRetry = 2\n            OSSLog.enableLog()\n            val credentialProvider = OSSStsTokenCredentialProvider(\n                accessKeyId,\n                accessKeySecret,\n                securityToken\n            )\n            val oss = OSSClient(context, endPoint, credentialProvider, conf)\n\n            uriList.forEachIndexed { index, uri ->\n                val put = PutObjectRequest(\n                    bucket,\n                    responseData.fileInfo[index].uploadFileName,\n                    uri\n                )\n                val metadata = ObjectMetadata()\n                metadata.contentType = typeList[index]\n                metadata.contentMD5 = Base64.encodeToString(md5List[index], Base64.DEFAULT).trim()\n                metadata.setHeader(\n                    \"x-oss-callback\",\n                    \"eyJjYWxsYmFja0JvZHlUeXBlIjoiYXBwbGljYXRpb25cL2pzb24iLCJjYWxsYmFja0hvc3QiOiJhcGkuY29vbGFway5jb20iLCJjYWxsYmFja1VybCI6Imh0dHBzOlwvXC9hcGkuY29vbGFway5jb21cL3Y2XC9jYWxsYmFja1wvbW9iaWxlT3NzVXBsb2FkU3VjY2Vzc0NhbGxiYWNrP2NoZWNrQXJ0aWNsZUNvdmVyUmVzb2x1dGlvbj0wJnZlcnNpb25Db2RlPTIxMDIwMzEiLCJjYWxsYmFja0JvZHkiOiJ7XCJidWNrZXRcIjoke2J1Y2tldH0sXCJvYmplY3RcIjoke29iamVjdH0sXCJoYXNQcm9jZXNzXCI6JHt4OnZhcjF9fSJ9\"\n                )\n                metadata.setHeader(\"x-oss-callback-var\", \"eyJ4OnZhcjEiOiJmYWxzZSJ9\")\n                put.metadata = metadata\n                put.callbackParam = object : HashMap<String, String>() {\n                    init {\n                        put(\"callbackUrl\", callbackUrl)\n                        put(\"callbackHost\", Uri.parse(callbackUrl).host ?: \"developer.coolapk.com\")\n                        put(\"callbackBodyType\", \"application/json\")\n                        put(\"callbackBody\", \"filename=${responseData.fileInfo[index].name}\")\n                    }\n                }\n                oss.asyncPutObject(put, OSSCallBack(\n                    iOnSuccess = {\n                        iOnSuccess(index)\n                    },\n                    iOnFailure = {\n                        iOnFailure()\n                    }\n                ))\n            }\n        } catch (e: Exception) {\n            closeDialog()\n            e.message?.let {\n                withContext(Dispatchers.Main) {\n                    context.makeToast(it)\n                }\n            }\n            e.printStackTrace()\n        }\n    }\n\n    class OSSCallBack(\n        private val iOnSuccess: () -> Unit,\n        private val iOnFailure: () -> Unit,\n    ) : OSSCompletedCallback<PutObjectRequest?, PutObjectResult?> {\n        override fun onSuccess(\n            request: PutObjectRequest?,\n            result: PutObjectResult?\n        ) {\n            iOnSuccess()\n        }\n\n        override fun onFailure(\n            request: PutObjectRequest?,\n            clientException: ClientException?,\n            serviceException: ServiceException?\n        ) {\n            iOnFailure()\n            // Request exception\n            if (clientException != null) {\n                // Local exception, such as a network exception\n                Log.i(\"OSSUpload\", \"uploadFailed: clientException: ${clientException.message}\")\n                clientException.printStackTrace()\n            }\n            if (serviceException != null) {\n                // Service exception\n                Log.i(\"OSSUpload\", \"OSSUpload: serviceException: ${serviceException.message}\")\n                Log.i(\"OSSUpload\", \"ErrorCode=\" + serviceException.errorCode)\n                Log.i(\"OSSUpload\", \"RequestId=\" + serviceException.requestId)\n                Log.i(\"OSSUpload\", \"HostId=\" + serviceException.hostId)\n                Log.i(\"OSSUpload\", \"RawMessage=\" + serviceException.rawMessage)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/SpannableStringBuilderUtil.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.content.Context\nimport android.text.Spannable\nimport android.text.SpannableStringBuilder\nimport android.text.style.URLSpan\nimport androidx.appcompat.content.res.AppCompatResources\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.text.HtmlCompat\nimport com.example.c001apk.compose.view.CenteredImageSpan\nimport com.example.c001apk.compose.view.MyURLSpan\nimport java.util.regex.Pattern\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\nobject SpannableStringBuilderUtil {\n\n    fun setText(\n        mContext: Context,\n        text: String,\n        size: Float,\n        linkTextColor: Int,\n        onShowTotalReply: (() -> Unit)? = null,\n        onOpenLink: (String, String?) -> Unit,\n        onShowImages: (String) -> Unit,\n    ): SpannableStringBuilder {\n        val mess = HtmlCompat.fromHtml(\n            text.replace(\"\\n\", \"<br/>\"),\n            HtmlCompat.FROM_HTML_MODE_COMPACT\n        )\n        val builder = SpannableStringBuilder(mess)\n        val urls = builder.getSpans(\n            0, mess.length,\n            URLSpan::class.java\n        )\n        urls.forEach {\n            val myURLSpan =\n                MyURLSpan(it.url, linkTextColor, onShowTotalReply, onOpenLink, onShowImages)\n            val start = builder.getSpanStart(it)\n            val end = builder.getSpanEnd(it)\n            val flags = builder.getSpanFlags(it)\n            builder.setSpan(myURLSpan, start, end, flags)\n            builder.removeSpan(it)\n        }\n        if (CookieUtil.showEmoji) {\n            val pattern = Pattern.compile(\"\\\\[[^\\\\]]+\\\\]\")\n            val matcher = pattern.matcher(builder)\n            while (matcher.find()) {\n                val group = matcher.group()\n                EmojiUtils.emojiMap[group]?.let {\n                    AppCompatResources.getDrawable(mContext, it)?.let { emoji ->\n                        if (group == \"[图片]\")\n                            DrawableCompat.setTint(emoji, linkTextColor)\n\n                        if (group in listOf(\"[楼主]\", \"[层主]\", \"[置顶]\")) {\n                            emoji.setBounds(0, 0, (size * 2).toInt(), size.toInt())\n                            DrawableCompat.setTint(emoji, linkTextColor)\n                        } else\n                            emoji.setBounds(0, 0, (size * 1.3).toInt(), (size * 1.3).toInt())\n                        val imageSpan = CenteredImageSpan(emoji, (size * 1.3).toInt(), group)\n                        builder.setSpan(\n                            imageSpan,\n                            matcher.start(),\n                            matcher.end(),\n                            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE\n                        )\n                    }\n\n                }\n            }\n        }\n        return builder\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/TokenDeviceUtils.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.util.Base64\nimport com.example.c001apk.compose.constant.Constants\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.util.Utils.getBase64\nimport com.example.c001apk.compose.util.Utils.getMD5\nimport org.mindrot.jbcrypt.BCrypt\nimport java.nio.charset.Charset\nimport java.nio.charset.StandardCharsets\nimport java.util.Random\n\n\nobject TokenDeviceUtils {\n\n    fun encode(deviceInfo: String): String {\n        val charset: Charset = StandardCharsets.UTF_8\n        val bytes = deviceInfo.toByteArray(charset)\n        val encodeToString = Base64.encodeToString(bytes, 0)\n        val replace = StringBuilder(encodeToString).reverse().toString()\n        return Regex(\"\\\\r\\\\n|\\\\r|\\\\n|=\").replace(replace, EMPTY_STRING)\n    }\n\n    fun randHexString(@Suppress(\"SameParameterValue\") n: Int): String {\n        Random().setSeed(System.currentTimeMillis())\n        return (0 until n).joinToString(\"\") {\n            Random().nextInt(256).toString(16)\n        }.uppercase()\n    }\n\n    fun String.getTokenV2(): String {\n        val timeStamp = (System.currentTimeMillis() / 1000f).toString()\n\n        val base64TimeStamp = timeStamp.getBase64()\n        val md5TimeStamp = timeStamp.getMD5()\n        val md5DeviceCode = this.getMD5()\n\n        val token = \"${Constants.APP_LABEL}?$md5TimeStamp$$md5DeviceCode&${Constants.APP_ID}\"\n        val base64Token = token.getBase64()\n        val md5Base64Token = base64Token.getMD5()\n        val md5Token = token.getMD5()\n\n        val bcryptSalt = \"${\"$2a$10$$base64TimeStamp/$md5Token\".substring(0, 31)}u\"\n        val bcryptResult = BCrypt.hashpw(md5Base64Token, bcryptSalt)\n\n        return \"v2${bcryptResult.replaceRange(0, 3, \"$2y\").getBase64()}\"\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/util/Utils.kt",
    "content": "package com.example.c001apk.compose.util\n\nimport android.app.DownloadManager\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.content.res.Configuration\nimport android.net.Uri\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Environment\nimport androidx.core.text.HtmlCompat\nimport com.example.c001apk.compose.c001Application\nimport java.io.BufferedReader\nimport java.io.FileInputStream\nimport java.io.InputStreamReader\nimport java.security.MessageDigest\nimport java.text.SimpleDateFormat\nimport java.util.Calendar\nimport java.util.Date\nimport java.util.Locale\nimport java.util.UUID\nimport kotlin.random.Random\n\n\nobject Utils {\n\n    fun getAppVersion(context: Context, packageName: String): Pair<String, Long> {\n        return try {\n            val info = context.packageManager.getPackageInfo(packageName, 0)\n            Pair(info.versionName.orEmpty(), info.longVersionCodeCompat)\n        } catch (e: Exception) {\n            e.printStackTrace()\n            Pair(\"-1\", -1)\n        }\n    }\n\n    fun downloadApk(context: Context, url: String, name: String) {\n        val downloadManager =\n            context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager\n        val request = DownloadManager\n            .Request(Uri.parse(url))\n            .setMimeType(\"application/vnd.android.package-archive\")\n            .setTitle(name)\n            .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)\n            .setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, name)\n        if (SDK_INT < 29) {\n            request.allowScanningByMediaScanner()\n            request.setVisibleInDownloadsUi(true)\n        }\n        downloadManager.enqueue(request)\n    }\n\n    /**\n     * 检测设备宽度是否大于等于800dp\n     *\n     * @receiver Configuration\n     * @return boolean\n     */\n    fun Configuration.isTable() = this.screenWidthDp >= 800\n\n    /**\n     * 二进制数组转十六进制字符串\n     *\n     * @receiver byte array to be converted\n     * @return string containing hex values\n     */\n    fun ByteArray.byteArrayToHexString(): String {\n        val sb = StringBuilder(size * 2)\n        for (element in this) {\n            val v = element.toInt() and 0xff\n            if (v < 16) {\n                sb.append('0')\n            }\n            sb.append(Integer.toHexString(v))\n        }\n        return sb.toString().uppercase(Locale.US)\n    }\n\n    /**\n     * 十六进制字符串转二进制数组\n     *\n     * @receiver string of hex-encoded values\n     * @return decoded byte array\n     */\n    fun String.hexStringToByteArray(): ByteArray {\n        val data = ByteArray(length / 2)\n        for (index in indices step 2) {\n            data[index / 2] = (\n                    (Character.digit(this[index], 16) shl 4) +\n                            Character.digit(this[index + 1], 16)\n                    ).toByte()\n        }\n        return data\n    }\n\n    /**\n     * 根据字符串生成Base64码\n     *\n     * @receiver 原生字符串\n     * @param isRaw 是否省略“=”符号\n     * @return Base64码\n     */\n    fun String.getBase64(isRaw: Boolean = true): String {\n        var result =\n            Base64Utils.encode(this.toByteArray())//Base64.getEncoder().encodeToString(this.toByteArray())\n        if (isRaw) {\n            result = result.replace(\"=\", \"\")\n        }\n        return result\n    }\n\n    /**\n     * 根据字符串生成MD5值\n     *\n     * @receiver 原生字符串\n     * @return MD5值\n     */\n    fun String.getMD5(): String {\n        val instance: MessageDigest = MessageDigest.getInstance(\"MD5\")\n\n        val digest = instance.digest(this.toByteArray())\n\n        val sb = StringBuffer()\n\n        for (b in digest) {\n            val i = b.toInt() and 0xff\n            var hexString = Integer.toHexString(i)\n            if (hexString.length < 2) {\n                hexString = \"0$hexString\"\n            }\n            sb.append(hexString)\n        }\n\n        return sb.toString().replace(\"-\", \"\")\n    }\n\n\n    fun getInstalledAppMd5(appInfo: ApplicationInfo): String {\n\n        return generateRandomMD5()\n        // 猜测MD5与增量更新有关，因不计划支持，且获取MD5耗时较长，现随机生成\n\n//            try {\n//                // 获取应用程序的APK文件路径\n//                val apkPath = appInfo.sourceDir\n//\n//                // 计算MD5值\n//                return FileInputStream(File(apkPath)).calculateMd5()\n//            } catch (e: Exception) {\n//                e.printStackTrace()\n//            }\n//            return \"\"\n    }\n\n    fun FileInputStream.calculateMd5(): String {\n        val md = MessageDigest.getInstance(\"MD5\")\n        val buffer = ByteArray(1024)\n        var length: Int\n        while (read(buffer, 0, 1024).also { length = it } != -1) {\n            md.update(buffer, 0, length)\n        }\n        close()\n\n        val md5Bytes = md.digest()\n        var i: Int\n        val buf = StringBuffer(\"\")\n        for (offset in md5Bytes.indices) {\n            i = md5Bytes[offset].toInt()\n            if (i < 0) i += 256\n            if (i < 16) buf.append(\"0\")\n            buf.append(Integer.toHexString(i))\n        }\n        //32位加密\n        return buf.toString()\n    }\n\n    fun generateRandomMD5(): String {\n        val hexChars = \"0123456789abcdef\"\n        val stringBuilder = StringBuilder(32)\n\n        repeat(32) {\n            val randomIndex = Random.nextInt(hexChars.length)\n            stringBuilder.append(hexChars[randomIndex])\n        }\n\n        return stringBuilder.toString()\n    }\n\n    /**\n     * 随机生成Mac地址\n     *\n     * @return Mac地址\n     */\n    fun randomMacAddress(): String {\n        val sb = StringBuilder()\n\n        for (i in 0..5) {\n            if (sb.isNotEmpty()) {\n                sb.append(\":\")\n            }\n            val value = Random.nextInt(256)\n            val element = Integer.toHexString(value)\n            if (element.length < 2) {\n                sb.append(0)\n            }\n            sb.append(element)\n        }\n\n        return sb.toString().uppercase()\n    }\n\n    /**\n     * 随机生成oaid 32位字符串\n     *\n     * @return oaid\n     */\n    fun randomOaid(): String {\n        val oaidLength = 32\n        val sb = StringBuilder(oaidLength)\n        for (i in 0 until oaidLength) {\n            val randomChar = (Random.nextInt(26) + 'a'.code).toChar()\n            sb.append(randomChar)\n        }\n        return sb.toString()\n    }\n\n\n    fun randomAndroidId(): String {\n        return UUID.randomUUID().toString().replace(\"-\", \"\")\n    }\n\n    fun randomManufacturer(): String {\n        val manufacturers = listOf(\n            \"Samsung\",\n            \"Google\",\n            \"Huawei\",\n            \"Xiaomi\",\n            \"OnePlus\",\n            \"Sony\",\n            \"LG\",\n            \"Motorola\",\n            \"HTC\",\n            \"Nokia\",\n            \"Lenovo\",\n            \"Asus\",\n            \"ZTE\",\n            \"Alcatel\",\n            \"OPPO\",\n            \"Vivo\",\n            \"Realme\"\n            // 添加更多制造商名称\n        )\n\n        return manufacturers[Random.nextInt(manufacturers.size)]\n    }\n\n    fun randomBrand(): String {\n        val brands = listOf(\n            \"Samsung\",\n            \"Google\",\n            \"Huawei\",\n            \"Xiaomi\",\n            \"Redmi\",\n            \"OnePlus\",\n            \"Sony\",\n            \"LG\",\n            \"Motorola\",\n            \"HTC\",\n            \"Nokia\",\n            \"Lenovo\",\n            \"Asus\",\n            \"ZTE\",\n            \"Alcatel\",\n            \"OPPO\",\n            \"Vivo\",\n            \"Realme\"\n            // 添加更多品牌名称\n        )\n\n        return brands[Random.nextInt(brands.size)]\n    }\n\n\n    fun randomDeviceModel(): String {\n        val assetManager = c001Application.assets\n\n        try {\n            val inputStream = assetManager.open(\"devicemodel.txt\")\n            val reader = BufferedReader(InputStreamReader(inputStream))\n            val lines = ArrayList<String>()\n\n            var line: String?\n            while (reader.readLine().also { line = it } != null) {\n                lines.add(line ?: \"\")\n            }\n\n            val randomIndex = Random.nextInt(lines.size)\n            return lines[randomIndex]\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n\n        return \"null\"\n    }\n\n    fun randomSdkInt(): String {\n        return Random.nextInt(21, 34).toString()\n    }\n\n    fun randomAndroidVersionRelease(): String {\n        val androidVersionRelease = listOf(\n            \"5.0.1\", // Lollipop\n            \"6.0\",   // Marshmallow\n            \"7.0\",   // Nougat\n            \"7.1.1\", // Nougat\n            \"8.0.0\", // Oreo\n            \"8.1.0\", // Oreo\n            \"9\",     // Pie\n            \"10\",    // Android 10\n            \"11\",    // Android 11\n            \"12\",     // Android 12\n            \"13\",\n            \"14\",\n        )\n\n        return androidVersionRelease[Random.nextInt(androidVersionRelease.size)]\n    }\n\n    /**\n     * 拓展Modifier无点击效果点击方法\n     *\n     * @receiver Modifier\n     * @return Modifier\n     */\n    /*fun Modifier.clickableNoRipple(onClick: () -> Unit) = composed {\n        clickable(\n            indication = null,\n            interactionSource = remember { MutableInteractionSource() }\n        ) {\n            onClick()\n        }\n    }*/\n\n    /**\n     * 格式化时间戳\n     *\n     * @receiver 时间戳\n     * @return 格式化的时间\n     */\n    fun Long.secondToDateString(pattern: String = \"yyyy-MM-dd HH:mm:ss\") =\n        SimpleDateFormat(pattern, Locale.CHINA).format(Date(this))\n\n    /**\n     * 计算时间差\n     *\n     * @receiver old timestamp\n     * @param currentTime current timestamp\n     */\n    fun Long.timeStampInterval(currentTime: Long): String {\n        val calendarOld = Calendar.getInstance()\n        val calendarNow = Calendar.getInstance()\n\n        val dateOld = Date(this)\n        val dateNow = Date(currentTime)\n\n        calendarOld.time = dateOld\n        calendarNow.time = dateNow\n\n        val dayOld = calendarOld.get(Calendar.DAY_OF_YEAR)\n        val dayNow = calendarNow.get(Calendar.DAY_OF_YEAR)\n\n        val hourOld = calendarOld.get(Calendar.HOUR_OF_DAY)\n        val hourNow = calendarNow.get(Calendar.HOUR_OF_DAY)\n\n        val minuteOld = calendarOld.get(Calendar.MINUTE)\n        val minuteNow = calendarNow.get(Calendar.MINUTE)\n\n        return if (dayOld == dayNow) {\n            if (hourOld == hourNow) {\n                \"${minuteNow - minuteOld}分钟前\"\n            } else {\n                \"${hourNow - hourOld}小时前\"\n            }\n        } else {\n            \"${dayNow - dayOld}天前\"\n        }\n    }\n\n    /**\n     * 格式化富文本\n     *\n     * @receiver 原始文本\n     * @return 格式化后文本\n     */\n    fun String.richToString(htmlCompat: Int = HtmlCompat.FROM_HTML_MODE_COMPACT) =\n        HtmlCompat.fromHtml(this, htmlCompat).toString()\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/BadgeDrawable.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.ColorFilter\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.graphics.Typeface\nimport android.graphics.drawable.Drawable\nimport android.text.TextPaint\nimport com.example.c001apk.compose.util.dp\n\n/*\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n */\n\n// mod from https://github.com/klinker24/Android-BadgedImageView\nclass BadgeDrawable(\n    private val text: String,\n    private val colorPrimaryContainer: Int,\n    private val colorOnPrimaryContainer: Int\n) : Drawable() {\n    private val width: Int\n    private val height: Int\n    private val paint: Paint\n\n    init {\n        val padding = PADDING\n        val cornerRadius = CORNER_RADIUS\n        val textBounds = Rect()\n        val textPaint = TextPaint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG).apply {\n            setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.NORMAL))\n            textSize = TEXT_SIZE\n            getTextBounds(text, 0, text.length, textBounds)\n            setColor(colorOnPrimaryContainer)\n        }\n        height = (padding + textBounds.height() + padding).toInt()\n        width = (padding + textBounds.width() + padding).toInt()\n        if (bitmaps[text] == null) {\n            val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)\n            bitmap.setHasAlpha(true)\n            val canvas = Canvas(bitmap)\n            val backgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)\n            backgroundPaint.setColor(colorPrimaryContainer)\n            canvas.drawRoundRect(\n                0f, 0f, width.toFloat(), height.toFloat(),\n                cornerRadius, cornerRadius, backgroundPaint\n            )\n            val fix = if (text == \"GIF\") 1 else 2\n            canvas.drawText(text, padding - fix, height - padding - fix, textPaint)\n            bitmaps[text] = bitmap\n        }\n        paint = Paint()\n    }\n\n    override fun getIntrinsicWidth(): Int {\n        return width\n    }\n\n    override fun getIntrinsicHeight(): Int {\n        return height\n    }\n\n    override fun draw(canvas: Canvas) {\n        bitmaps[text]?.let {\n            canvas.drawBitmap(\n                it,\n                getBounds().left.toFloat(),\n                getBounds().top.toFloat(),\n                paint\n            )\n        }\n\n    }\n\n    override fun setAlpha(alpha: Int) {}\n\n    override fun setColorFilter(cf: ColorFilter?) {\n        paint.setColorFilter(cf)\n    }\n\n    @Deprecated(\"Deprecated in Java\", ReplaceWith(\"\", \"\"))\n    override fun getOpacity(): Int {\n        return 0.dp\n    }\n\n    companion object {\n        private val bitmaps: MutableMap<String, Bitmap?> = HashMap()\n        private val TEXT_SIZE = 12.dp.toFloat()\n        private val PADDING = 4.dp.toFloat()\n        private val CORNER_RADIUS = 4.dp.toFloat()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/BadgedImageView.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Rect\nimport android.view.Gravity\nimport com.example.c001apk.compose.util.dp\n\n\n/*\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n    limitations under the License.\n */\n\n// mod from https://github.com/klinker24/Android-BadgedImageView\nclass BadgedImageView(\n    context: Context,\n) : RoundedImageView(context) {\n    private var badgeBoundsSet = false\n    private var badge: BadgeDrawable? = null\n    private var badgeGravity: Int = Gravity.END or Gravity.BOTTOM\n    private var badgePadding: Int = 4.dp\n    var colorPrimaryContainer: Int = Color.BLACK\n    var colorOnPrimaryContainer: Int = Color.WHITE\n\n    fun setBadge(text: String) {\n        badge = BadgeDrawable(text, colorPrimaryContainer, colorOnPrimaryContainer)\n        badgeBoundsSet = false\n        invalidate()\n    }\n\n    override fun draw(canvas: Canvas) {\n        super.draw(canvas)\n        if (badge != null) {\n            if (!badgeBoundsSet) {\n                layoutBadge()\n            }\n            badge?.draw(canvas)\n        }\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        if (badge != null) {\n            layoutBadge()\n        }\n    }\n\n    private fun layoutBadge() {\n        badge?.let { badge ->\n            val badgeBounds = badge.getBounds()\n            Gravity.apply(\n                badgeGravity,\n                badge.intrinsicWidth,\n                badge.intrinsicHeight,\n                Rect(0, 0, width, height),\n                badgePadding,\n                badgePadding,\n                badgeBounds\n            )\n            badge.bounds = badgeBounds\n            badgeBoundsSet = true\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/CenteredImageSpan.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.drawable.Drawable\nimport android.text.style.ImageSpan\n\nclass CenteredImageSpan(drawableRes: Drawable, private val size: Int, private val group: String?) :\n    ImageSpan(drawableRes) {\n\n    override fun getSize(\n        paint: Paint,\n        text: CharSequence?,\n        start: Int,\n        end: Int,\n        fm: Paint.FontMetricsInt?\n    ): Int {\n        return if (group in listOf(\"[楼主]\", \"[层主]\", \"[置顶]\"))\n            super.getSize(paint, text, start, end, fm)\n        else size\n    }\n\n    override fun draw(\n        canvas: Canvas, text: CharSequence,\n        start: Int, end: Int, x: Float,\n        top: Int, y: Int, bottom: Int, paint: Paint\n    ) {\n        // image to draw\n        val b = drawable\n        // font metrics of text to be replaced\n        val fm = paint.fontMetricsInt\n        val transY = ((y + fm.descent + y + fm.ascent) / 2\n                - b.bounds.bottom / 2)\n        canvas.save()\n        canvas.translate(x, transY.toFloat())\n        b.draw(canvas)\n        canvas.restore()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/CircleIndexIndicator.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.animation.ValueAnimator\nimport android.view.Gravity\nimport android.view.View\nimport android.widget.FrameLayout\nimport androidx.core.view.isVisible\nimport androidx.viewpager.widget.ViewPager\nimport net.mikaelzero.mojito.interfaces.IIndicator\nimport net.mikaelzero.mojito.tools.Utils\n\n/**\n * @Author: MikaelZero\n * @CreateDate: 2020/6/13 5:39 PM\n * @Description:\n */\nclass CircleIndexIndicator : IIndicator {\n\n    private var circleIndicator: CircleIndicator? = null\n    private var originBottomMargin = 10\n    private var currentBottomMargin = originBottomMargin\n\n    override fun attach(parent: FrameLayout) {\n        originBottomMargin = Utils.dip2px(parent.context, 16f)\n        val indexLp = FrameLayout.LayoutParams(\n            FrameLayout.LayoutParams.WRAP_CONTENT,\n            Utils.dip2px(parent.context, 36f)\n        )\n        indexLp.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL\n        indexLp.bottomMargin = originBottomMargin\n        circleIndicator = CircleIndicator(parent.context)\n        circleIndicator?.gravity = Gravity.CENTER_VERTICAL\n        circleIndicator?.setLayoutParams(indexLp)\n        parent.addView(circleIndicator)\n    }\n\n    override fun onShow(viewPager: ViewPager) {\n        circleIndicator?.isVisible = true\n        circleIndicator?.setViewPager(viewPager)\n    }\n\n    override fun move(moveX: Float, moveY: Float) {\n        if (circleIndicator == null) {\n            return\n        }\n        val indexLp = circleIndicator!!.layoutParams as FrameLayout.LayoutParams\n        currentBottomMargin = Math.round(originBottomMargin - moveY / 6f)\n        if (currentBottomMargin > originBottomMargin) {\n            currentBottomMargin = originBottomMargin\n        }\n        indexLp.bottomMargin = currentBottomMargin\n        circleIndicator?.setLayoutParams(indexLp)\n    }\n\n    override fun fingerRelease(isToMax: Boolean, isToMin: Boolean) {\n        if (circleIndicator == null) {\n            return\n        }\n        var begin = 0\n        var end = 0\n        if (isToMax) {\n            begin = currentBottomMargin\n            end = originBottomMargin\n        }\n        if (isToMin) {\n            circleIndicator!!.visibility = View.GONE\n            return\n        }\n        val indexLp = circleIndicator!!.layoutParams as FrameLayout.LayoutParams\n        val valueAnimator = ValueAnimator.ofInt(begin, end)\n        valueAnimator.addUpdateListener { animation: ValueAnimator ->\n            indexLp.bottomMargin = animation.getAnimatedValue() as Int\n            circleIndicator!!.setLayoutParams(indexLp)\n        }\n        valueAnimator.setDuration(300).start()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/CircleIndicator.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.animation.Animator\nimport android.animation.AnimatorSet\nimport android.animation.ObjectAnimator\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.database.DataSetObserver\nimport android.graphics.Color\nimport android.graphics.drawable.GradientDrawable\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.View\nimport android.view.animation.Interpolator\nimport android.widget.LinearLayout\nimport androidx.viewpager.widget.ViewPager\nimport androidx.viewpager.widget.ViewPager.OnPageChangeListener\nimport com.example.c001apk.compose.util.dp\nimport net.mikaelzero.mojito.R\nimport kotlin.math.abs\n\nclass CircleIndicator @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : LinearLayout(context, attrs, defStyleAttr) {\n\n    private var mViewpager: ViewPager? = null\n    private var mIndicatorBackground: GradientDrawable? = null\n    private var mAnimatorOut: Animator? = null\n    private var mAnimatorIn: Animator? = null\n    private var mImmediateAnimatorOut: Animator? = null\n    private var mImmediateAnimatorIn: Animator? = null\n    private var mIndicatorMargin = -1\n    private var mIndicatorWidth = -1\n    private var mIndicatorHeight = -1\n    private var mLastPosition = -1\n\n    private val mInternalPageChangeListener: OnPageChangeListener = object : OnPageChangeListener {\n        override fun onPageScrolled(\n            position: Int,\n            positionOffset: Float,\n            positionOffsetPixels: Int\n        ) {\n        }\n\n        override fun onPageSelected(position: Int) {\n            if (mViewpager?.adapter == null || (mViewpager?.adapter?.count ?: 0) <= 0) {\n                return\n            }\n            if (mAnimatorIn?.isRunning == true) {\n                mAnimatorIn?.end()\n                mAnimatorIn?.cancel()\n            }\n            if (mAnimatorOut?.isRunning == true) {\n                mAnimatorOut?.end()\n                mAnimatorOut?.cancel()\n            }\n            val currentIndicator: View? = getChildAt(mLastPosition)\n            if (mLastPosition >= 0 && currentIndicator != null) {\n                currentIndicator.background = mIndicatorBackground\n                mAnimatorIn?.setTarget(currentIndicator)\n                mAnimatorIn?.start()\n            }\n            val selectedIndicator = getChildAt(position)\n            if (selectedIndicator != null) {\n                selectedIndicator.background = mIndicatorBackground\n                mAnimatorOut?.setTarget(selectedIndicator)\n                mAnimatorOut?.start()\n            }\n            mLastPosition = position\n        }\n\n        override fun onPageScrollStateChanged(state: Int) {}\n    }\n    val dataSetObserver: DataSetObserver = object : DataSetObserver() {\n        override fun onChanged() {\n            super.onChanged()\n            if (mViewpager == null) {\n                return\n            }\n            val newCount = mViewpager?.adapter?.count ?: 0\n            val currentCount = childCount\n            mLastPosition = if (newCount == currentCount) {  // No change\n                return\n            } else if (mLastPosition < newCount) {\n                mViewpager?.currentItem ?: 0\n            } else {\n                -1\n            }\n            createIndicators()\n        }\n    }\n\n    init {\n        init(context, attrs)\n    }\n\n    private fun init(context: Context, attrs: AttributeSet?) {\n        mIndicatorBackground = GradientDrawable()\n        mIndicatorBackground?.setShape(GradientDrawable.OVAL)\n        mIndicatorBackground?.setColor(Color.WHITE)\n        handleTypedArray(context, attrs)\n        checkIndicatorConfig()\n    }\n\n    private fun handleTypedArray(context: Context, attrs: AttributeSet?) {\n        if (attrs == null) {\n            return\n        }\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleIndicator)\n        mIndicatorWidth = typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_width, -1)\n        mIndicatorHeight =\n            typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_height, -1)\n        mIndicatorMargin =\n            typedArray.getDimensionPixelSize(R.styleable.CircleIndicator_ci_margin, -1)\n        val orientation = typedArray.getInt(R.styleable.CircleIndicator_ci_orientation, -1)\n        setOrientation(if (orientation == VERTICAL) VERTICAL else HORIZONTAL)\n        val gravity = typedArray.getInt(R.styleable.CircleIndicator_ci_gravity, -1)\n        setGravity(if (gravity >= 0) gravity else Gravity.CENTER)\n        typedArray.recycle()\n    }\n\n    /**\n     * Create and configure Indicator in Java code.\n     */\n    fun configureIndicator(indicatorWidth: Int, indicatorHeight: Int, indicatorMargin: Int) {\n        mIndicatorWidth = indicatorWidth\n        mIndicatorHeight = indicatorHeight\n        mIndicatorMargin = indicatorMargin\n        checkIndicatorConfig()\n    }\n\n    private fun checkIndicatorConfig() {\n        mIndicatorWidth =\n            if (mIndicatorWidth < 0) 5.dp else mIndicatorWidth\n        mIndicatorHeight =\n            if (mIndicatorHeight < 0) 5.dp else mIndicatorHeight\n        mIndicatorMargin =\n            if (mIndicatorMargin < 0) 5.dp else mIndicatorMargin\n        mAnimatorOut = createAnimatorOut()\n        mImmediateAnimatorOut = createAnimatorOut()\n        mImmediateAnimatorOut?.setDuration(0)\n        mAnimatorIn = createAnimatorIn()\n        mImmediateAnimatorIn = createAnimatorIn()\n        mImmediateAnimatorIn?.setDuration(0)\n    }\n\n    @SuppressLint(\"ObjectAnimatorBinding\")\n    private fun createAnimatorOut(): Animator {\n        val alphaAnima = ObjectAnimator.ofFloat(null, \"alpha\", .5f, 1f)\n        val scaleX = ObjectAnimator.ofFloat(null, \"scaleX\", 1.0f, 1.8f)\n        val scaleY = ObjectAnimator.ofFloat(null, \"scaleY\", 1.0f, 1.8f)\n        val animatorOut = AnimatorSet()\n        animatorOut.play(alphaAnima).with(scaleX).with(scaleY)\n        return animatorOut\n    }\n\n    private fun createAnimatorIn(): Animator {\n        val animatorIn = createAnimatorOut()\n        animatorIn.interpolator =\n            ReverseInterpolator()\n        return animatorIn\n    }\n\n    fun setViewPager(viewPager: ViewPager?) {\n        mViewpager = viewPager\n        if (mViewpager != null && mViewpager?.adapter != null) {\n            mLastPosition = -1\n            createIndicators()\n            mViewpager?.removeOnPageChangeListener(mInternalPageChangeListener)\n            mViewpager?.addOnPageChangeListener(mInternalPageChangeListener)\n            mInternalPageChangeListener.onPageSelected(mViewpager?.currentItem ?: 0)\n        }\n    }\n\n    @Deprecated(\"User ViewPager addOnPageChangeListener\")\n    fun setOnPageChangeListener(onPageChangeListener: OnPageChangeListener?) {\n        if (mViewpager == null) {\n            throw NullPointerException(\"can not find Viewpager , setViewPager first\")\n        }\n        onPageChangeListener?.let {\n            mViewpager?.removeOnPageChangeListener(it)\n            mViewpager?.addOnPageChangeListener(it)\n        }\n    }\n\n    private fun createIndicators() {\n        removeAllViews()\n        val count = mViewpager?.adapter?.count ?: 0\n        if (count <= 0) {\n            return\n        }\n        val currentItem = mViewpager?.currentItem ?: 0\n        val orientation = orientation\n        for (i in 0 until count) {\n            if (currentItem == i) {\n                addIndicator(orientation, mImmediateAnimatorOut)\n            } else {\n                addIndicator(orientation, mImmediateAnimatorIn)\n            }\n        }\n    }\n\n    private fun addIndicator(orientation: Int, animator: Animator?) {\n        if (animator?.isRunning == true) {\n            animator.end()\n            animator.cancel()\n        }\n        val indicator = View(context)\n        indicator.background = mIndicatorBackground\n        addView(indicator, mIndicatorWidth, mIndicatorHeight)\n        val lp = indicator.layoutParams as LayoutParams\n        if (orientation == HORIZONTAL) {\n            lp.leftMargin = mIndicatorMargin\n            lp.rightMargin = mIndicatorMargin\n        } else {\n            lp.topMargin = mIndicatorMargin\n            lp.bottomMargin = mIndicatorMargin\n        }\n        indicator.setLayoutParams(lp)\n        animator?.setTarget(indicator)\n        animator?.start()\n    }\n\n    private class ReverseInterpolator : Interpolator {\n        override fun getInterpolation(value: Float): Float {\n            return abs((1.0f - value).toDouble()).toFloat()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/LinkTextView.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Color\nimport android.text.Selection\nimport android.text.Spannable\nimport android.text.method.LinkMovementMethod\nimport android.text.method.Touch\nimport android.text.style.ClickableSpan\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.MotionEvent\nimport android.widget.TextView\nimport com.example.c001apk.compose.util.SpannableStringBuilderUtil\n\n//https://stackoverflow.com/questions/8558732\nclass LinkTextView : androidx.appcompat.widget.AppCompatTextView {\n\n    override fun getHighlightColor(): Int {\n        return Color.TRANSPARENT\n    }\n\n    private var dontConsumeNonUrlClicks = true\n    var linkHit = false\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) :\n            super(context, attrs, defStyleAttr)\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        linkHit = false\n        val res = super.onTouchEvent(event)\n        return if (dontConsumeNonUrlClicks) linkHit else res\n    }\n\n    class LocalLinkMovementMethod(private val isReply: Boolean) : LinkMovementMethod() {\n        override fun onTouchEvent(\n            widget: TextView,\n            buffer: Spannable, event: MotionEvent\n        ): Boolean {\n            val action = event.action\n            if (action == MotionEvent.ACTION_UP ||\n                action == MotionEvent.ACTION_DOWN\n            ) {\n                var x = event.x.toInt()\n                var y = event.y.toInt()\n                x -= widget.totalPaddingLeft\n                y -= widget.totalPaddingTop\n                x += widget.scrollX\n                y += widget.scrollY\n                val layout = widget.layout\n                val isOutOfLineBounds: Boolean = if (y < 0 || y > layout.height) {\n                    true\n                } else {\n                    val line = layout.getLineForVertical(y)\n                    (x < layout.getLineLeft(line) || x > layout.getLineRight(line))\n                }\n                if (isOutOfLineBounds) {\n                    Selection.removeSelection(buffer)\n                    return Touch.onTouchEvent(widget, buffer, event)\n                }\n                val line = layout.getLineForVertical(y)\n                val off = layout.getOffsetForHorizontal(line, x.toFloat())\n                val link = buffer.getSpans(\n                    off, off, ClickableSpan::class.java\n                )\n                if (link.isNotEmpty()) {\n                    if (action == MotionEvent.ACTION_UP) {\n                        link[0].onClick(widget)\n                    } else {\n                        /*Selection.setSelection(\n                            buffer,\n                            buffer.getSpanStart(link[0]),\n                            buffer.getSpanEnd(link[0])\n                        )*/\n                    }\n                    val linkText =\n                        buffer.substring(buffer.getSpanStart(link[0]), buffer.getSpanEnd(link[0]))\n                    if (widget is LinkTextView) {\n                        widget.linkHit = if (isReply) true else linkText != \"查看更多\"\n                    }\n                    return true\n                } else {\n                    Selection.removeSelection(buffer)\n                    return Touch.onTouchEvent(widget, buffer, event)\n                }\n            }\n            return super.onTouchEvent(widget, buffer, event)\n        }\n\n        companion object {\n            private var sInstance: LocalLinkMovementMethod? = null\n            private var rInstance: LocalLinkMovementMethod? = null\n            val instance: LocalLinkMovementMethod?\n                get() {\n                    if (sInstance == null) sInstance = LocalLinkMovementMethod(false)\n                    return sInstance\n                }\n\n            val instanceR: LocalLinkMovementMethod?\n                get() {\n                    if (rInstance == null) rInstance = LocalLinkMovementMethod(true)\n                    return rInstance\n                }\n        }\n    }\n\n    fun setParams(\n        isReply: Boolean = false,\n        size: Float,\n        fontScale: Float,\n    ) {\n        movementMethod =\n            if (isReply) LocalLinkMovementMethod.instanceR else LocalLinkMovementMethod.instance\n        setTextSize(TypedValue.COMPLEX_UNIT_SP, size * fontScale)\n    }\n\n\n    fun setSpText(\n        text: String,\n        color: Int,\n        onShowTotalReply: (() -> Unit)? = null,\n        onOpenLink: (String, String?) -> Unit,\n        onShowImages: (String) -> Unit,\n    ) {\n        val spText = SpannableStringBuilderUtil.setText(\n            context,\n            text,\n            textSize,\n            color,\n            onShowTotalReply,\n            onOpenLink,\n            onShowImages,\n        )\n        setText(spText)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/MyURLSpan.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.text.TextPaint\nimport android.text.style.ClickableSpan\nimport android.view.View\n\n/**\n * Created by bggRGjQaUbCoE on 2024/6/4\n */\nclass MyURLSpan(\n    private val mUrl: String,\n    private val linkTextColor: Int,\n    private val onShowTotalReply: (() -> Unit)? = null,\n    private val onOpenLink: (String, String?) -> Unit,\n    private val onShowImages: (String) -> Unit,\n) :\n    ClickableSpan() {\n\n    override fun onClick(widget: View) {\n        if (mUrl.isNotEmpty()) {\n            when {\n                mUrl.contains(\"/feed/replyList\") -> onShowTotalReply?.let { it() }\n\n                mUrl.contains(\"image.coolapk.com\") -> onShowImages(mUrl)\n\n                else -> onOpenLink(mUrl, null)\n            }\n        }\n    }\n\n    override fun updateDrawState(ds: TextPaint) {\n        super.updateDrawState(ds)\n        ds.color = linkTextColor\n        ds.isUnderlineText = false\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/NestedScrollableHost.kt",
    "content": "package com.example.c001apk.compose.view\n\n/*\n * Copyright 2019 The Android Open Source Project\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewConfiguration\nimport android.widget.FrameLayout\nimport androidx.viewpager2.widget.ViewPager2\nimport androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL\nimport com.example.c001apk.compose.R\nimport kotlin.math.absoluteValue\nimport kotlin.math.sign\n\n/**\n * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem\n * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as\n * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.\n *\n * This solution has limitations when using multiple levels of nested scrollable elements\n * (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).\n */\nclass NestedScrollableHost : FrameLayout {\n    constructor(context: Context) : super(context)\n\n    @SuppressLint(\"CustomViewStyleable\")\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {\n        val a = context.obtainStyledAttributes(attrs, R.styleable.NestedScrollableHost)\n        isChildHasSameDirection =\n            a.getBoolean(R.styleable.NestedScrollableHost_sameDirectionWithParent, false)\n        isChildVerticalScroll =\n            a.getBoolean(R.styleable.NestedScrollableHost_childVerticalScroll, false)\n        a.recycle()\n    }\n\n    // ViewPager2与RecyclerView的滑动方向是否一致\n    private var isChildHasSameDirection = false\n    private var isChildVerticalScroll = false\n    private var touchSlop = 0\n    private var initialX = 0f\n    private var initialY = 0f\n    private val parentViewPager: ViewPager2?\n        get() {\n            var v: View? = parent as? View\n            while (v != null && v !is ViewPager2) {\n                v = v.parent as? View\n            }\n            return v as? ViewPager2\n        }\n\n    private val child: View? get() = if (childCount > 0) getChildAt(0) else null\n\n    init {\n        touchSlop = ViewConfiguration.get(context).scaledTouchSlop\n    }\n\n    private fun canChildScroll(orientation: Int, delta: Float): Boolean {\n        val direction = -delta.sign.toInt()\n        return when (orientation) {\n            0 -> child?.canScrollHorizontally(direction) ?: false\n            1 -> child?.canScrollVertically(direction) ?: false\n            else -> throw IllegalArgumentException()\n        }\n    }\n\n    override fun onInterceptTouchEvent(e: MotionEvent): Boolean {\n        handleInterceptTouchEvent(e)\n        return super.onInterceptTouchEvent(e)\n    }\n\n    private fun handleInterceptTouchEvent(e: MotionEvent) {\n        val orientation = parentViewPager?.orientation ?: return\n\n        /*// Early return if child can't scroll in same direction as parent\n        if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {\n            return\n        }*/\n        // Early return if child can't scroll in its direction\n        val childOrientation = if (isChildHasSameDirection) orientation else orientation xor 1\n        if (!canChildScroll(childOrientation, -1f) && !canChildScroll(childOrientation, 1f)) {\n            return\n        }\n\n        if (e.action == MotionEvent.ACTION_DOWN) {\n            initialX = e.x\n            initialY = e.y\n            parent.requestDisallowInterceptTouchEvent(true)\n        } else if (e.action == MotionEvent.ACTION_MOVE) {\n            val dx = e.x - initialX\n            val dy = e.y - initialY\n            val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL\n\n            // assuming ViewPager2 touch-slop is 2x touch-slop of child\n            val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f\n            val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f\n\n            if (scaledDx > touchSlop || scaledDy > touchSlop) {\n                if (isVpHorizontal == (scaledDy > scaledDx)) {\n                    // Gesture is perpendicular, allow all parents to intercept\n                    // vertical scroll // true -> child scroll\n                    parent.requestDisallowInterceptTouchEvent(isChildVerticalScroll)\n                } else {\n                    // Gesture is parallel, query child if movement in that direction is possible\n                    if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {\n                        // Child can scroll, disallow all parents to intercept\n                        parent.requestDisallowInterceptTouchEvent(true)\n                    } else {\n                        // Child cannot scroll, allow all parents to intercept\n                        parent.requestDisallowInterceptTouchEvent(false)\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/NineGridImageView.kt",
    "content": "package com.example.c001apk.compose.view\n\n/*\nMIT License\n\nCopyright (c) 2021 Plain\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n*/\n\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport androidx.appcompat.content.res.AppCompatResources\nimport androidx.core.view.setPadding\nimport coil.load\nimport coil.request.CachePolicy\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.constant.Constants.EMPTY_STRING\nimport com.example.c001apk.compose.constant.Constants.SUFFIX_GIF\nimport com.example.c001apk.compose.constant.Constants.SUFFIX_THUMBNAIL\nimport com.example.c001apk.compose.util.CookieUtil\nimport com.example.c001apk.compose.util.ImageShowUtil\nimport com.example.c001apk.compose.util.ImageShowUtil.getImageLp\nimport com.example.c001apk.compose.util.dp\nimport com.example.c001apk.compose.util.http2https\nimport jp.wasabeef.transformers.coil.ColorFilterTransformation\n\nclass NineGridImageView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : ViewGroup(context, attrs, defStyleAttr) {\n\n    var userAgent = \"\"\n\n    var isSingle = false\n\n    private var urlList: List<String>? = null\n\n    var imgHeight = 1\n    var imgWidth = 1\n\n    private var singleWidth = 0\n    private var singleHeight = 0\n\n    private var totalWidth = 0\n\n    private var columns: Int = 0\n    private var rows: Int = 0\n\n    private var gap: Int = 3.dp\n\n    var colorPrimaryContainer: Int = Color.BLACK\n    var colorOnPrimaryContainer: Int = Color.WHITE\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        val sizeWidth = MeasureSpec.getSize(widthMeasureSpec)\n        totalWidth = sizeWidth - paddingLeft - paddingRight\n        var defaultWidth = (totalWidth - gap * (3 - 1)) / 3\n        if (!urlList.isNullOrEmpty()) {\n            val childrenCount = urlList?.size ?: 0\n            if (childrenCount == 1) {\n                if (isSingle) {\n                    singleWidth = totalWidth\n                    singleHeight =\n                        if (imgHeight >= imgWidth * 22f / 9f)\n                            totalWidth * 22 / 9\n                        else totalWidth * imgHeight / imgWidth\n                } else if (imgHeight < imgWidth) {\n                    singleHeight = defaultWidth * 2\n                    singleWidth = singleHeight * imgWidth / imgHeight\n                    if (singleWidth > totalWidth) {\n                        singleWidth = totalWidth\n                        singleHeight = singleWidth * imgHeight / imgWidth\n                    }\n                } else if (imgHeight > imgWidth) {\n                    if (imgHeight < imgWidth * 1.5) {\n                        singleWidth = defaultWidth * 2\n                        singleHeight = singleWidth * imgHeight / imgWidth\n                    } else if (imgHeight <= imgWidth * 22f / 9f) {\n                        singleWidth = defaultWidth\n                        singleHeight = singleWidth * imgHeight / imgWidth\n                    } else {\n                        singleWidth = defaultWidth\n                        singleHeight = singleWidth * 22 / 9\n                    }\n                } else {\n                    singleWidth = defaultWidth * 2\n                    singleHeight = defaultWidth * 2\n                }\n            } else if (childrenCount == 2) {\n                defaultWidth = (totalWidth - gap * (2 - 1)) / 2\n                singleWidth = defaultWidth\n                singleHeight = defaultWidth\n            } else {\n                singleWidth = defaultWidth\n                singleHeight = defaultWidth\n            }\n            measureChildren(\n                MeasureSpec.makeMeasureSpec(singleWidth, MeasureSpec.EXACTLY),\n                MeasureSpec.makeMeasureSpec(singleHeight, MeasureSpec.EXACTLY)\n            )\n            val measureHeight: Int = singleHeight * rows + gap * (rows - 1)\n            setMeasuredDimension(sizeWidth, measureHeight)\n        }\n    }\n\n    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {\n        layoutChildrenView()\n    }\n\n    private fun findPosition(childNum: Int): IntArray {\n        val position = IntArray(2)\n        for (i in 0 until rows) {\n            for (j in 0 until columns) {\n                if (i * columns + j == childNum) {\n                    position[0] = i //行\n                    position[1] = j //列\n                    break\n                }\n            }\n        }\n        return position\n    }\n\n    private fun layoutChildrenView() {\n        if (urlList.isNullOrEmpty()) {\n            return\n        }\n        val childrenCount = urlList?.size ?: 0\n        for (i in 0 until childrenCount) {\n            val position = findPosition(i)\n            val left = (singleWidth + gap) * position[1] + paddingLeft\n            val top = (singleHeight + gap) * position[0] + paddingTop\n            val right = left + singleWidth\n            val bottom = top + singleHeight\n            val childrenView = getChildAt(i) as ImageView\n            if (childrenCount == 1) {\n                //只有一张图片\n                childrenView.scaleType = ImageView.ScaleType.FIT_CENTER\n            } else {\n                childrenView.scaleType = ImageView.ScaleType.CENTER_CROP\n            }\n            urlList?.let { urlList ->\n                childrenView.setOnClickListener {\n                    ImageShowUtil.startBigImgView(\n                        this,\n                        childrenView,\n                        urlList,\n                        i,\n                        userAgent = userAgent,\n                    )\n                }\n            }\n            childrenView.layout(left, top, right, bottom)\n        }\n    }\n\n\n    private fun generateChildrenLayout(length: Int) {\n        if (length <= 3) {\n            rows = 1\n            columns = length\n        } else if (length <= 6) {\n            rows = 2\n            columns = 3\n            if (length == 4) {\n                columns = 2\n            }\n        } else {\n            rows = 3\n            columns = 3\n        }\n    }\n\n    fun getImageViews(): List<ImageView> {\n        val imageViews = mutableListOf<ImageView>()\n        for (i in 0 until childCount) {\n            val imageView = getChildAt(i)\n            if (imageView is ImageView) {\n                imageViews.add(imageView)\n            }\n        }\n        return imageViews\n    }\n\n    fun getImageViewAt(position: Int) = getChildAt(position) as? ImageView\n\n    fun setUrlList(urlList: List<String>?) {\n        if (urlList != null) {\n            this.urlList = urlList.map { it.http2https }\n            generateChildrenLayout(urlList.size)\n            removeAllViews()\n\n            this.urlList?.forEach {\n                val imageView = BadgedImageView(context)\n                imageView.apply {\n                    colorPrimaryContainer = this@NineGridImageView.colorPrimaryContainer\n                    colorOnPrimaryContainer = this@NineGridImageView.colorOnPrimaryContainer\n                    setCornerRadius(12.dp)\n                    setBorderWidth(1.dp)\n                    setPadding(1.dp)\n                    setBorderColor(context.getColor(R.color.image_stroke))\n                    background =\n                        AppCompatResources.getDrawable(context, R.drawable.round_corners_12)\n                    foreground =\n                        AppCompatResources.getDrawable(context, R.drawable.selector_bg_12_trans)\n                    scaleType = ImageView.ScaleType.CENTER_CROP\n                    val replace = it.replace(SUFFIX_THUMBNAIL, EMPTY_STRING)\n                    val imageLp = getImageLp(replace)\n                    imgWidth = imageLp.first\n                    imgHeight = imageLp.second\n                    if (replace.endsWith(SUFFIX_GIF))\n                        setBadge(\"GIF\")\n                    else if (imgHeight > imgWidth * 22f / 9f)\n                        setBadge(\"长图\")\n                }\n                addView(imageView, generateDefaultLayoutParams())\n\n                imageView.load(it) {\n                    memoryCachePolicy(CachePolicy.ENABLED)\n                    diskCachePolicy(CachePolicy.ENABLED)\n                    memoryCacheKey(it)\n                    diskCacheKey(it)\n                    crossfade(200)\n                    if (CookieUtil.isDarkMode && CookieUtil.imageFilter)\n                        transformations(ColorFilterTransformation(Color.parseColor(\"#2D000000\")))\n                    addHeader(\"User-Agent\", userAgent)\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/RoundedImageView.kt",
    "content": "package com.example.c001apk.compose.view\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.graphics.PorterDuff\nimport android.graphics.PorterDuffXfermode\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.widget.ImageView\nimport androidx.annotation.ColorInt\nimport androidx.annotation.IntRange\nimport androidx.annotation.Px\nimport kotlin.math.max\n\n/**\n * 圆角view\n */\n// https://github.com/weimingjue/RoundedImageView\nopen class RoundedImageView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) :\n    ImageView(context, attrs, defStyleAttr) {\n    private var radii: FloatArray? = null\n    private val radiusPath = Path()\n    private val radiusPaint = Paint(Paint.ANTI_ALIAS_FLAG)\n    private val borderPath = Path()\n    private val borderPaint = Paint(Paint.ANTI_ALIAS_FLAG)\n\n    private var isOval: Boolean = false\n\n    init {\n        super.setCropToPadding(true)//false在使用crop属性时padding会无效，难以计算\n\n        borderPaint.style = Paint.Style.STROKE\n\n        radiusPath.fillType = Path.FillType.INVERSE_WINDING\n        radiusPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        if (w != oldw || h != oldh) {//init完第一次layout时也会走一遍\n            newRadiusBorderPath()\n        }\n    }\n\n    private fun setCornerRadiusPrivate(\n        @Px @IntRange(from = 0) topLeft: Int,\n        @Px @IntRange(from = 0) topRight: Int,\n        @Px @IntRange(from = 0) bottomLeft: Int,\n        @Px @IntRange(from = 0) bottomRight: Int,\n        isInvalidate: Boolean\n    ) {\n        if ((topLeft <= 0) and (topRight <= 0) and (bottomLeft <= 0) and (bottomRight <= 0)) {\n            radii = null\n        } else {\n            if (radii == null) {\n                radii = FloatArray(8)\n            }\n            radii?.run {\n                if ((this[0] == topLeft.toFloat()) and (this[2] == topRight.toFloat()) and\n                    (this[4] == bottomLeft.toFloat()) and (this[6] == bottomRight.toFloat())\n                ) {\n                    return\n                }\n                this[0] = topLeft.toFloat()\n                this[1] = this[0]\n                this[2] = topRight.toFloat()\n                this[3] = this[2]\n                this[4] = bottomRight.toFloat()\n                this[5] = this[4]\n                this[6] = bottomLeft.toFloat()\n                this[7] = this[6]\n            }\n        }\n        if (isInvalidate) {\n            newRadiusBorderPath()\n            invalidate()\n        }\n    }\n\n    /**\n     * 重新计算圆角及path\n     */\n    protected open fun newRadiusBorderPath() {\n        if (width == 0 || height == 0) {\n            return\n        }\n\n        val stroke2 = (borderPaint.strokeWidth - 2) / 2\n\n        if (isOval) {//椭圆效果\n            val rx = (width - paddingStart - paddingEnd) / 2f\n            val ry = (height - paddingTop - paddingBottom) / 2f\n\n            radiusPath.reset()\n            radiusPath.addRoundRect(\n                RectF(\n                    paddingStart.toFloat(),\n                    paddingTop.toFloat(),\n                    width - paddingEnd.toFloat(),\n                    height - paddingBottom.toFloat()\n                ),\n                rx,\n                ry,\n                Path.Direction.CW\n            )\n\n            borderPath.reset()\n            borderPath.addRoundRect(\n                RectF(\n                    paddingStart + stroke2,\n                    paddingTop + stroke2,\n                    width - paddingEnd - stroke2,\n                    height - paddingBottom - stroke2\n                ),\n                if (rx - stroke2 < 0) 0f else (rx - stroke2),\n                if (ry - stroke2 < 0) 0f else (ry - stroke2),\n                Path.Direction.CW\n            )\n        } else {\n            radii?.run {\n                radiusPath.reset()\n                radiusPath.addRoundRect(\n                    RectF(\n                        paddingStart.toFloat(),\n                        paddingTop.toFloat(),\n                        width - paddingEnd.toFloat(),\n                        height - paddingBottom.toFloat()\n                    ),\n                    this,\n                    Path.Direction.CW\n                )\n\n                borderPath.reset()\n                val topLeft = if (this[0] - stroke2 < 0) 0f else (this[0] - stroke2)\n                val topRight = if (this[2] - stroke2 < 0) 0f else (this[2] - stroke2)\n                val bottomLeft = if (this[4] - stroke2 < 0) 0f else (this[4] - stroke2)\n                val bottomRight = if (this[6] - stroke2 < 0) 0f else (this[6] - stroke2)\n                borderPath.addRoundRect(\n                    RectF(\n                        paddingStart + stroke2,\n                        paddingTop + stroke2,\n                        width - paddingEnd - stroke2,\n                        height - paddingBottom - stroke2\n                    ),\n                    floatArrayOf(\n                        topLeft,\n                        topLeft,\n                        topRight,\n                        topRight,\n                        bottomLeft,\n                        bottomLeft,\n                        bottomRight,\n                        bottomRight\n                    ),\n                    Path.Direction.CW\n                )\n            }\n        }\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        val saveCount = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), null)\n        super.onDraw(canvas)\n        if (isOval || radii != null) {\n            canvas.drawPath(radiusPath, radiusPaint)\n        }\n        canvas.restoreToCount(saveCount)\n        if (borderPaint.strokeWidth > 0) {\n            canvas.drawPath(borderPath, borderPaint)\n        }\n    }\n\n    override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {\n        super.setPadding(left, top, right, bottom)\n        newRadiusBorderPath()\n    }\n\n    @Deprecated(\"设置false的效果也不好，并且会导致padding难以计算，所以暂不支持设置\")\n    override fun setCropToPadding(cropToPadding: Boolean) {//空实现\n    }\n\n    /////////////////////////////////////////////////////////////////////////////////////////////////\n    // 公共方法（public method）\n    /////////////////////////////////////////////////////////////////////////////////////////////////\n\n    /**\n     * =0表示无描边\n     */\n    fun setBorderWidth(@Px @IntRange(from = 0) borderWidth: Int) {\n        borderPaint.strokeWidth = max(0f, borderWidth.toFloat())\n        invalidate()\n    }\n\n    fun setBorderColor(@ColorInt borderColor: Int) {\n        borderPaint.color = borderColor\n        invalidate()\n    }\n\n    /**\n     * =0表示无圆角\n     */\n    fun setCornerRadius(@Px @IntRange(from = 0) radius: Int) {\n        setCornerRadius(radius, radius, radius, radius)\n    }\n\n    fun setCornerRadius(\n        @Px @IntRange(from = 0) topLeft: Int,\n        @Px @IntRange(from = 0) topRight: Int,\n        @Px @IntRange(from = 0) bottomLeft: Int,\n        @Px @IntRange(from = 0) bottomRight: Int\n    ) {\n        setCornerRadiusPrivate(topLeft, topRight, bottomLeft, bottomRight, true)\n    }\n\n    /**\n     * 是否以椭圆效果绘制\n     */\n    fun setIsOval(isOval: Boolean) {\n        if (this.isOval == isOval) {\n            return\n        }\n        this.isOval = isOval\n        newRadiusBorderPath()\n        invalidate()\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/SmoothInputLayout.kt",
    "content": "/*\n * Copyright (C) 2015 AlexMofer\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage com.example.c001apk.compose.view\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.LinearLayout\nimport com.example.c001apk.compose.R\nimport com.example.c001apk.compose.util.dp\n\n/**\n * 顺滑的输入面板\n * Created by Alex on 2016/12/4.\n */\n@Suppress(\"unused\")\nclass SmoothInputLayout : LinearLayout {\n    private var mMaxKeyboardHeight = Int.MIN_VALUE\n    private var mDefaultKeyboardHeight = 0\n    private var mMinKeyboardHeight = 0\n    private var mKeyboardHeight = 0\n        get() = if (field < 276.dp) 276.dp\n        else field\n    private var mInputViewId = 0\n    private var mInputView: View? = null\n    private val imm by lazy {\n        context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager\n    }\n\n\n    /**\n     * 是否输入法已打开\n     *\n     * @return 是否输入法已打开\n     */\n    var isKeyBoardOpen = false\n    private var mEmojiPanelId = 0\n    private var mEmojiPanel: View? = null\n    private var mListener: OnVisibilityChangeListener? = null\n    private var keyboardChangeListener: OnKeyboardChangeListener? = null\n    private var mAutoSaveKeyboardHeight = false\n    private var mKeyboardProcessor: KeyboardProcessor? = null\n    private var isShowEmojiPanel = false\n\n    constructor(context: Context) : super(context) {\n        initView(null)\n    }\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {\n        initView(attrs)\n    }\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    ) {\n        initView(attrs)\n    }\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int,\n        defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes) {\n        initView(attrs)\n    }\n\n    private fun initView(attrs: AttributeSet?) {\n        var defaultInputHeight =\n            (DEFAULT_KEYBOARD_HEIGHT * resources.displayMetrics.density).toInt()\n        var minInputHeight = (MIN_KEYBOARD_HEIGHT * resources.displayMetrics.density).toInt()\n        mInputViewId = NO_ID\n        mEmojiPanelId = NO_ID\n        val autoSave: Boolean\n        val custom = context.obtainStyledAttributes(attrs, R.styleable.SmoothInputLayout)\n        defaultInputHeight = custom.getDimensionPixelOffset(\n            R.styleable.SmoothInputLayout_silDefaultKeyboardHeight, defaultInputHeight\n        )\n        minInputHeight = custom.getDimensionPixelOffset(\n            R.styleable.SmoothInputLayout_silMinKeyboardHeight,\n            minInputHeight\n        )\n        mInputViewId =\n            custom.getResourceId(R.styleable.SmoothInputLayout_silInputView, mInputViewId)\n        mEmojiPanelId =\n            custom.getResourceId(R.styleable.SmoothInputLayout_silEmojiPanel, mEmojiPanelId)\n        autoSave =\n            custom.getBoolean(R.styleable.SmoothInputLayout_silAutoSaveKeyboardHeight, true)\n        custom.recycle()\n        setDefaultKeyboardHeight(defaultInputHeight)\n        setMinKeyboardHeight(minInputHeight)\n        setAutoSaveKeyboardHeight(autoSave)\n    }\n\n    override fun onFinishInflate() {\n        super.onFinishInflate()\n        if (mInputViewId != NO_ID) {\n            setInputView(findViewById(mInputViewId))\n        }\n        if (mEmojiPanelId != NO_ID) {\n            setEmojiPanel(findViewById(mEmojiPanelId))\n        }\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        val heightSize = MeasureSpec.getSize(heightMeasureSpec)\n        if (heightSize > mMaxKeyboardHeight) {\n            mMaxKeyboardHeight = heightSize\n        }\n        val heightChange = mMaxKeyboardHeight - heightSize\n        if (heightChange > mMinKeyboardHeight) {\n            if (mKeyboardHeight != heightChange) {\n                mKeyboardHeight = heightChange\n                saveKeyboardHeight()\n            }\n            isKeyBoardOpen = true\n            // 输入法弹出，隐藏功能面板\n            if (mEmojiPanel != null && mEmojiPanel?.visibility == VISIBLE) {\n                mEmojiPanel?.visibility = GONE\n                mListener?.onVisibilityChange(GONE)\n            }\n        } else {\n            isKeyBoardOpen = false\n            if (isShowEmojiPanel) {\n                isShowEmojiPanel = false\n                if (mEmojiPanel != null && mEmojiPanel?.visibility == GONE) {\n                    updateLayout()\n                    mEmojiPanel?.visibility = VISIBLE\n                    mListener?.onVisibilityChange(VISIBLE)\n                    forceLayout()\n                }\n            }\n        }\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        keyboardChangeListener?.onKeyboardChanged(isKeyBoardOpen)\n    }\n\n    private val keyboardSharedPreferences: SharedPreferences\n        /**\n         * 获取键盘SP\n         *\n         * @return 键盘SP\n         */\n        get() = context.getSharedPreferences(SP_KEYBOARD, Context.MODE_PRIVATE)\n\n    /**\n     * 存储键盘高度\n     */\n    private fun saveKeyboardHeight() {\n        if (mAutoSaveKeyboardHeight)\n            keyboardSharedPreferences.edit().putInt(KEY_HEIGHT, mKeyboardHeight).apply()\n        else\n            mKeyboardProcessor?.onSaveKeyboardHeight(mKeyboardHeight)\n    }\n\n    /**\n     * 更新子项高度\n     */\n    private fun updateLayout() {\n        if (mEmojiPanel == null) return\n        if (mKeyboardHeight == 0) mKeyboardHeight = getKeyboardHeight(mDefaultKeyboardHeight)\n        val layoutParams = mEmojiPanel?.layoutParams\n        if (layoutParams != null) {\n            layoutParams.height = mKeyboardHeight\n            mEmojiPanel?.setLayoutParams(layoutParams)\n        }\n    }\n\n    private fun getKeyboardHeight(defaultHeight: Int): Int {\n        return if (mAutoSaveKeyboardHeight)\n            keyboardSharedPreferences.getInt(KEY_HEIGHT, defaultHeight)\n        else if (mKeyboardProcessor != null)\n            mKeyboardProcessor?.getSavedKeyboardHeight(defaultHeight) ?: defaultHeight\n        else defaultHeight\n    }\n\n    /**\n     * 设置默认系统输入面板高度\n     *\n     * @param height 输入面板高度\n     */\n    private fun setDefaultKeyboardHeight(height: Int) {\n        if (mDefaultKeyboardHeight != height)\n            mDefaultKeyboardHeight = height\n    }\n\n    /**\n     * 设置最小系统输入面板高度\n     *\n     * @param height 输入面板高度\n     */\n    private fun setMinKeyboardHeight(height: Int) {\n        if (mMinKeyboardHeight != height)\n            mMinKeyboardHeight = height\n    }\n\n    /**\n     * 设置输入框\n     *\n     * @param edit 输入框\n     */\n    private fun setInputView(edit: View) {\n        if (mInputView !== edit)\n            mInputView = edit\n    }\n\n    /**\n     * 设置特殊输入面板\n     *\n     * @param pane 面板\n     */\n    private fun setEmojiPanel(pane: View) {\n        if (mEmojiPanel !== pane)\n            mEmojiPanel = pane\n    }\n\n    /**\n     * 设置面板可见改变监听\n     *\n     * @param listener 面板可见改变监听\n     */\n    fun setOnVisibilityChangeListener(listener: OnVisibilityChangeListener?) {\n        mListener = listener\n    }\n\n    /**\n     * 设置键盘改变监听\n     *\n     * @param listener 键盘改变监听\n     */\n    fun setOnKeyboardChangeListener(listener: OnKeyboardChangeListener?) {\n        keyboardChangeListener = listener\n    }\n\n    /**\n     * 设置自动保存键盘高度\n     *\n     * @param auto 是否自动\n     */\n    private fun setAutoSaveKeyboardHeight(auto: Boolean) {\n        mAutoSaveKeyboardHeight = auto\n    }\n\n    /**\n     * 设置键盘处理器\n     * 仅在关闭自动保存键盘高度时设置的处理器才有效[.setAutoSaveKeyboardHeight]\n     *\n     * @param processor 处理器\n     */\n    fun setKeyboardProcessor(processor: KeyboardProcessor?) {\n        mKeyboardProcessor = processor\n    }\n\n    val isEmojiPanelOpen: Boolean\n        /**\n         * 是否特殊输入面板已打开\n         *\n         * @return 特殊输入面板已打开\n         */\n        get() = mEmojiPanel != null && mEmojiPanel?.visibility == VISIBLE\n\n    /**\n     * 关闭特殊输入面板\n     */\n    fun closeEmojiPanel() {\n        if (isEmojiPanelOpen) {\n            mEmojiPanel?.visibility = GONE\n            mListener?.onVisibilityChange(GONE)\n        }\n    }\n\n    /**\n     * 显示特殊输入面板\n     *\n     * @param focus 是否让输入框拥有焦点\n     */\n    fun showEmojiPanel(focus: Boolean) {\n        if (isKeyBoardOpen) {\n            isShowEmojiPanel = true\n            imm.hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS)\n        } else {\n            if (mEmojiPanel != null && mEmojiPanel?.visibility == GONE) {\n                updateLayout()\n                mEmojiPanel?.visibility = VISIBLE\n                mListener?.onVisibilityChange(VISIBLE)\n            }\n        }\n        if (focus) {\n            mInputView?.requestFocus()\n            mInputView?.requestFocusFromTouch()\n        } else {\n            if (mInputView != null) {\n                isFocusable = true\n                setFocusableInTouchMode(true)\n                mInputView?.clearFocus()\n            }\n        }\n    }\n\n    /**\n     * 关闭键盘\n     *\n     * @param clearFocus 是否清除输入框焦点\n     */\n    fun closeKeyboard(clearFocus: Boolean) {\n        imm.hideSoftInputFromWindow(windowToken, InputMethodManager.HIDE_NOT_ALWAYS)\n        if (clearFocus && mInputView != null) {\n            isFocusable = true\n            setFocusableInTouchMode(true)\n            mInputView?.clearFocus()\n        }\n    }\n\n    /**\n     * 打开键盘\n     */\n    fun showKeyboard() {\n        mInputView?.let {\n            it.requestFocus()\n            it.requestFocusFromTouch()\n            imm.showSoftInput(it, InputMethodManager.SHOW_IMPLICIT)\n        }\n    }\n\n    /**\n     * 面板可见改变监听\n     */\n    interface OnVisibilityChangeListener {\n        fun onVisibilityChange(visibility: Int)\n    }\n\n    /**\n     * 键盘改变监听\n     */\n    interface OnKeyboardChangeListener {\n        fun onKeyboardChanged(open: Boolean)\n    }\n\n    /**\n     * 键盘处理器\n     */\n    interface KeyboardProcessor {\n        /**\n         * 存储键盘高度\n         *\n         * @param height 高度\n         */\n        fun onSaveKeyboardHeight(height: Int)\n\n        /**\n         * 获取存储的键盘高度\n         *\n         * @param defaultHeight 默认高度\n         * @return 键盘高度\n         */\n        fun getSavedKeyboardHeight(defaultHeight: Int): Int\n    }\n\n    companion object {\n        const val DEFAULT_KEYBOARD_HEIGHT = 387\n        const val MIN_KEYBOARD_HEIGHT = 20\n        private const val SP_KEYBOARD = \"keyboard\"\n        private const val KEY_HEIGHT = \"height\"\n    }\n}"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/circleindicator/BaseCircleIndicator.kt",
    "content": "package com.example.c001apk.compose.view.circleindicator\n\nimport android.animation.Animator\nimport android.animation.AnimatorInflater\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.Gravity\nimport android.view.View\nimport android.view.animation.Interpolator\nimport android.widget.LinearLayout\nimport androidx.annotation.ColorInt\nimport androidx.annotation.DrawableRes\nimport androidx.appcompat.content.res.AppCompatResources\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.view.ViewCompat\nimport com.example.c001apk.compose.R\nimport kotlin.math.abs\n\nopen class BaseCircleIndicator : LinearLayout {\n\n    private var mIndicatorMargin = -1\n    private var mIndicatorWidth = -1\n    private var mIndicatorHeight = -1\n    private var mIndicatorBackgroundResId = 0\n    private var mIndicatorUnselectedBackgroundResId = 0\n    private var mIndicatorTintColor: ColorStateList? = null\n    private var mIndicatorTintUnselectedColor: ColorStateList? = null\n    private var mAnimatorOut: Animator? = null\n    private var mAnimatorIn: Animator? = null\n    private var mImmediateAnimatorOut: Animator? = null\n    private var mImmediateAnimatorIn: Animator? = null\n    var mLastPosition = -1\n    private var mIndicatorCreatedListener: IndicatorCreatedListener? = null\n\n    constructor(context: Context) : super(context) {\n        init(context, null)\n    }\n\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {\n        init(context, attrs)\n    }\n\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    ) {\n        init(context, attrs)\n    }\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int,\n        defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes) {\n        init(context, attrs)\n    }\n\n    private fun init(context: Context, attrs: AttributeSet?) {\n        val config = handleTypedArray(context, attrs)\n        initialize(config)\n        if (isInEditMode) {\n            createIndicators(3, 1)\n        }\n    }\n\n    @SuppressLint(\"ResourceType\")\n    private fun handleTypedArray(context: Context, attrs: AttributeSet?): Config {\n        val config = Config()\n        if (attrs == null) {\n            return config\n        }\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BaseCircleIndicator)\n        config.width =\n            typedArray.getDimensionPixelSize(R.styleable.BaseCircleIndicator_ci_width, -1)\n        config.height =\n            typedArray.getDimensionPixelSize(R.styleable.BaseCircleIndicator_ci_height, -1)\n        config.margin =\n            typedArray.getDimensionPixelSize(R.styleable.BaseCircleIndicator_ci_margin, -1)\n        config.animatorResId = typedArray.getResourceId(\n            R.styleable.BaseCircleIndicator_ci_animator,\n            R.anim.scale_with_alpha\n        )\n        config.animatorReverseResId =\n            typedArray.getResourceId(R.styleable.BaseCircleIndicator_ci_animator_reverse, 0)\n        config.backgroundResId = typedArray.getResourceId(\n            R.styleable.BaseCircleIndicator_ci_drawable,\n            R.drawable.white_radius\n        )\n        config.unselectedBackgroundId = config.backgroundResId\n        //  typedArray.getResourceId(R.styleable.BaseCircleIndicator_ci_drawable_unselected,\n        // config.backgroundResId );\n        config.orientation =\n            -1 //typedArray.getInt(R.styleable.BaseCircleIndicator_ci_orientation, -1);\n        config.gravity = -1 //typedArray.getInt(R.styleable.BaseCircleIndicator_ci_gravity, -1);\n        typedArray.recycle()\n        return config\n    }\n\n    private fun initialize(config: Config) {\n        val miniSize = (TypedValue.applyDimension(\n            TypedValue.COMPLEX_UNIT_DIP,\n            5f, resources.displayMetrics\n        ) + 0.5f).toInt()\n        mIndicatorWidth = if (config.width < 0) miniSize else config.width\n        mIndicatorHeight = if (config.height < 0) miniSize else config.height\n        mIndicatorMargin = if (config.margin < 0) miniSize else config.margin\n        mAnimatorOut = createAnimatorOut(config)\n        mImmediateAnimatorOut = createAnimatorOut(config)\n        mImmediateAnimatorOut?.setDuration(0)\n        mAnimatorIn = createAnimatorIn(config)\n        mImmediateAnimatorIn = createAnimatorIn(config)\n        mImmediateAnimatorIn?.setDuration(0)\n        mIndicatorBackgroundResId =\n            if (config.backgroundResId == 0) R.drawable.white_radius else config.backgroundResId\n        mIndicatorUnselectedBackgroundResId =\n            if (config.unselectedBackgroundId == 0) config.backgroundResId else config.unselectedBackgroundId\n        orientation =\n            if (config.orientation == VERTICAL) VERTICAL else HORIZONTAL\n        gravity = if (config.gravity >= 0) config.gravity else Gravity.CENTER\n    }\n\n    @JvmOverloads\n    fun tintIndicator(\n        @ColorInt indicatorColor: Int,\n        @ColorInt unselectedIndicatorColor: Int = indicatorColor\n    ) {\n        mIndicatorTintColor = ColorStateList.valueOf(indicatorColor)\n        mIndicatorTintUnselectedColor = ColorStateList.valueOf(unselectedIndicatorColor)\n        changeIndicatorBackground()\n    }\n\n    @JvmOverloads\n    fun changeIndicatorResource(\n        @DrawableRes indicatorResId: Int,\n        @DrawableRes indicatorUnselectedResId: Int = indicatorResId\n    ) {\n        mIndicatorBackgroundResId = indicatorResId\n        mIndicatorUnselectedBackgroundResId = indicatorUnselectedResId\n        changeIndicatorBackground()\n    }\n\n    interface IndicatorCreatedListener {\n        /**\n         * IndicatorCreatedListener\n         *\n         * @param view     internal indicator view\n         * @param position position\n         */\n        fun onIndicatorCreated(view: View?, position: Int)\n    }\n\n    fun setIndicatorCreatedListener(\n        indicatorCreatedListener: IndicatorCreatedListener?\n    ) {\n        mIndicatorCreatedListener = indicatorCreatedListener\n    }\n\n    private fun createAnimatorOut(config: Config): Animator {\n        return AnimatorInflater.loadAnimator(context, config.animatorResId)\n    }\n\n    private fun createAnimatorIn(config: Config): Animator {\n        val animatorIn: Animator\n        if (config.animatorReverseResId == 0) {\n            animatorIn = AnimatorInflater.loadAnimator(context, config.animatorResId)\n            animatorIn.interpolator =\n                ReverseInterpolator()\n        } else {\n            animatorIn = AnimatorInflater.loadAnimator(context, config.animatorReverseResId)\n        }\n        return animatorIn\n    }\n\n    fun createIndicators(count: Int, currentPosition: Int) {\n        if (mImmediateAnimatorOut?.isRunning == true) {\n            mImmediateAnimatorOut?.end()\n            mImmediateAnimatorOut?.cancel()\n        }\n        if (mImmediateAnimatorIn?.isRunning == true) {\n            mImmediateAnimatorIn?.end()\n            mImmediateAnimatorIn?.cancel()\n        }\n\n        // Diff View\n        val childViewCount = childCount\n        if (count < childViewCount) {\n            removeViews(count, childViewCount - count)\n        } else if (count > childViewCount) {\n            val addCount = count - childViewCount\n            val orientation = orientation\n            for (i in 0 until addCount) {\n                addIndicator(orientation)\n            }\n        }\n\n        // Bind Style\n        var indicator: View\n        for (i in 0 until count) {\n            indicator = getChildAt(i)\n            if (currentPosition == i) {\n                bindIndicatorBackground(indicator, mIndicatorBackgroundResId, mIndicatorTintColor)\n                mImmediateAnimatorOut?.setTarget(indicator)\n                mImmediateAnimatorOut?.start()\n                mImmediateAnimatorOut?.end()\n            } else {\n                bindIndicatorBackground(\n                    indicator, mIndicatorUnselectedBackgroundResId,\n                    mIndicatorTintUnselectedColor\n                )\n                mImmediateAnimatorIn?.setTarget(indicator)\n                mImmediateAnimatorIn?.start()\n                mImmediateAnimatorIn?.end()\n            }\n            if (mIndicatorCreatedListener != null) {\n                mIndicatorCreatedListener?.onIndicatorCreated(indicator, i)\n            }\n        }\n        mLastPosition = currentPosition\n    }\n\n    private fun addIndicator(orientation: Int) {\n        val indicator = View(context)\n        val params = generateDefaultLayoutParams()\n        params.width = mIndicatorWidth\n        params.height = mIndicatorHeight\n        if (orientation == HORIZONTAL) {\n            params.leftMargin = mIndicatorMargin\n            params.rightMargin = mIndicatorMargin\n        } else {\n            params.topMargin = mIndicatorMargin\n            params.bottomMargin = mIndicatorMargin\n        }\n        addView(indicator, params)\n    }\n\n    fun animatePageSelected(position: Int) {\n        if (mLastPosition == position) {\n            return\n        }\n        if (mAnimatorIn?.isRunning == true) {\n            mAnimatorIn?.end()\n            mAnimatorIn?.cancel()\n        }\n        if (mAnimatorOut?.isRunning == true) {\n            mAnimatorOut?.end()\n            mAnimatorOut?.cancel()\n        }\n        val currentIndicator: View? = getChildAt(mLastPosition)\n        if (mLastPosition >= 0 && currentIndicator != null) {\n            bindIndicatorBackground(\n                currentIndicator, mIndicatorUnselectedBackgroundResId,\n                mIndicatorTintUnselectedColor\n            )\n            mAnimatorIn?.setTarget(currentIndicator)\n            mAnimatorIn?.start()\n        }\n        val selectedIndicator = getChildAt(position)\n        if (selectedIndicator != null) {\n            bindIndicatorBackground(\n                selectedIndicator, mIndicatorBackgroundResId,\n                mIndicatorTintColor\n            )\n            mAnimatorOut?.setTarget(selectedIndicator)\n            mAnimatorOut?.start()\n        }\n        mLastPosition = position\n    }\n\n    private fun changeIndicatorBackground() {\n        val count = childCount\n        if (count <= 0) {\n            return\n        }\n        var currentIndicator: View\n        for (i in 0 until count) {\n            currentIndicator = getChildAt(i)\n            if (i == mLastPosition) {\n                bindIndicatorBackground(\n                    currentIndicator, mIndicatorBackgroundResId,\n                    mIndicatorTintColor\n                )\n            } else {\n                bindIndicatorBackground(\n                    currentIndicator, mIndicatorUnselectedBackgroundResId,\n                    mIndicatorTintUnselectedColor\n                )\n            }\n        }\n    }\n\n    private fun bindIndicatorBackground(\n        view: View, @DrawableRes drawableRes: Int,\n        tintColor: ColorStateList?\n    ) {\n        if (tintColor != null) {\n            val indicatorDrawable =\n                AppCompatResources.getDrawable(context, drawableRes)?.mutate()?.let {\n                    DrawableCompat.wrap(it)\n                }\n            indicatorDrawable?.let {\n                DrawableCompat.setTintList(it, tintColor)\n            }\n            ViewCompat.setBackground(view, indicatorDrawable)\n        } else {\n            view.setBackgroundResource(drawableRes)\n        }\n    }\n\n    protected class ReverseInterpolator : Interpolator {\n        override fun getInterpolation(value: Float): Float {\n            return abs((1.0f - value).toDouble()).toFloat()\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/circleindicator/CircleIndicator3.kt",
    "content": "package com.example.c001apk.compose.view.circleindicator\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager2.widget.ViewPager2\n\n/**\n * CircleIndicator work with ViewPager2\n */\nclass CircleIndicator3 : BaseCircleIndicator {\n    private var mViewpager: ViewPager2? = null\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int\n    ) : super(context, attrs, defStyleAttr)\n\n    constructor(\n        context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int\n    ) : super(context, attrs, defStyleAttr, defStyleRes)\n\n    fun setViewPager(viewPager: ViewPager2?) {\n        mViewpager = viewPager\n        if (mViewpager != null && mViewpager?.adapter != null) {\n            mLastPosition = -1\n            createIndicators()\n            mViewpager?.unregisterOnPageChangeCallback(mInternalPageChangeCallback)\n            mViewpager?.registerOnPageChangeCallback(mInternalPageChangeCallback)\n            mInternalPageChangeCallback.onPageSelected(mViewpager?.currentItem ?: 0)\n        }\n    }\n\n    private fun createIndicators() {\n        val adapter = mViewpager?.adapter\n        val count: Int = adapter?.itemCount ?: 0\n        createIndicators(count, mViewpager?.currentItem ?: 0)\n    }\n\n    private val mInternalPageChangeCallback: ViewPager2.OnPageChangeCallback =\n        object : ViewPager2.OnPageChangeCallback() {\n            override fun onPageSelected(position: Int) {\n                if (position == mLastPosition\n                    || mViewpager?.adapter == null\n                    || (mViewpager?.adapter?.itemCount ?: 0) <= 0\n                ) {\n                    return\n                }\n                animatePageSelected(position)\n            }\n        }\n    val adapterDataObserver: RecyclerView.AdapterDataObserver =\n        object : RecyclerView.AdapterDataObserver() {\n            override fun onChanged() {\n                super.onChanged()\n                if (mViewpager == null) {\n                    return\n                }\n                val adapter = mViewpager?.adapter\n                val newCount = adapter?.itemCount ?: 0\n                val currentCount = childCount\n                mLastPosition = if (newCount == currentCount) {\n                    // No change\n                    return\n                } else if (mLastPosition < newCount) {\n                    mViewpager?.currentItem ?: 0\n                } else {\n                    RecyclerView.NO_POSITION\n                }\n                createIndicators()\n            }\n\n            override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {\n                super.onItemRangeChanged(positionStart, itemCount)\n                onChanged()\n            }\n\n            override fun onItemRangeChanged(\n                positionStart: Int, itemCount: Int,\n                payload: Any?\n            ) {\n                super.onItemRangeChanged(positionStart, itemCount, payload)\n                onChanged()\n            }\n\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                super.onItemRangeInserted(positionStart, itemCount)\n                onChanged()\n            }\n\n            override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {\n                super.onItemRangeRemoved(positionStart, itemCount)\n                onChanged()\n            }\n\n            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n                super.onItemRangeMoved(fromPosition, toPosition, itemCount)\n                onChanged()\n            }\n        }\n}\n"
  },
  {
    "path": "app/src/main/java/com/example/c001apk/compose/view/circleindicator/Config.kt",
    "content": "package com.example.c001apk.compose.view.circleindicator\n\nimport android.annotation.SuppressLint\nimport android.view.Gravity\nimport android.widget.LinearLayout\nimport androidx.annotation.AnimatorRes\nimport androidx.annotation.DrawableRes\nimport com.example.c001apk.compose.R\n\nclass Config internal constructor() {\n    var width = -1\n    var height = -1\n    var margin = -1\n\n    @SuppressLint(\"ResourceType\")\n    @AnimatorRes\n    var animatorResId = R.anim.scale_with_alpha\n\n    @AnimatorRes\n    var animatorReverseResId = 0\n\n    @DrawableRes\n    var backgroundResId = R.drawable.white_radius\n\n    @DrawableRes\n    var unselectedBackgroundId = 0\n    var orientation = LinearLayout.HORIZONTAL\n    var gravity = Gravity.CENTER\n\n    class Builder {\n        private val mConfig: Config = Config()\n\n        fun width(width: Int): Builder {\n            mConfig.width = width\n            return this\n        }\n\n        fun height(height: Int): Builder {\n            mConfig.height = height\n            return this\n        }\n\n        fun margin(margin: Int): Builder {\n            mConfig.margin = margin\n            return this\n        }\n\n        fun animator(@AnimatorRes animatorResId: Int): Builder {\n            mConfig.animatorResId = animatorResId\n            return this\n        }\n\n        fun animatorReverse(@AnimatorRes animatorReverseResId: Int): Builder {\n            mConfig.animatorReverseResId = animatorReverseResId\n            return this\n        }\n\n        fun drawable(@DrawableRes backgroundResId: Int): Builder {\n            mConfig.backgroundResId = backgroundResId\n            return this\n        }\n\n        fun drawableUnselected(@DrawableRes unselectedBackgroundId: Int): Builder {\n            mConfig.unselectedBackgroundId = unselectedBackgroundId\n            return this\n        }\n\n        fun orientation(orientation: Int): Builder {\n            mConfig.orientation = orientation\n            return this\n        }\n\n        fun gravity(gravity: Int): Builder {\n            mConfig.gravity = gravity\n            return this\n        }\n\n        fun build(): Config {\n            return mConfig\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/proto/UserPreferences.proto",
    "content": "syntax = \"proto3\";\n\noption java_package = \"com.example.c001apk.compose\";\noption java_multiple_files = true;\n\nmessage UserPreferences {\n  ThemeMode themeMode = 1;\n  bool materialYou = 2;\n  bool pureBlack = 3;\n  float fontScale = 4;\n  float contentScale = 5;\n  string szlmId = 6;\n  int32 imageQuality = 7;\n  bool imageFilter = 8;\n  bool openInBrowser = 9;\n  bool showSquare = 10;\n  bool recordHistory = 11;\n  bool showEmoji = 12;\n  bool checkUpdate = 13;\n  bool checkCount = 14;\n  string versionName = 15;\n  string apiVersion = 16;\n  string versionCode = 17;\n  string manufacturer = 18;\n  string brand = 19;\n  string model = 20;\n  string buildNumber = 21;\n  string sdkInt = 22;\n  string androidVersion = 23;\n  string userAgent = 24;\n  string xAppDevice = 25;\n  string xAppToken = 26;\n  bool isLogin = 27;\n  string userAvatar = 28;\n  string username = 29;\n  string level = 30;\n  string experience = 31;\n  string nextLevelExperience = 32;\n  string uid = 33;\n  string token = 34;\n  FollowType followType = 35;\n  string recentIds = 36;\n  int32 checkCountPeriod = 37;\n  string installTime = 38;\n  ThemeType themeType = 39;\n  string seedColor = 40;\n  int32 paletteStyle = 41;\n}\n\nenum ThemeType{\n  Default = 0;\n  Red = 1;\n  Pink = 2;\n  Purple = 3;\n  Indigo = 4;\n  Blue = 5;\n  LightBlue = 6;\n  Cyan = 7;\n  Teal = 8;\n  Green = 9;\n  LightGreen = 10;\n  Lime = 11;\n  Yellow = 12;\n  Amber = 13;\n  Orange = 14;\n  DeepOrange = 15;\n  Brown = 16;\n  BlueGrey = 17;\n  Sakura = 18;\n  Custom = 19;\n}\n\nenum FollowType{\n  ALL = 0;\n  USER = 1;\n  TOPIC = 2;\n  PRODUCT = 3;\n  APP = 4;\n}\n\nenum ThemeMode {\n  FOLLOW_SYSTEM = 0;\n  ALWAYS_ON = 1;\n  ALWAYS_OFF = 2;\n}\n"
  },
  {
    "path": "app/src/main/res/anim/anim_bottom_sheet_slide_down.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <translate\n        android:duration=\"@android:integer/config_mediumAnimTime\"\n        android:fromYDelta=\"0%p\"\n        android:interpolator=\"@android:anim/accelerate_interpolator\"\n        android:toYDelta=\"100%p\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/anim_bottom_sheet_slide_up.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <translate\n        android:duration=\"@android:integer/config_mediumAnimTime\"\n        android:fromYDelta=\"100%p\"\n        android:interpolator=\"@android:anim/accelerate_interpolator\"\n        android:toXDelta=\"0%p\" />\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/indicator_animator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\">\n\n    <objectAnimator\n        android:propertyName=\"alpha\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"0.2\"\n        android:valueTo=\"1.0\"/>\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/indicator_animator_reverse.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\">\n\n    <objectAnimator\n        android:propertyName=\"alpha\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"1.0\"\n        android:valueTo=\"0.2\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/anim/scale_with_alpha.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"@android:integer/config_shortAnimTime\">\n\n    <objectAnimator\n        android:propertyName=\"alpha\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"0.5\"\n        android:valueTo=\"1.0\"/>\n\n    <objectAnimator\n        android:propertyName=\"scaleX\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"1.0\"\n        android:valueTo=\"1.8\"/>\n\n    <objectAnimator\n        android:propertyName=\"scaleY\"\n        android:valueType=\"floatType\"\n        android:valueFrom=\"1.0\"\n        android:valueTo=\"1.8\"/>\n</set>"
  },
  {
    "path": "app/src/main/res/color/image_stroke.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"?attr/colorSurfaceVariant\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_author.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"58.579994dp\"\n    android:height=\"29.399998dp\"\n    android:viewportWidth=\"58.58\"\n    android:viewportHeight=\"29.4\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M17.17,9.09h2.17c-0.68,-0.81 -1.35,-1.53 -2.03,-2.17l1.48,-1.33c0.7,0.6 1.46,1.3 2.28,2.13l-1.51,1.37h1.97V5.04h2.39v4.05h1.82l-1.39,-1.22c0.84,-0.77 1.59,-1.54 2.26,-2.32l1.63,1.31c-0.73,0.8 -1.45,1.54 -2.18,2.22h2.5v2.09h-3.69c1.23,1.02 2.63,1.73 4.21,2.13c-0.47,0.6 -0.94,1.29 -1.41,2.09c-1.41,-0.7 -2.67,-1.67 -3.76,-2.93v2.28h-2.08l1.32,0.4c-0.13,0.2 -0.27,0.39 -0.41,0.59h6.09v2.09h-2.01c-0.46,0.96 -0.99,1.79 -1.6,2.49l3.71,1.39l-1.31,2.11c-1.5,-0.69 -2.93,-1.32 -4.28,-1.89c-1.55,0.95 -3.68,1.6 -6.37,1.95c-0.38,-0.81 -0.79,-1.56 -1.23,-2.26c1.87,-0.12 3.44,-0.41 4.71,-0.86c-1.18,-0.46 -2.29,-0.85 -3.32,-1.2c0.45,-0.53 0.9,-1.1 1.34,-1.73h-2.02v-2.09h3.37c0.27,-0.44 0.53,-0.89 0.79,-1.37l0.93,0.29v-2.38c-1.17,1.29 -2.43,2.36 -3.8,3.19c-0.28,-0.42 -0.58,-0.83 -0.88,-1.23l-1.07,1.42c-0.32,-0.32 -0.65,-0.62 -0.97,-0.91v8.99h-2.36V15.7c-0.49,1.35 -1.09,2.62 -1.79,3.82c-0.2,-1.25 -0.41,-2.41 -0.61,-3.48c0.92,-1.53 1.64,-3.19 2.18,-4.98h-1.99V8.82h2.2V5.1h2.36v3.72h1.8v2.24h-1.8v1.05c0.57,0.38 1.16,0.79 1.78,1.24c1.29,-0.55 2.49,-1.28 3.6,-2.18h-3V9.09zM20.46,18.63l2.34,0.82c0.53,-0.46 0.96,-1 1.31,-1.61h-2.96C20.92,18.09 20.69,18.36 20.46,18.63z\"\n        tools:ignore=\"VectorPath\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M29.69,20.49h7.87v-3.55H31.7v-2.47h5.85v-3.44h-6.76V8.52h7.36c-0.42,-0.84 -0.9,-1.66 -1.43,-2.47l2.6,-1.18c0.57,0.84 1.14,1.77 1.71,2.79L39.2,8.52h8.08v2.51h-6.86v3.44h5.98v2.47h-5.98v3.55h7.88v2.55H29.69V20.49z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M53.09,29.15H5.49c-2.89,0 -5.23,-2.34 -5.23,-5.23V5.49c0,-2.89 2.34,-5.23 5.23,-5.23h47.6c2.89,0 5.23,2.34 5.23,5.23v18.43C58.32,26.81 55.98,29.15 53.09,29.15z\"\n        android:strokeWidth=\"0.5\"\n        android:strokeColor=\"#FF000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_feed_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"58.579994dp\"\n    android:height=\"29.399998dp\"\n    android:viewportWidth=\"58.58\"\n    android:viewportHeight=\"29.4\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M9.33,21.64h2.13v-8.3h5.75v-0.77H9.73v-1.73h7.47v-0.84h-6.67V5.68h16.73v4.33h-7.15v0.84h7.9v1.73h-7.9v0.77h6.19v8.3h2.15v1.76H9.33V21.64zM13.05,8.44h2.4V7.25h-2.4V8.44zM23.65,15.03h-9.53v0.73h9.53V15.03zM14.12,17.71h9.53v-0.73h-9.53V17.71zM14.12,19.67h9.53v-0.73h-9.53V19.67zM14.12,21.64h9.53v-0.75h-9.53V21.64zM17.71,8.44h2.4V7.25h-2.4V8.44zM24.76,8.44V7.25h-2.4v1.19H24.76z\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M29.02,6h6.77v2.63h-1.71v11.83c0,0.78 -0.13,1.4 -0.41,1.85c-0.27,0.45 -0.64,0.74 -1.11,0.85c-0.47,0.11 -1.51,0.17 -3.14,0.17c-0.09,-0.77 -0.24,-1.6 -0.44,-2.49c0.59,0.06 1.16,0.1 1.72,0.1c0.51,0 0.77,-0.28 0.77,-0.84V8.63h-2.45V6zM40.5,13.48h2.53c0,1.87 -0.15,3.43 -0.45,4.67c1.8,1.14 3.65,2.39 5.55,3.77l-1.63,2.07c-1.28,-1.06 -2.88,-2.28 -4.8,-3.65c-0.86,1.44 -2.65,2.68 -5.38,3.73c-0.4,-0.75 -0.91,-1.58 -1.53,-2.49c1.38,-0.38 2.53,-0.87 3.45,-1.48c0.92,-0.61 1.52,-1.44 1.82,-2.5S40.5,15.16 40.5,13.48zM36.07,5.79H47.8v2.36h-5.07l-0.34,1.84h4.88v8.76h-2.57v-6.46h-5.92v6.59H36.2V9.99h3.3l0.34,-1.84h-3.78V5.79z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M53.09,29.15H5.49c-2.89,0 -5.23,-2.34 -5.23,-5.23V5.49c0,-2.89 2.34,-5.23 5.23,-5.23h47.6c2.89,0 5.23,2.34 5.23,5.23v18.43C58.32,26.81 55.98,29.15 53.09,29.15z\"\n        android:strokeWidth=\"0.5\"\n        android:strokeColor=\"#FF000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher_foreground.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <group\n        android:scaleX=\"2\"\n        android:scaleY=\"2\"\n        android:translateX=\"30\"\n        android:translateY=\"30\">\n        <path\n            android:fillColor=\"@color/ic_launcher_foreground\"\n            android:pathData=\"M18.6 6.62c-1.44 0-2.8.56-3.77 1.53L7.8 14.39c-.64.64-1.49.99-2.4.99-1.87 0-3.39-1.51-3.39-3.38S3.53 8.62 5.4 8.62c.91 0 1.76.35 2.44 1.03l1.13 1 1.51-1.34L9.22 8.2C8.2 7.18 6.84 6.62 5.4 6.62 2.42 6.62 0 9.04 0 12s2.42 5.38 5.4 5.38c1.44 0 2.8-.56 3.77-1.53l7.03-6.24c.64-.64 1.49-.99 2.4-.99 1.87 0 3.39 1.51 3.39 3.38s-1.52 3.38-3.39 3.38c-.9 0-1.76-.35-2.44-1.03l-1.14-1.01-1.51 1.34 1.27 1.12c1.02 1.01 2.37 1.57 3.82 1.57 2.98 0 5.4-2.41 5.4-5.38s-2.42-5.37-5.4-5.37z\" />\n    </group>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_photo.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M19 5v14H5V5h14m0-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-4.86 8.86l-3 3.87L9 13.14 6 17h12l-3.86-5.14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_subauthor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"58.579994dp\"\n    android:height=\"29.399998dp\"\n    android:viewportWidth=\"58.58\"\n    android:viewportHeight=\"29.4\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12.02,5.73h16.17v5.79H14.68c-0.01,1.9 -0.06,3.49 -0.15,4.78h14.61v2.16h-4.5c1.75,1.53 3.16,2.81 4.21,3.85l-1.88,1.56l-1.01,-1.04c-6.07,0.27 -9.52,0.52 -10.34,0.74l-1.06,-2.05c1.01,-0.61 2.06,-1.63 3.16,-3.05H14.3c-0.23,1.7 -0.87,3.37 -1.94,5.03c-0.76,-0.91 -1.44,-1.66 -2.03,-2.24c0.53,-0.93 0.94,-2 1.23,-3.2c0.29,-1.21 0.44,-3.31 0.46,-6.31V5.73zM25.53,9.36V7.9H14.68v1.46H25.53zM15.59,12.77h12.18v2.16H15.59V12.77zM22.55,19.45l1.19,-0.99h-2.98c-0.78,0.96 -1.48,1.79 -2.1,2.5l5.24,-0.2L22.55,19.45z\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M29.89,20.4h7.86v-3.55H31.9v-2.47h5.84v-3.43h-6.75v-2.5h7.35c-0.42,-0.83 -0.9,-1.66 -1.43,-2.47l2.6,-1.18c0.57,0.83 1.14,1.76 1.71,2.79l-1.83,0.85h8.07v2.5h-6.85v3.43h5.98v2.47h-5.98v3.55h7.88v2.54h-18.6V20.4z\" />\n    <path\n        android:fillColor=\"#00000000\"\n        android:pathData=\"M53.09,29.15H5.49c-2.89,0 -5.23,-2.34 -5.23,-5.23V5.49c0,-2.89 2.34,-5.23 5.23,-5.23h47.6c2.89,0 5.23,2.34 5.23,5.23v18.43C58.32,26.81 55.98,29.15 53.09,29.15z\"\n        android:strokeWidth=\"0.5\"\n        android:strokeColor=\"#FF000000\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/outline_alternate_email_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M12,1.95c-5.52,0 -10,4.48 -10,10s4.48,10 10,10h5v-2h-5c-4.34,0 -8,-3.66 -8,-8s3.66,-8 8,-8 8,3.66 8,8v1.43c0,0.79 -0.71,1.57 -1.5,1.57s-1.5,-0.78 -1.5,-1.57v-1.43c0,-2.76 -2.24,-5 -5,-5s-5,2.24 -5,5 2.24,5 5,5c1.38,0 2.64,-0.56 3.54,-1.47 0.65,0.89 1.77,1.47 2.96,1.47 1.97,0 3.5,-1.6 3.5,-3.57v-1.43c0,-5.52 -4.48,-10 -10,-10zM12,14.95c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_backspace_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:autoMirrored=\"true\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM22,19L7.07,19L2.4,12l4.66,-7L22,5v14zM10.41,17L14,13.41 17.59,17 19,15.59 15.41,12 19,8.41 17.59,7 14,10.59 10.41,7 9,8.41 12.59,12 9,15.59z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_emoji_emotions_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M15.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\" />\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M8.5,9.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\" />\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M12,18c2.28,0 4.22,-1.66 5,-4H7C7.78,16.34 9.72,18 12,18z\" />\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M11.99,2C6.47,2 2,6.48 2,12c0,5.52 4.47,10 9.99,10C17.52,22 22,17.52 22,12C22,6.48 17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,16.42 16.42,20 12,20z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_image_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M19,5v14L5,19L5,5h14m0,-2L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14.14,11.86l-3,3.87L9,13.14 6,17h12l-3.86,-5.14z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_keyboard_hide_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/black\"\n        android:pathData=\"M20,3L4,3c-1.1,0 -1.99,0.9 -1.99,2L2,15c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,15L4,15L4,5h16v10zM11,6h2v2h-2zM11,9h2v2h-2zM8,6h2v2L8,8zM8,9h2v2L8,11zM5,9h2v2L5,11zM5,6h2v2L5,8zM8,12h8v2L8,14zM14,9h2v2h-2zM14,6h2v2h-2zM17,9h2v2h-2zM17,6h2v2h-2zM12,23l4,-4L8,19z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_keyboard_show_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <group\n        android:pivotX=\"12\"\n        android:pivotY=\"12\"\n        android:rotation=\"180\">\n        <path\n            android:fillColor=\"@android:color/black\"\n            android:pathData=\"M20,3L4,3c-1.1,0 -1.99,0.9 -1.99,2L2,15c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,5c0,-1.1 -0.9,-2 -2,-2zM20,15L4,15L4,5h16v10zM11,6h2v2h-2zM11,9h2v2h-2zM8,6h2v2L8,8zM8,9h2v2L8,11zM5,9h2v2L5,11zM5,6h2v2L5,8zM8,12h8v2L8,14zM14,9h2v2h-2zM14,6h2v2h-2zM17,9h2v2h-2zM17,6h2v2h-2zM12,23l4,-4L8,19z\" />\n    </group>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_note_alt_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"?attr/colorControlNormal\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"@android:color/black\"\n        android:pathData=\"M18,4L6,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,6c0,-1.1 -0.9,-2 -2,-2zM18,18L6,18L6,6h12v12z\" />\n    <path\n        android:fillColor=\"@android:color/black\"\n        android:pathData=\"M15.08,11.03l-2.12,-2.12l-5.96,5.95l0,2.14l2.1,0z\" />\n    <path\n        android:fillColor=\"@android:color/black\"\n        android:pathData=\"M16.85,9.27c0.2,-0.2 0.2,-0.51 0,-0.71l-1.41,-1.41c-0.2,-0.2 -0.51,-0.2 -0.71,0l-1.06,1.06l2.12,2.12L16.85,9.27z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/outline_tag_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M20,10V8h-4V4h-2v4h-4V4H8v4H4v2h4v4H4v2h4v4h2v-4h4v4h2v-4h4v-2h-4v-4H20zM14,14h-4v-4h4V14z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/round_corners_12.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <solid android:color=\"@color/cover\"/>\n    <corners android:radius=\"12dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/selector_bg_12_trans.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"?android:attr/colorControlHighlight\">\n    <item android:id=\"@android:id/mask\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"#000000\" />\n            <corners android:radius=\"12dp\" />\n        </shape>\n    </item>\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n</ripple>"
  },
  {
    "path": "app/src/main/res/drawable/selector_bg_trans.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"?android:attr/colorControlHighlight\">\n    <item android:id=\"@android:id/mask\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"#000000\" />\n        </shape>\n    </item>\n    <item>\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n</ripple>"
  },
  {
    "path": "app/src/main/res/drawable/selector_emoji.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\">\n        <shape>\n            <corners android:radius=\"4dp\" />\n            <solid android:color=\"?colorSurface\" />\n        </shape>\n    </item>\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/selector_emoji_indicator.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"?android:attr/colorControlHighlight\">\n    <item android:id=\"@android:id/mask\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"#000000\" />\n        </shape>\n    </item>\n</ripple>"
  },
  {
    "path": "app/src/main/res/drawable/selector_emoji_indicator_selected.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"?android:attr/colorControlHighlight\">\n    <item android:id=\"@android:id/mask\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"#000000\" />\n        </shape>\n    </item>\n    <item>\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"?colorPrimary\" />\n        </shape>\n    </item>\n</ripple>"
  },
  {
    "path": "app/src/main/res/drawable/selector_reply.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ripple xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:color=\"?android:attr/colorControlHighlight\">\n    <item android:id=\"@android:id/mask\">\n        <shape android:shape=\"rectangle\">\n            <solid android:color=\"#000000\" />\n            <corners\n                android:bottomLeftRadius=\"0dp\"\n                android:bottomRightRadius=\"0dp\"\n                android:topLeftRadius=\"0dp\"\n                android:topRightRadius=\"16dp\" />\n        </shape>\n    </item>\n    <item>\n        <shape>\n            <solid android:color=\"@android:color/transparent\" />\n        </shape>\n    </item>\n</ripple>"
  },
  {
    "path": "app/src/main/res/drawable/shape_oval_primary.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"?attr/colorPrimary\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/white_radius.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n    <solid android:color=\"?colorOnPrimaryContainer\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/layout/activity_reply.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.example.c001apk.compose.view.SmoothInputLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:focusable=\"true\"\n    android:focusableInTouchMode=\"true\"\n    android:orientation=\"vertical\"\n    app:silEmojiPanel=\"@+id/emojiLayout\"\n    app:silInputView=\"@+id/editText\">\n\n    <View\n        android:id=\"@+id/out\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/inputLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"16dp\"\n            android:textSize=\"16sp\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:text=\"回复\" />\n\n        <TextView\n            android:id=\"@+id/publish\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"end\"\n            android:foreground=\"@drawable/selector_reply\"\n            android:padding=\"16dp\"\n            android:text=\"发布\"\n            android:textColor=\"@android:color/darker_gray\"\n            android:textSize=\"16sp\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:id=\"@+id/textInputLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"16dp\"\n            android:layout_marginRight=\"16dp\"\n            app:counterEnabled=\"true\"\n            app:counterMaxLength=\"500\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/title\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/editText\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"@null\"\n                android:gravity=\"start\"\n                android:lineSpacingMultiplier=\"1.2\"\n                android:maxLength=\"500\"\n                android:maxLines=\"8\"\n                android:minLines=\"4\"\n                android:textSize=\"14sp\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n        <com.google.android.material.checkbox.MaterialCheckBox\n            android:id=\"@+id/checkBox\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"10dp\"\n            android:background=\"@null\"\n            android:button=\"@null\"\n            android:drawableStart=\"?android:attr/listChoiceIndicatorMultiple\"\n            android:text=\"回复并转发\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/textInputLayout\" />\n\n        <HorizontalScrollView\n            android:id=\"@+id/imageScrollLayout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:scrollbars=\"none\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/checkBox\">\n\n            <LinearLayout\n                android:id=\"@+id/imageLayout\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"65dp\"\n                android:orientation=\"horizontal\"\n                android:paddingStart=\"11dp\"\n                android:paddingEnd=\"16dp\"\n                android:visibility=\"gone\" />\n\n        </HorizontalScrollView>\n\n        <View\n            android:id=\"@+id/line\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1dp\"\n            android:background=\"?attr/colorSurfaceVariant\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/imageScrollLayout\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@id/line\">\n\n            <ImageView\n                android:id=\"@+id/emojiBtn\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:background=\"?attr/actionBarItemBackground\"\n                android:padding=\"8dp\"\n                android:src=\"@drawable/outline_emoji_emotions_24\" />\n\n            <ImageView\n                android:id=\"@+id/imageBtn\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:background=\"?attr/actionBarItemBackground\"\n                android:padding=\"8dp\"\n                android:src=\"@drawable/outline_image_24\" />\n\n            <ImageView\n                android:id=\"@+id/atBtn\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:background=\"?attr/actionBarItemBackground\"\n                android:padding=\"8dp\"\n                android:src=\"@drawable/outline_alternate_email_24\" />\n\n            <ImageView\n                android:id=\"@+id/tagBtn\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:background=\"?attr/actionBarItemBackground\"\n                android:padding=\"8dp\"\n                android:src=\"@drawable/outline_tag_24\" />\n\n            <ImageView\n                android:id=\"@+id/keyboardBtn\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:background=\"?attr/actionBarItemBackground\"\n                android:padding=\"8dp\"\n                android:src=\"@drawable/outline_keyboard_hide_24\" />\n\n        </LinearLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/emojiLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\">\n\n        <androidx.viewpager2.widget.ViewPager2\n            android:id=\"@+id/emojiPanel\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintBottom_toTopOf=\"@id/line2\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <View\n            android:id=\"@+id/line2\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1dp\"\n            android:background=\"?attr/colorSurfaceVariant\"\n            app:layout_constraintBottom_toTopOf=\"@id/indicator\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\" />\n\n        <LinearLayout\n            android:id=\"@+id/indicator\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"41dp\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toStartOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</com.example.c001apk.compose.view.SmoothInputLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_refresh.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:padding=\"16dp\">\n\n    <com.google.android.material.progressindicator.CircularProgressIndicator\n        android:id=\"@+id/indicator\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:indeterminate=\"true\"\n        app:trackCornerRadius=\"12dp\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_captcha.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:paddingHorizontal=\"10dp\">\n\n    <com.google.android.material.imageview.ShapeableImageView\n        android:id=\"@+id/captchaImg\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"4dp\"\n        android:layout_marginEnd=\"5dp\"\n        android:layout_weight=\"1\"\n        tools:src=\"?colorPrimary\" />\n\n    <com.google.android.material.textfield.TextInputLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginStart=\"5dp\"\n        android:layout_weight=\"1\">\n\n        <com.google.android.material.textfield.TextInputEditText\n            android:id=\"@+id/captchaText\"\n            style=\"@style/Widget.Material3.TextInputEditText.OutlinedBox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:singleLine=\"true\" />\n\n    </com.google.android.material.textfield.TextInputLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_emoji.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"48dp\"\n    android:layout_height=\"48dp\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/imageView\"\n        android:layout_width=\"28dp\"\n        android:layout_height=\"28dp\"\n        tools:src=\"@mipmap/ic_launcher\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_emoji_child_viewpager.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\">\n\n    <com.example.c001apk.compose.view.NestedScrollableHost\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        app:sameDirectionWithParent=\"true\">\n\n        <androidx.viewpager2.widget.ViewPager2\n            android:id=\"@+id/viewPager\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:scrollbars=\"none\" />\n\n    </com.example.c001apk.compose.view.NestedScrollableHost>\n\n    <com.example.c001apk.compose.view.circleindicator.CircleIndicator3\n        android:id=\"@+id/indicator\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"5dp\"\n        app:ci_animator=\"@anim/indicator_animator\"\n        app:ci_animator_reverse=\"@anim/indicator_animator_reverse\"\n        app:ci_drawable=\"@drawable/shape_oval_primary\"\n        app:ci_height=\"5dp\"\n        app:ci_width=\"5dp\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout-land/activity_reply.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/main\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:id=\"@+id/out\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/bottomLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/inputLayout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toStartOf=\"@id/emojiLayout\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <TextView\n                android:id=\"@+id/title\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"16dp\"\n                android:textSize=\"16sp\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:text=\"回复\" />\n\n            <TextView\n                android:id=\"@+id/publish\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?attr/actionBarItemBackground\"\n                android:padding=\"16dp\"\n                android:text=\"发布\"\n                android:textColor=\"@android:color/darker_gray\"\n                android:textSize=\"16sp\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <com.google.android.material.textfield.TextInputLayout\n                android:id=\"@+id/textInputLayout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_marginLeft=\"16dp\"\n                android:layout_marginRight=\"16dp\"\n                app:counterEnabled=\"true\"\n                app:counterMaxLength=\"500\"\n                app:layout_constraintBottom_toTopOf=\"@id/input_bottom\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toBottomOf=\"@id/title\">\n\n                <com.google.android.material.textfield.TextInputEditText\n                    android:id=\"@+id/editText\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"match_parent\"\n                    android:background=\"@null\"\n                    android:gravity=\"start\"\n                    android:lineSpacingMultiplier=\"1.2\"\n                    android:maxLength=\"500\"\n                    android:textSize=\"14sp\" />\n\n            </com.google.android.material.textfield.TextInputLayout>\n\n            <LinearLayout\n                android:id=\"@+id/input_bottom\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_vertical\"\n                android:orientation=\"horizontal\"\n                app:layout_constraintBottom_toTopOf=\"@id/imageScrollLayout\"\n                app:layout_constraintStart_toStartOf=\"parent\">\n\n                <com.google.android.material.checkbox.MaterialCheckBox\n                    android:id=\"@+id/checkBox\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"10dp\"\n                    android:background=\"@null\"\n                    android:button=\"@null\"\n                    android:drawableStart=\"?android:attr/listChoiceIndicatorMultiple\"\n                    android:text=\"回复并转发\" />\n\n                <ImageView\n                    android:id=\"@+id/imageBtn\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:background=\"?attr/actionBarItemBackground\"\n                    android:padding=\"8dp\"\n                    android:src=\"@drawable/outline_image_24\"\n                    app:layout_constraintBottom_toBottomOf=\"@id/checkBox\"\n                    app:layout_constraintStart_toEndOf=\"@id/checkBox\"\n                    app:layout_constraintTop_toTopOf=\"@id/checkBox\" />\n\n                <ImageView\n                    android:id=\"@+id/atBtn\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:background=\"?attr/actionBarItemBackground\"\n                    android:padding=\"8dp\"\n                    android:src=\"@drawable/outline_alternate_email_24\" />\n\n                <ImageView\n                    android:id=\"@+id/tagBtn\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:background=\"?attr/actionBarItemBackground\"\n                    android:padding=\"8dp\"\n                    android:src=\"@drawable/outline_tag_24\" />\n\n            </LinearLayout>\n\n            <HorizontalScrollView\n                android:id=\"@+id/imageScrollLayout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:scrollbars=\"none\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\">\n\n                <LinearLayout\n                    android:id=\"@+id/imageLayout\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"65dp\"\n                    android:orientation=\"horizontal\"\n                    android:paddingStart=\"11dp\"\n                    android:paddingEnd=\"16dp\"\n                    android:visibility=\"gone\" />\n\n            </HorizontalScrollView>\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/emojiLayout\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"10dp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintEnd_toEndOf=\"parent\"\n            app:layout_constraintStart_toEndOf=\"@id/inputLayout\">\n\n            <androidx.viewpager2.widget.ViewPager2\n                android:id=\"@+id/emojiPanel\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:layout_constraintBottom_toTopOf=\"@id/line2\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <View\n                android:id=\"@+id/line2\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"1dp\"\n                android:background=\"?attr/colorSurfaceVariant\"\n                app:layout_constraintBottom_toTopOf=\"@id/indicator\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\" />\n\n            <LinearLayout\n                android:id=\"@+id/indicator\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"41dp\"\n                android:orientation=\"horizontal\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\"/>\n    <monochrome android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/ic_launcher_background\"/>\n    <foreground android:drawable=\"@drawable/ic_launcher_foreground\"/>\n    <monochrome android:drawable=\"@drawable/ic_launcher_foreground\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <declare-styleable name=\"NestedScrollableHost\">\n        <attr name=\"sameDirectionWithParent\" format=\"boolean\" />\n        <attr name=\"childVerticalScroll\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"SmoothInputLayout\">\n        <attr name=\"silDefaultKeyboardHeight\" format=\"dimension\" />\n        <attr name=\"silMinKeyboardHeight\" format=\"dimension\" />\n        <attr name=\"silInputView\" format=\"reference\" />\n        <attr name=\"silEmojiPanel\" format=\"reference\" />\n        <attr name=\"silAutoSaveKeyboardHeight\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"BaseCircleIndicator\">\n        <attr name=\"ci_width\" format=\"dimension\"/>\n        <attr name=\"ci_height\" format=\"dimension\"/>\n        <attr name=\"ci_margin\" format=\"dimension\"/>\n        <attr name=\"ci_animator\" format=\"reference\"/>\n        <attr name=\"ci_animator_reverse\" format=\"reference\"/>\n        <attr name=\"ci_drawable\" format=\"reference\"/>\n        <attr name=\"ci_drawable_unselected\" format=\"reference\"/>\n    </declare-styleable>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"purple_200\">#FFBB86FC</color>\n    <color name=\"purple_500\">#FF6200EE</color>\n    <color name=\"purple_700\">#FF3700B3</color>\n    <color name=\"teal_200\">#FF03DAC5</color>\n    <color name=\"teal_700\">#FF018786</color>\n    <color name=\"black\">#FF000000</color>\n    <color name=\"white\">#FFFFFFFF</color>\n    <color name=\"ic_launcher_foreground\">#FFFFFF</color>\n    <color name=\"ic_launcher_background\">#607D8B</color>\n    <color name=\"cover\">#0D000000</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">c001apk-compose</string>\n    <string name=\"home\">Home</string>\n    <string name=\"message\">Message</string>\n    <string name=\"settings\">Settings</string>\n    <string name=\"about_source_code\"><![CDATA[View source code at %1$s]]></string>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/themes.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <style name=\"Theme.C001apkCompose\" parent=\"Theme.Material3.DayNight.NoActionBar\" />\n\n    <style name=\"radius12\">\n        <item name=\"cornerSize\">12dp</item>\n    </style>\n\n    <style name=\"ThemeOverlay.MaterialAlertDialog.Rounded\" parent=\"ThemeOverlay.MaterialComponents.MaterialAlertDialog\">\n        <item name=\"alertDialogStyle\">@style/MaterialAlertDialog.Rounded</item>\n    </style>\n\n    <style name=\"MaterialAlertDialog.Rounded\" parent=\"MaterialAlertDialog.MaterialComponents\">\n        <item name=\"shapeAppearance\">@style/ShapeAppearance.MaterialAlertDialog.Rounded</item>\n    </style>\n\n    <style name=\"ShapeAppearance.MaterialAlertDialog.Rounded\" parent=\"\">\n        <item name=\"cornerSize\">12dp</item>\n    </style>\n\n    <style name=\"AppThemeTranslucent\" parent=\"Theme.Material3.DayNight.NoActionBar\">\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:backgroundDimEnabled\">true</item>\n        <item name=\"android:windowActionBar\">false</item>\n        <item name=\"android:windowNoTitle\">true</item>\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowContentOverlay\">@null</item>\n    </style>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"cover\">#0DFFFFFF</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-night-v31/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_foreground\">@android:color/system_accent1_800</color>\n    <color name=\"ic_launcher_background\">@android:color/system_accent1_200</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-v31/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"ic_launcher_foreground\">@android:color/system_accent1_0</color>\n    <color name=\"ic_launcher_background\">@android:color/system_accent1_600</color>\n</resources>"
  },
  {
    "path": "app/src/main/res/xml/backup_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample backup rules file; uncomment and customize as necessary.\n   See https://developer.android.com/guide/topics/data/autobackup\n   for details.\n   Note: This file is ignored for devices older that API 31\n   See https://developer.android.com/about/versions/12/backup-restore\n-->\n<full-backup-content>\n    <!--\n   <include domain=\"sharedpref\" path=\".\"/>\n   <exclude domain=\"sharedpref\" path=\"device.xml\"/>\n-->\n</full-backup-content>"
  },
  {
    "path": "app/src/main/res/xml/data_extraction_rules.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n   Sample data extraction rules file; uncomment and customize as necessary.\n   See https://developer.android.com/about/versions/12/backup-restore#xml-changes\n   for details.\n-->\n<data-extraction-rules>\n    <cloud-backup>\n        <!-- TODO: Use <include> and <exclude> to control what is backed up.\n        <include .../>\n        <exclude .../>\n        -->\n    </cloud-backup>\n    <!--\n    <device-transfer>\n        <include .../>\n        <exclude .../>\n    </device-transfer>\n    -->\n</data-extraction-rules>"
  },
  {
    "path": "app/src/main/res/xml/file_provider_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-cache-path name=\"imageShare\" path=\"imageShare/\" />\n</paths>"
  },
  {
    "path": "app/src/test/java/com/example/c001apk/compose/ExampleUnitTest.kt",
    "content": "package com.example.c001apk.compose\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n}"
  },
  {
    "path": "build.gradle.kts",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\nplugins {\n    alias(libs.plugins.android.application) apply false\n    alias(libs.plugins.google.dagger.hilt.android) apply false\n    alias(libs.plugins.google.devtools.ksp) apply false\n    alias(libs.plugins.google.protobuf) apply false\n    alias(libs.plugins.jetbrains.kotlin.android) apply false\n    alias(libs.plugins.jetbrains.kotlin.plugin.compose) apply false\n    alias(libs.plugins.kotlin.parcelize) apply false\n}"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\naccompanist = \"0.36.0\"\nactivity = \"1.9.2\"\nactivityCompose = \"1.9.2\"\nagp = \"8.6.0\"\nandroidxCompose = \"1.7.1\"\nandroidxDataStore = \"1.1.1\"\nandroidxHiltNavigationCompose = \"1.2.0\"\nandroidxLifecycle = \"2.8.5\"\nappcompat = \"1.7.0\"\nappiconloader-coil = \"1.5.0\"\ncoilCompose = \"2.7.0\"\ncoilTransformers = \"1.0.6\"\ncomposeBom = \"2024.09.01\"\ncomposeNavigation = \"2.8.0\"\nconstraintlayoutCompose = \"1.1.0-beta01\"\ncoreKtx = \"1.13.1\"\nespressoCore = \"3.6.1\"\nexifinterface = \"1.3.7\"\nhilt = \"2.52\"\njbcrypt = \"0.4\"\njsoup = \"1.18.1\"\njunit = \"4.13.2\"\njunitVersion = \"1.2.1\"\nkotlin = \"2.0.20\"\nksp = \"2.0.20-1.0.25\"\nleakcanary = \"2.14\"\nmaterial = \"1.12.0\"\nmaterial3 = \"1.3.0\"\nmaterialKolor = \"1.7.0\"\nokhttp = \"4.12.0\"\noss-android-sdk = \"2.9.19\"\nprotobuf = \"4.28.1\"\nprotobufPlugin = \"0.9.4\"\nrecyclerview = \"1.3.2\"\nretrofit = \"2.11.0\"\nroom = \"2.6.1\"\nsketch-gif = \"2.7.1\"\ntoolbarCompose = \"2.3.5\"\nviewpager2 = \"1.1.0\"\nwebkit = \"1.11.0\"\nmaterial3WindowSizeClassAndroid = \"1.3.0\"\n\n[libraries]\nandroidx-activity = { group = \"androidx.activity\", name = \"activity\", version.ref = \"activity\" }\nandroidx-activity-compose = { group = \"androidx.activity\", name = \"activity-compose\", version.ref = \"activityCompose\" }\nandroidx-appcompat = { module = \"androidx.appcompat:appcompat\", version.ref = \"appcompat\" }\nandroidx-compose-bom = { group = \"androidx.compose\", name = \"compose-bom\", version.ref = \"composeBom\" }\nandroidx-compose-material-icons-extended = { group = \"androidx.compose.material\", name = \"material-icons-extended\", version.ref = \"androidxCompose\" }\nandroidx-compose-navigation = { group = \"androidx.navigation\", name = \"navigation-compose\", version.ref = \"composeNavigation\" }\nandroidx-constraintlayout-compose = { module = \"androidx.constraintlayout:constraintlayout-compose\", version.ref = \"constraintlayoutCompose\" }\nandroidx-core-ktx = { group = \"androidx.core\", name = \"core-ktx\", version.ref = \"coreKtx\" }\nandroidx-datastore-core = { group = \"androidx.datastore\", name = \"datastore\", version.ref = \"androidxDataStore\" }\nandroidx-espresso-core = { group = \"androidx.test.espresso\", name = \"espresso-core\", version.ref = \"espressoCore\" }\nandroidx-exifinterface = { module = \"androidx.exifinterface:exifinterface\", version.ref = \"exifinterface\" }\nandroidx-hilt-navigation-compose = { group = \"androidx.hilt\", name = \"hilt-navigation-compose\", version.ref = \"androidxHiltNavigationCompose\" }\nandroidx-junit = { group = \"androidx.test.ext\", name = \"junit\", version.ref = \"junitVersion\" }\nandroidx-lifecycle-livedata-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-livedata-ktx\", version.ref = \"androidxLifecycle\" }\nandroidx-lifecycle-runtime-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-compose\", version.ref = \"androidxLifecycle\" }\nandroidx-lifecycle-runtime-ktx = { group = \"androidx.lifecycle\", name = \"lifecycle-runtime-ktx\", version.ref = \"androidxLifecycle\" }\nandroidx-lifecycle-viewModel-compose = { group = \"androidx.lifecycle\", name = \"lifecycle-viewmodel-compose\", version.ref = \"androidxLifecycle\" }\nandroidx-material3 = { group = \"androidx.compose.material3\", name = \"material3\", version.ref = \"material3\" }\nandroidx-material3-adaptive-navigation-suite = { group = \"androidx.compose.material3\", name = \"material3-adaptive-navigation-suite\", version.ref = \"material3\" }\nandroidx-material3-window-size-android = { group = \"androidx.compose.material3\", name = \"material3-window-size-class-android\", version.ref = \"material3WindowSizeClassAndroid\" }\nandroidx-recyclerview = { module = \"androidx.recyclerview:recyclerview\", version.ref = \"recyclerview\" }\nandroidx-room-compiler = { module = \"androidx.room:room-compiler\", version.ref = \"room\" }\nandroidx-room-ktx = { module = \"androidx.room:room-ktx\", version.ref = \"room\" }\nandroidx-room-runtime = { module = \"androidx.room:room-runtime\", version.ref = \"room\" }\nandroidx-ui = { group = \"androidx.compose.ui\", name = \"ui\", version.ref = \"androidxCompose\" }\nandroidx-ui-graphics = { group = \"androidx.compose.ui\", name = \"ui-graphics\", version.ref = \"androidxCompose\" }\nandroidx-ui-test-junit4 = { group = \"androidx.compose.ui\", name = \"ui-test-junit4\", version.ref = \"androidxCompose\" }\nandroidx-ui-test-manifest = { group = \"androidx.compose.ui\", name = \"ui-test-manifest\", version.ref = \"androidxCompose\" }\nandroidx-ui-tooling = { group = \"androidx.compose.ui\", name = \"ui-tooling\", version.ref = \"androidxCompose\" }\nandroidx-ui-tooling-preview = { group = \"androidx.compose.ui\", name = \"ui-tooling-preview\", version.ref = \"androidxCompose\" }\nandroidx-viewpager2 = { module = \"androidx.viewpager2:viewpager2\", version.ref = \"viewpager2\" }\nandroidx-webkit = { module = \"androidx.webkit:webkit\", version.ref = \"webkit\" }\ncoil = { module = \"io.coil-kt:coil\", version.ref = \"coilCompose\" }\ncoil-base = { module = \"io.coil-kt:coil-base\", version.ref = \"coilCompose\" }\ncoil-compose = { module = \"io.coil-kt:coil-compose\", version.ref = \"coilCompose\" }\ncoil-gif = { module = \"io.coil-kt:coil-gif\", version.ref = \"coilCompose\" }\ncoil-svg = { module = \"io.coil-kt:coil-svg\", version.ref = \"coilCompose\" }\ngoogle-accompanist-drawablepainter = { group = \"com.google.accompanist\", name = \"accompanist-drawablepainter\", version.ref = \"accompanist\" }\ngoogle-android-material = { module = \"com.google.android.material:material\", version.ref = \"material\" }\ngoogle-dagger-hilt-android = { module = \"com.google.dagger:hilt-android\", version.ref = \"hilt\" }\ngoogle-dagger-hilt-android-compiler = { module = \"com.google.dagger:hilt-android-compiler\", version.ref = \"hilt\" }\ngoogle-protobuf-kotlin-lite = { group = \"com.google.protobuf\", name = \"protobuf-kotlin-lite\", version.ref = \"protobuf\" }\ngoogle-protobuf-protoc = { group = \"com.google.protobuf\", name = \"protoc\", version.ref = \"protobuf\" }\njbcrypt = { module = \"org.mindrot:jbcrypt\", version.ref = \"jbcrypt\" }\njp-wasabeef-transformers-coil = { module = \"jp.wasabeef.transformers:coil\", version.ref = \"coilTransformers\" }\njsoup = { module = \"org.jsoup:jsoup\", version.ref = \"jsoup\" }\njunit = { group = \"junit\", name = \"junit\", version.ref = \"junit\" }\nkotlin-stdlib = { module = \"org.jetbrains.kotlin:kotlin-stdlib\", version.ref = \"kotlin\" }\nleakcanary-android = { module = \"com.squareup.leakcanary:leakcanary-android\", version.ref = \"leakcanary\" }\nmaterial-kolor = { module = \"com.materialkolor:material-kolor\", version.ref = \"materialKolor\" }\nme-zhanghai-android-appiconloader-coil = { group = \"me.zhanghai.android.appiconloader\", name = \"appiconloader-coil\", version.ref = \"appiconloader-coil\" }\noss-android-sdk = { module = \"com.aliyun.dpa:oss-android-sdk\", version.ref = \"oss-android-sdk\" }\nsketch-gif = { module = \"io.github.panpf.sketch:sketch-gif\", version.ref = \"sketch-gif\" }\nsquareup-okhttp = { module = \"com.squareup.okhttp3:okhttp\", version.ref = \"okhttp\" }\nsquareup-okhttp3-logging-interceptor = { module = \"com.squareup.okhttp3:logging-interceptor\", version.ref = \"okhttp\" }\nsquareup-retrofit = { module = \"com.squareup.retrofit2:retrofit\", version.ref = \"retrofit\" }\nsquareup-retrofit-converter-gson = { module = \"com.squareup.retrofit2:converter-gson\", version.ref = \"retrofit\" }\ntoolbar-compose = { module = \"me.onebone:toolbar-compose\", version.ref = \"toolbarCompose\" }\n\n[plugins]\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\ngoogle-dagger-hilt-android = { id = \"com.google.dagger.hilt.android\", version.ref = \"hilt\" }\ngoogle-devtools-ksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\ngoogle-protobuf = { id = \"com.google.protobuf\", version.ref = \"protobufPlugin\" }\njetbrains-kotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\njetbrains-kotlin-plugin-compose = { id = \"org.jetbrains.kotlin.plugin.compose\", version.ref = \"kotlin\" }\nkotlin-parcelize = { id = \"org.jetbrains.kotlin.plugin.parcelize\", version.ref = \"kotlin\" }\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.10.1-bin.zip\nnetworkTimeout=10000\nvalidateDistributionUrl=true\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. For more details, visit\n# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# Enables namespacing of each library's R class so that its R class includes only the\n# resources declared in the library itself and none from the library's dependencies,\n# thereby reducing the size of the R class for that library\nandroid.nonTransitiveRClass=true"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\n# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)\nAPP_HOME=$( cd \"${APP_HOME:-./}\" > /dev/null && pwd -P ) || exit\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    if ! command -v java >/dev/null 2>&1\n    then\n        die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC2039,SC3045\n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Collect all arguments for the java command:\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,\n#     and any embedded shellness will be escaped.\n#   * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be\n#     treated as '${Hostname}' itself on the command line.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho. 1>&2\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2\necho. 1>&2\necho Please set the JAVA_HOME variable in your environment to match the 1>&2\necho location of your Java installation. 1>&2\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\"\n  ]\n}\n"
  },
  {
    "path": "settings.gradle.kts",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        mavenCentral()\n        gradlePluginPortal()\n    }\n}\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        google()\n        mavenCentral()\n        maven (\"https://jitpack.io\")\n        maven(\"https://oss.sonatype.org/content/repositories/public\")\n        gradlePluginPortal()\n    }\n}\n\nrootProject.name = \"c001apk-compose\"\ninclude(\":app\", \":mojito\", \":SketchImageViewLoader\", \":coilimageLoader\")\n"
  }
]